@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.
Files changed (336) hide show
  1. package/CHANGELOG.md +788 -0
  2. package/LICENSE +21 -0
  3. package/README.md +541 -0
  4. package/dist/.build-fingerprint +1 -0
  5. package/dist/agent-file-refresh-ZEMK33AX.js +38 -0
  6. package/dist/answer-shape-K67TAAPY.js +33 -0
  7. package/dist/backend-dir-2U53CIAL.js +39 -0
  8. package/dist/backend-slot-7QXW2QYC.js +28 -0
  9. package/dist/bin.d.ts +1 -0
  10. package/dist/bin.js +255 -0
  11. package/dist/branch-NCRNWQBQ.js +61 -0
  12. package/dist/branch-commands-ACIRCEQJ.js +79 -0
  13. package/dist/bundle.d.ts +223 -0
  14. package/dist/bundle.js +153 -0
  15. package/dist/capture-WAXPD7DR.js +41 -0
  16. package/dist/chunk-27GWNP2I.js +23 -0
  17. package/dist/chunk-2CMJZDCL.js +177 -0
  18. package/dist/chunk-2EZ3ZJZZ.js +302 -0
  19. package/dist/chunk-2GNFDZA2.js +390 -0
  20. package/dist/chunk-2MNLRIOX.js +11 -0
  21. package/dist/chunk-2TFRR6JV.js +104 -0
  22. package/dist/chunk-2ZUEE4EK.js +408 -0
  23. package/dist/chunk-36JUQZVQ.js +22 -0
  24. package/dist/chunk-3DRXWLJ5.js +83 -0
  25. package/dist/chunk-3JDU4VRL.js +137 -0
  26. package/dist/chunk-3KZCLN4Z.js +241 -0
  27. package/dist/chunk-3LDULPRE.js +168 -0
  28. package/dist/chunk-3M4LFYNQ.js +111 -0
  29. package/dist/chunk-3XI5CTUE.js +22 -0
  30. package/dist/chunk-3YW23UFH.js +50 -0
  31. package/dist/chunk-45UIT5T2.js +862 -0
  32. package/dist/chunk-4BW2SG2N.js +46 -0
  33. package/dist/chunk-4KHEMC32.js +79 -0
  34. package/dist/chunk-4POWHT2K.js +321 -0
  35. package/dist/chunk-4Q2GN7NW.js +192 -0
  36. package/dist/chunk-5A2PLKZB.js +70 -0
  37. package/dist/chunk-5FBYXK5P.js +159 -0
  38. package/dist/chunk-5HVXPTU4.js +323 -0
  39. package/dist/chunk-5QU2P5CA.js +304 -0
  40. package/dist/chunk-5V5XU32D.js +191 -0
  41. package/dist/chunk-5ZSO4T2G.js +10 -0
  42. package/dist/chunk-622HUCZT.js +10 -0
  43. package/dist/chunk-65HWOMGV.js +177 -0
  44. package/dist/chunk-6ELFRN3P.js +365 -0
  45. package/dist/chunk-6HO3NCSJ.js +1055 -0
  46. package/dist/chunk-6IUMKYWI.js +457 -0
  47. package/dist/chunk-6KEB3C3U.js +65 -0
  48. package/dist/chunk-6MR5TI5V.js +987 -0
  49. package/dist/chunk-6NNTNXN7.js +157 -0
  50. package/dist/chunk-6PBR37LN.js +97 -0
  51. package/dist/chunk-6QXLMMT3.js +155 -0
  52. package/dist/chunk-6ZGJCBZS.js +224 -0
  53. package/dist/chunk-73IVBKMA.js +250 -0
  54. package/dist/chunk-7G3CTU7I.js +286 -0
  55. package/dist/chunk-A3UDOZB6.js +222 -0
  56. package/dist/chunk-A6E77GDW.js +85 -0
  57. package/dist/chunk-AHJWCYQO.js +660 -0
  58. package/dist/chunk-AKNCFVKZ.js +36 -0
  59. package/dist/chunk-AM4FKL4J.js +237 -0
  60. package/dist/chunk-AN6BL47T.js +26 -0
  61. package/dist/chunk-AOSYVMUS.js +147 -0
  62. package/dist/chunk-APWXY5SL.js +182 -0
  63. package/dist/chunk-AWEIIR2U.js +218 -0
  64. package/dist/chunk-AWQ7BQTQ.js +73 -0
  65. package/dist/chunk-BAUZOWGX.js +393 -0
  66. package/dist/chunk-BHACRS6R.js +940 -0
  67. package/dist/chunk-BWPNT55T.js +680 -0
  68. package/dist/chunk-C6RIMRU4.js +187 -0
  69. package/dist/chunk-CGVQZXYG.js +263 -0
  70. package/dist/chunk-CIMQZRRE.js +162 -0
  71. package/dist/chunk-CJHQKJU4.js +113 -0
  72. package/dist/chunk-CTMO46PK.js +1897 -0
  73. package/dist/chunk-D53J55C5.js +106 -0
  74. package/dist/chunk-D7VGG3WS.js +90 -0
  75. package/dist/chunk-DAD3ONIS.js +245 -0
  76. package/dist/chunk-DGWHAVRG.js +162 -0
  77. package/dist/chunk-DX4QR4B2.js +483 -0
  78. package/dist/chunk-EHP3WPEG.js +21 -0
  79. package/dist/chunk-ELP2HMXC.js +432 -0
  80. package/dist/chunk-ERVIASGL.js +21 -0
  81. package/dist/chunk-ET4Y4XCB.js +865 -0
  82. package/dist/chunk-ETO6LGLD.js +4750 -0
  83. package/dist/chunk-EUUUMY7J.js +24 -0
  84. package/dist/chunk-FQJ2TTHP.js +218 -0
  85. package/dist/chunk-FQOF7E2E.js +133 -0
  86. package/dist/chunk-FWD7AL6N.js +21 -0
  87. package/dist/chunk-FXFV3KU6.js +138 -0
  88. package/dist/chunk-FXRQOWTE.js +171 -0
  89. package/dist/chunk-G554NZNI.js +210 -0
  90. package/dist/chunk-GSGDWJB5.js +13 -0
  91. package/dist/chunk-H2V3N23D.js +66 -0
  92. package/dist/chunk-H5C7MBCR.js +159 -0
  93. package/dist/chunk-HHN734BU.js +371 -0
  94. package/dist/chunk-HRVBLCVJ.js +243 -0
  95. package/dist/chunk-HVRUQXGD.js +1264 -0
  96. package/dist/chunk-I4F5TCJH.js +130 -0
  97. package/dist/chunk-IDOTCMDV.js +57 -0
  98. package/dist/chunk-IGOLHKWF.js +427 -0
  99. package/dist/chunk-JGE7CAWB.js +148 -0
  100. package/dist/chunk-JTUKLCXS.js +92 -0
  101. package/dist/chunk-JZGVVJSI.js +66 -0
  102. package/dist/chunk-K4WX6UUU.js +923 -0
  103. package/dist/chunk-K5BBGTYZ.js +217 -0
  104. package/dist/chunk-K6RXBGUB.js +427 -0
  105. package/dist/chunk-KTSDSD36.js +165 -0
  106. package/dist/chunk-L5JYN3NM.js +1038 -0
  107. package/dist/chunk-LIEOKXKD.js +354 -0
  108. package/dist/chunk-LLNZGEFK.js +51 -0
  109. package/dist/chunk-LPECSJ2J.js +55 -0
  110. package/dist/chunk-LQCMKAJ7.js +413 -0
  111. package/dist/chunk-LRN4ZJCQ.js +1145 -0
  112. package/dist/chunk-MJ4ZVHKN.js +17 -0
  113. package/dist/chunk-MXHSQOC4.js +3061 -0
  114. package/dist/chunk-N5GKCWE5.js +367 -0
  115. package/dist/chunk-NA3PXMEF.js +10 -0
  116. package/dist/chunk-NAQDMEHV.js +76 -0
  117. package/dist/chunk-NWKXM5RD.js +109 -0
  118. package/dist/chunk-NZOWWHU5.js +196 -0
  119. package/dist/chunk-OHX6MIUZ.js +184 -0
  120. package/dist/chunk-OOXWYAA4.js +15 -0
  121. package/dist/chunk-OPHB5ORL.js +1990 -0
  122. package/dist/chunk-OXA6PSZT.js +1725 -0
  123. package/dist/chunk-P2HAGF6H.js +73 -0
  124. package/dist/chunk-PTHVMXZI.js +3036 -0
  125. package/dist/chunk-PZ5AY32C.js +10 -0
  126. package/dist/chunk-Q325JKYB.js +189 -0
  127. package/dist/chunk-Q7DGTQ5G.js +255 -0
  128. package/dist/chunk-QCNYHBL7.js +601 -0
  129. package/dist/chunk-QO6BVGS5.js +145 -0
  130. package/dist/chunk-QXTKSOFI.js +76 -0
  131. package/dist/chunk-ROY4YAEQ.js +66 -0
  132. package/dist/chunk-RTJRZFFY.js +447 -0
  133. package/dist/chunk-SAVJUS2X.js +128 -0
  134. package/dist/chunk-SFLHVCKX.js +173 -0
  135. package/dist/chunk-STSFNCY4.js +127 -0
  136. package/dist/chunk-SWBF2LTK.js +170 -0
  137. package/dist/chunk-T5KELPAR.js +109 -0
  138. package/dist/chunk-TFFGOFVZ.js +1481 -0
  139. package/dist/chunk-TLHIFK4M.js +256 -0
  140. package/dist/chunk-TLQE7ZN3.js +156 -0
  141. package/dist/chunk-TPHWJ7UG.js +1981 -0
  142. package/dist/chunk-TRJCALW3.js +291 -0
  143. package/dist/chunk-TTEIYD5P.js +133 -0
  144. package/dist/chunk-U64O2MXY.js +407 -0
  145. package/dist/chunk-UCQ2VUTG.js +630 -0
  146. package/dist/chunk-UTI5G6OJ.js +44 -0
  147. package/dist/chunk-UVHDMMIP.js +188 -0
  148. package/dist/chunk-UZ6QQWJT.js +120 -0
  149. package/dist/chunk-VAX3UZOY.js +686 -0
  150. package/dist/chunk-W3HBUKFO.js +57 -0
  151. package/dist/chunk-WKQLGKCL.js +2823 -0
  152. package/dist/chunk-WQFNASK4.js +560 -0
  153. package/dist/chunk-WRBKLYSJ.js +224 -0
  154. package/dist/chunk-WTPZRR4I.js +17 -0
  155. package/dist/chunk-WZDJ2LLX.js +2385 -0
  156. package/dist/chunk-X2NL4KUO.js +14 -0
  157. package/dist/chunk-X4DVXBFY.js +38 -0
  158. package/dist/chunk-X677DMZM.js +7050 -0
  159. package/dist/chunk-XBKMWY7F.js +612 -0
  160. package/dist/chunk-XNCP4KYO.js +78 -0
  161. package/dist/chunk-XVBGLISP.js +35 -0
  162. package/dist/chunk-XW6KISBB.js +1192 -0
  163. package/dist/chunk-YGZHHAAB.js +8156 -0
  164. package/dist/chunk-YHEFQUQB.js +186 -0
  165. package/dist/chunk-YLDALTWZ.js +653 -0
  166. package/dist/chunk-YU4HSQW7.js +229 -0
  167. package/dist/chunk-ZDPP6RKB.js +27 -0
  168. package/dist/chunk-ZFDZKNGG.js +194 -0
  169. package/dist/chunk-ZIY7RNUT.js +49 -0
  170. package/dist/chunk-ZYJP6BOO.js +30 -0
  171. package/dist/cli.d.ts +1111 -0
  172. package/dist/cli.js +165 -0
  173. package/dist/codegen-command-VR2GWR6C.js +173 -0
  174. package/dist/codegen.d.ts +115 -0
  175. package/dist/codegen.js +19 -0
  176. package/dist/commands-GJMJ4XYZ.js +76 -0
  177. package/dist/completion-ZQLO6PB6.js +738 -0
  178. package/dist/config-MBYGCAFK.js +136 -0
  179. package/dist/deploy-command-JVZPWHMK.js +180 -0
  180. package/dist/deploy-source-3W45CXMP.js +81 -0
  181. package/dist/dev-env-sync-HN44K4D5.js +188 -0
  182. package/dist/diagnostics-UEAAHWVF.js +30 -0
  183. package/dist/display-name-hint-ABBPVQIR.js +46 -0
  184. package/dist/emit-CyhBDxli.d.ts +7007 -0
  185. package/dist/env-example-refresh-ET5HMMDW.js +126 -0
  186. package/dist/env-pull-command-TNMNB3CV.js +323 -0
  187. package/dist/env-target-O4HRKTGZ.js +40 -0
  188. package/dist/env-var-command-ZIRDCAZA.js +427 -0
  189. package/dist/ephemeral-P7F3PMAD.js +54 -0
  190. package/dist/ephemeral-command-CLJJKOFT.js +592 -0
  191. package/dist/ephemeral-state-PYCBSOGX.js +61 -0
  192. package/dist/file-lock-PT74GWYW.js +10 -0
  193. package/dist/generate-command-AYEOLA26.js +510 -0
  194. package/dist/gitignore-3SJ3WNRG.js +51 -0
  195. package/dist/guards-4EBNCZDC.js +162 -0
  196. package/dist/hosted-file-PSHJB3HX.js +28 -0
  197. package/dist/impersonate-command-5JAS3WSV.js +152 -0
  198. package/dist/import-EQSFU6JC.js +19 -0
  199. package/dist/index.d.ts +5470 -0
  200. package/dist/index.js +371 -0
  201. package/dist/init-command-X2FR4UAK.js +125 -0
  202. package/dist/init-modules-RY7UHAOL.js +50 -0
  203. package/dist/init-web-M4YZSZK6.js +42 -0
  204. package/dist/internal.d.ts +179 -0
  205. package/dist/internal.js +2634 -0
  206. package/dist/io-RC56QSN5.js +26 -0
  207. package/dist/keep-data-merge-7MPLVAVI.js +1119 -0
  208. package/dist/lambda-config-reconcile-OUZ4OYAB.js +116 -0
  209. package/dist/landed-753YLJ5H.js +40 -0
  210. package/dist/landing-record-SU52SKXU.js +61 -0
  211. package/dist/live-diff-YONW2VI3.js +65 -0
  212. package/dist/local-engine-choice-CH5X3W7E.js +79 -0
  213. package/dist/local-engine-command-Z3OL7VOD.js +766 -0
  214. package/dist/local-engine-config-5U3SC434.js +39 -0
  215. package/dist/local-engine-deploy-PCQ4SNPB.js +39 -0
  216. package/dist/local-engine-process-EHTE5LLU.js +63 -0
  217. package/dist/local-engine-state-A4FTRFAF.js +32 -0
  218. package/dist/local-engine-update-check-6L645VBL.js +162 -0
  219. package/dist/lock-MRRHW5GS.js +87 -0
  220. package/dist/lock-commands-2QG25HL7.js +963 -0
  221. package/dist/login-command-QPYRWOXZ.js +538 -0
  222. package/dist/logout-command-WOIQLDP6.js +56 -0
  223. package/dist/loop-ALIZLOSX.js +191 -0
  224. package/dist/marketplace-command-K5YL42NO.js +977 -0
  225. package/dist/marketplace-resolve-LY32JES5.js +40 -0
  226. package/dist/meta-client-YXGPW53C.js +244 -0
  227. package/dist/module-manifest-JFGHU2TC.js +38 -0
  228. package/dist/near-key.d.ts +13 -0
  229. package/dist/near-key.js +8 -0
  230. package/dist/node.d.ts +95 -0
  231. package/dist/node.js +969 -0
  232. package/dist/operation-outcome-TZSESNC7.js +51 -0
  233. package/dist/output-target-AL3I44GG.js +47 -0
  234. package/dist/package-manager-PRAHUSCN.js +34 -0
  235. package/dist/plan-disclosure-6G4MLZKD.js +114 -0
  236. package/dist/plugin-contributions-WRNY5ABG.js +28 -0
  237. package/dist/plugin-prompt-4VA3B4FF.js +20 -0
  238. package/dist/plugin-questions-JTSYA74Q.js +24 -0
  239. package/dist/plugin.d.ts +396 -0
  240. package/dist/plugin.js +1 -0
  241. package/dist/preflight-command-2T5Z36MI.js +321 -0
  242. package/dist/profile-command-AH3PIM4X.js +800 -0
  243. package/dist/profile-pointer-GRWJU3YD.js +31 -0
  244. package/dist/profile-select-74TRDEPD.js +23 -0
  245. package/dist/project-dependency-KSH63ODT.js +35 -0
  246. package/dist/project-sdk-REPPOIBD.js +24 -0
  247. package/dist/promote-command-PCWT5N6S.js +140 -0
  248. package/dist/prompt-II3F3LND.js +27 -0
  249. package/dist/publish-command-TTDUUJI5.js +414 -0
  250. package/dist/pull-command-VBO7C4S3.js +161 -0
  251. package/dist/pull-superseded-GJWLG7XO.js +20 -0
  252. package/dist/release-command-N4ZPKMHR.js +146 -0
  253. package/dist/release-ns-command-3KFDMZ6G.js +1745 -0
  254. package/dist/response-HlXahv4K.d.ts +925 -0
  255. package/dist/retry-command-AS5EVREX.js +34 -0
  256. package/dist/routes-manifest-SNWUVBT6.js +43 -0
  257. package/dist/runtime-XETHVF33.js +14 -0
  258. package/dist/scaffold.d.ts +1911 -0
  259. package/dist/scaffold.js +107 -0
  260. package/dist/secrets-file-LVZD6IIM.js +56 -0
  261. package/dist/secrets-fill-command-TTCPSVXN.js +218 -0
  262. package/dist/seed-VU7C3QYS.js +46 -0
  263. package/dist/sent-writes-N7X4OVXT.js +22 -0
  264. package/dist/source-resolve-S2XA3XZF.js +83 -0
  265. package/dist/static-host-B2Y6AG3W.js +352 -0
  266. package/dist/status-command-RUDXTF7I.js +554 -0
  267. package/dist/store-6VNCD4D7.js +20 -0
  268. package/dist/store-ARM6VMO5.js +98 -0
  269. package/dist/store-DuYfzKlW.d.ts +3144 -0
  270. package/dist/suggest-6FOOYKHG.js +12 -0
  271. package/dist/sveltekit.d.ts +154 -0
  272. package/dist/sveltekit.js +109 -0
  273. package/dist/sync-contributions-UCEROO26.js +415 -0
  274. package/dist/table-ENAVMITR.js +42 -0
  275. package/dist/tables-command-GUPXXSB5.js +156 -0
  276. package/dist/tenant-IIHPOHGP.js +42 -0
  277. package/dist/tenant-command-D5HGI2X4.js +965 -0
  278. package/dist/test-command-TIKZMDPZ.js +476 -0
  279. package/dist/tests-BGGIOK3L.js +26 -0
  280. package/dist/token-NICJUTMT.js +72 -0
  281. package/dist/toolchain-hooks-GHKLUJOH.js +96 -0
  282. package/dist/toolchain-modules-5EAZUONB.js +24 -0
  283. package/dist/tracked-backend-EIBUMDZA.js +80 -0
  284. package/dist/update-check-2W2NLDTC.js +136 -0
  285. package/dist/upgrade-command-QHNFWM7O.js +531 -0
  286. package/dist/verify-rollout-DRCK5TDD.js +61 -0
  287. package/dist/whoami-command-STGINTXH.js +51 -0
  288. package/dist/workspace-2CLHU2LK.js +93 -0
  289. package/dist/workspace-binding-56PIIPCP.js +54 -0
  290. package/dist/workspace-command-JEXQETJD.js +535 -0
  291. package/dist/workspace-env-F2RFMSC5.js +70 -0
  292. package/dist/workspace-export-OJSH2HIW.js +21 -0
  293. package/dist/writable-VA7OISSK.js +25 -0
  294. package/dist/xano-ICFUSPFR.js +42 -0
  295. package/dist/xanosdk-import-GN42TO2B.js +56 -0
  296. package/dist/xanosdk-project-S6DXJONQ.js +41 -0
  297. package/dist/xdo-CxDr2Q1C.d.ts +1371 -0
  298. package/guides/README.md +33 -0
  299. package/guides/authoring.md +921 -0
  300. package/guides/cli.md +401 -0
  301. package/guides/codegen.md +177 -0
  302. package/guides/codes.md +404 -0
  303. package/guides/coverage.md +74 -0
  304. package/guides/deploying.md +1525 -0
  305. package/guides/environment.md +400 -0
  306. package/guides/marketplace.md +415 -0
  307. package/guides/module-contract.md +558 -0
  308. package/guides/object-kinds.md +433 -0
  309. package/guides/project-structure.md +43 -0
  310. package/guides/scaffold.md +351 -0
  311. package/guides/typed-frontend.md +242 -0
  312. package/llms/client.md +63 -0
  313. package/llms/errors.md +56 -0
  314. package/llms/fields.md +176 -0
  315. package/llms/filters.md +183 -0
  316. package/llms/kinds-agent-mcp.md +30 -0
  317. package/llms/kinds-core.md +100 -0
  318. package/llms/kinds-knowledge.md +20 -0
  319. package/llms/kinds-realtime.md +76 -0
  320. package/llms/lambda.md +94 -0
  321. package/llms/legacy.md +44 -0
  322. package/llms/lock.md +40 -0
  323. package/llms/object-kinds.md +34 -0
  324. package/llms/statements-calls.md +62 -0
  325. package/llms/statements-catalog.md +320 -0
  326. package/llms/statements-data.md +66 -0
  327. package/llms/statements-runtime.md +68 -0
  328. package/llms/tests.md +59 -0
  329. package/llms/toolchain.md +16 -0
  330. package/llms/triggers.md +36 -0
  331. package/llms/values.md +29 -0
  332. package/llms-full.txt +2085 -0
  333. package/llms.txt +493 -0
  334. package/manifest.json +11224 -0
  335. package/package.json +162 -0
  336. 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.