@xanots/sdk 0.0.11 → 0.0.12

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 (97) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +11 -6
  3. package/dist/.build-fingerprint +1 -1
  4. package/dist/{agent-file-refresh-QNKN5RYD.js → agent-file-refresh-GQWAAOBV.js} +4 -4
  5. package/dist/bin.js +11 -11
  6. package/dist/{branch-commands-2BLOC2GR.js → branch-commands-5KMPAAZV.js} +7 -7
  7. package/dist/bundle.d.ts +2 -2
  8. package/dist/bundle.js +2 -2
  9. package/dist/{capture-4WVJY4DQ.js → capture-YLUVAITI.js} +2 -2
  10. package/dist/chunk-2VTJSI6X.js +192 -0
  11. package/dist/{chunk-5XZ744TS.js → chunk-2ZCUO2UG.js} +1 -1
  12. package/dist/{chunk-XEOX6AM7.js → chunk-3LQGF2WS.js} +2 -2
  13. package/dist/{chunk-WP4OZZV4.js → chunk-3VFCKHOB.js} +2 -2
  14. package/dist/{chunk-XHEXOES3.js → chunk-5YDINUAP.js} +1 -1
  15. package/dist/{chunk-LU7TRWMC.js → chunk-6VNRMKFJ.js} +2 -2
  16. package/dist/{chunk-CTD5ZCV6.js → chunk-7WRJPKGK.js} +2 -2
  17. package/dist/{chunk-3INK4Y4E.js → chunk-AE3PDSDS.js} +1 -1
  18. package/dist/{chunk-22TKBSDV.js → chunk-AG5GCZDD.js} +2 -2
  19. package/dist/{chunk-DBFU47BJ.js → chunk-AOFFKSJC.js} +2 -2
  20. package/dist/{chunk-TCFIPDB3.js → chunk-AVGDL6RB.js} +1 -1
  21. package/dist/{chunk-BC2C5GVI.js → chunk-DCMANKMX.js} +1 -1
  22. package/dist/{chunk-WHOJWOSV.js → chunk-EETVJZAZ.js} +1 -1
  23. package/dist/{chunk-4Q7ZOHH7.js → chunk-ELK7UALJ.js} +3 -3
  24. package/dist/{chunk-VNQM3V2C.js → chunk-EXENFOWE.js} +2 -2
  25. package/dist/{chunk-UOZMSF4C.js → chunk-GSP2BY4F.js} +5 -5
  26. package/dist/{chunk-AIZKXUNP.js → chunk-IMLYGQK6.js} +2 -2
  27. package/dist/{chunk-QK7ZQJLP.js → chunk-N74KDCBD.js} +39 -25
  28. package/dist/{chunk-3IGNIP6R.js → chunk-NDP7OUPS.js} +1 -1
  29. package/dist/{chunk-EQW3YT5U.js → chunk-P6PVBQL6.js} +2 -2
  30. package/dist/{chunk-HYBN4H3F.js → chunk-PLE5QQOZ.js} +27 -20
  31. package/dist/{chunk-BSK7ELHU.js → chunk-PR7OXHGZ.js} +1 -1
  32. package/dist/{chunk-OWGCOGKK.js → chunk-QYSQ3UDO.js} +8 -8
  33. package/dist/{chunk-ZQ2PKR6R.js → chunk-RQ3FXV4K.js} +2 -2
  34. package/dist/{chunk-Q77KNEUL.js → chunk-RQNMTDXD.js} +1703 -2
  35. package/dist/{chunk-QKM4U5UK.js → chunk-TJS2AF5Y.js} +2 -2
  36. package/dist/{chunk-DIA7CT7J.js → chunk-V5Y7D4LH.js} +11 -2
  37. package/dist/{chunk-KA6G2L7U.js → chunk-VK26K7AY.js} +3 -3
  38. package/dist/{chunk-4IF54NU5.js → chunk-VPAWRBK5.js} +12 -16
  39. package/dist/{chunk-QYMAZRAU.js → chunk-VRNZ2NVV.js} +5 -5
  40. package/dist/{chunk-RCT7UX7B.js → chunk-XT3XQ4PF.js} +32 -27
  41. package/dist/{chunk-VAF6A3YD.js → chunk-XWFRNJMQ.js} +1 -1
  42. package/dist/{chunk-7ZYW652H.js → chunk-YHS6VVLJ.js} +2 -2
  43. package/dist/{chunk-G4EJMQLD.js → chunk-YX22LKQE.js} +2 -2
  44. package/dist/{chunk-F6CYJ7TN.js → chunk-Z2ZIE5CO.js} +2 -2
  45. package/dist/cli.d.ts +13 -6
  46. package/dist/cli.js +10 -10
  47. package/dist/codegen-command-Y2SPMAUW.js +47 -0
  48. package/dist/codegen.d.ts +5 -5
  49. package/dist/codegen.js +2 -2
  50. package/dist/{completion-WF46272M.js → completion-HJU5QEFB.js} +2 -2
  51. package/dist/{deploy-command-IP7V7GT4.js → deploy-command-XHS5PKPV.js} +19 -19
  52. package/dist/{ephemeral-command-U4AQ3TXX.js → ephemeral-command-2NKPEXUU.js} +8 -8
  53. package/dist/index.d.ts +88 -104
  54. package/dist/index.js +9 -9
  55. package/dist/init-command-23FFNUFT.js +32 -0
  56. package/dist/internal.d.ts +10 -16
  57. package/dist/internal.js +26 -26
  58. package/dist/{io-P2H75UV2.js → io-UBDMMDH6.js} +3 -3
  59. package/dist/{live-diff-IXKBVG4K.js → live-diff-HCOTN5WC.js} +3 -3
  60. package/dist/{lock-46FWYE4D.js → lock-HQ4KARU2.js} +2 -2
  61. package/dist/{lock-commands-ZZKZ4LZJ.js → lock-commands-WCRC56ME.js} +11 -11
  62. package/dist/{login-command-Z6CHTA57.js → login-command-P7LXD5TE.js} +6 -6
  63. package/dist/{loop-D5NPL4VH.js → loop-IC5ISSYB.js} +3 -3
  64. package/dist/{marketplace-command-P4IPLJ6J.js → marketplace-command-T6JCHW7J.js} +4 -4
  65. package/dist/{meta-client-K2J4XH64.js → meta-client-LKRKR3L2.js} +4 -4
  66. package/dist/node.d.ts +6 -6
  67. package/dist/node.js +14 -14
  68. package/dist/{preflight-command-K346GPTY.js → preflight-command-TZWPHBXY.js} +16 -16
  69. package/dist/{profile-command-LJSBDV2L.js → profile-command-WBNSMQSI.js} +3 -3
  70. package/dist/{release-command-IMNTIVWK.js → release-command-WEFYRTCI.js} +26 -26
  71. package/dist/{response-BQVQ24l1.d.ts → response-D6xGLEIn.d.ts} +18 -23
  72. package/dist/{routes-manifest-PWZHDOI5.js → routes-manifest-5ZFKUQWA.js} +2 -2
  73. package/dist/{runtime-V4C3AC3A.js → runtime-LSLIDALK.js} +1 -1
  74. package/dist/scaffold.d.ts +16 -8
  75. package/dist/scaffold.js +7 -11
  76. package/dist/{static-host-3WMV7IZO.js → static-host-4JDQCHUW.js} +1 -1
  77. package/dist/{status-command-AL47VG7H.js → status-command-VARULAR5.js} +4 -4
  78. package/dist/{store-BLyNeQ8S.d.ts → store-DAnUIi1T.d.ts} +81 -86
  79. package/dist/{test-command-YAZLKLGQ.js → test-command-YCAR4O35.js} +7 -7
  80. package/dist/{upgrade-command-BN3EHAOI.js → upgrade-command-GYEIMJBG.js} +15 -16
  81. package/dist/{workspace-2COHDBM3.js → workspace-33KHFLX3.js} +2 -2
  82. package/dist/{workspace-command-MNK7Y7MQ.js → workspace-command-2ZTGT26W.js} +26 -28
  83. package/dist/{workspace-export-DURY5WYL.js → workspace-export-MLYTYZS5.js} +3 -3
  84. package/dist/{xdo-BjJj5W_E.d.ts → xdo-ODuJklk6.d.ts} +27 -23
  85. package/guides/authoring.md +11 -2
  86. package/guides/typed-frontend.md +4 -4
  87. package/llms/statements-data.md +3 -2
  88. package/llms/values.md +1 -1
  89. package/llms-full.txt +18 -18
  90. package/llms.txt +14 -15
  91. package/manifest.json +6 -2
  92. package/package.json +1 -1
  93. package/dist/chunk-ANUDXFEX.js +0 -881
  94. package/dist/chunk-JGCWTCA7.js +0 -95
  95. package/dist/chunk-YBC3IKMF.js +0 -845
  96. package/dist/codegen-command-FUT2KJB6.js +0 -49
  97. package/dist/init-command-NPVL32L6.js +0 -34
@@ -4,25 +4,25 @@ import {
4
4
  } from "./chunk-NUQCEOKA.js";
5
5
  import {
6
6
  resolveMetaTarget
7
- } from "./chunk-ZQ2PKR6R.js";
7
+ } from "./chunk-RQ3FXV4K.js";
8
8
  import "./chunk-K5IOND4K.js";
9
9
  import {
10
10
  getAccessToken
11
- } from "./chunk-QKM4U5UK.js";
11
+ } from "./chunk-TJS2AF5Y.js";
12
12
  import "./chunk-FE5I6S6N.js";
13
13
  import {
14
14
  fetchOrExplain,
15
15
  httpFailure,
16
16
  serverMessage
17
- } from "./chunk-3IGNIP6R.js";
17
+ } from "./chunk-NDP7OUPS.js";
18
18
  import {
19
19
  parseEnvSelector,
20
20
  unknownEnv
21
- } from "./chunk-CTD5ZCV6.js";
21
+ } from "./chunk-7WRJPKGK.js";
22
22
  import {
23
23
  UsageError,
24
24
  unknownSubcommand
25
- } from "./chunk-F6CYJ7TN.js";
25
+ } from "./chunk-Z2ZIE5CO.js";
26
26
  import "./chunk-GNPVYOPB.js";
27
27
  import {
28
28
  blank,
@@ -32,7 +32,7 @@ import {
32
32
  success,
33
33
  warn
34
34
  } from "./chunk-EZG76F7R.js";
35
- import "./chunk-DIA7CT7J.js";
35
+ import "./chunk-V5Y7D4LH.js";
36
36
 
37
37
  // src/deploy/tests.ts
38
38
  var TIMEOUT_MS = 12e4;
@@ -397,4 +397,4 @@ export {
397
397
  runSuite,
398
398
  runTestCommand
399
399
  };
400
- //# sourceMappingURL=test-command-YAZLKLGQ.js.map
400
+ //# sourceMappingURL=test-command-YCAR4O35.js.map
@@ -3,30 +3,29 @@ import {
3
3
  } from "./chunk-ERQZFWIW.js";
4
4
  import {
5
5
  refreshAgentFiles
6
- } from "./chunk-JGCWTCA7.js";
6
+ } from "./chunk-2VTJSI6X.js";
7
7
  import {
8
8
  isMachineOutput,
9
9
  writeJson
10
10
  } from "./chunk-NUQCEOKA.js";
11
- import "./chunk-KA6G2L7U.js";
11
+ import "./chunk-VK26K7AY.js";
12
12
  import {
13
13
  runNpm
14
14
  } from "./chunk-ZSYZTGJH.js";
15
15
  import {
16
16
  sdkDep
17
- } from "./chunk-ANUDXFEX.js";
18
- import "./chunk-Q77KNEUL.js";
17
+ } from "./chunk-RQNMTDXD.js";
19
18
  import {
20
19
  checkForUpgrade,
21
20
  detectInstallMode,
22
21
  registryUrl,
23
22
  suppressUpdateNotice,
24
23
  upgradeCommand
25
- } from "./chunk-22TKBSDV.js";
26
- import "./chunk-RCT7UX7B.js";
27
- import "./chunk-CTD5ZCV6.js";
28
- import "./chunk-XEOX6AM7.js";
29
- import "./chunk-F6CYJ7TN.js";
24
+ } from "./chunk-AG5GCZDD.js";
25
+ import "./chunk-XT3XQ4PF.js";
26
+ import "./chunk-7WRJPKGK.js";
27
+ import "./chunk-3LQGF2WS.js";
28
+ import "./chunk-Z2ZIE5CO.js";
30
29
  import "./chunk-GNPVYOPB.js";
31
30
  import {
32
31
  blank,
@@ -36,12 +35,12 @@ import {
36
35
  success,
37
36
  warn
38
37
  } from "./chunk-EZG76F7R.js";
39
- import "./chunk-OWGCOGKK.js";
40
- import "./chunk-DIA7CT7J.js";
41
- import "./chunk-QK7ZQJLP.js";
42
- import "./chunk-WHOJWOSV.js";
43
- import "./chunk-VAF6A3YD.js";
44
- import "./chunk-5XZ744TS.js";
38
+ import "./chunk-QYSQ3UDO.js";
39
+ import "./chunk-V5Y7D4LH.js";
40
+ import "./chunk-N74KDCBD.js";
41
+ import "./chunk-EETVJZAZ.js";
42
+ import "./chunk-XWFRNJMQ.js";
43
+ import "./chunk-2ZCUO2UG.js";
45
44
  import "./chunk-OHX6MIUZ.js";
46
45
 
47
46
  // src/emit/upgrade-command.ts
@@ -176,4 +175,4 @@ function messageOf(err) {
176
175
  export {
177
176
  runUpgradeCommand
178
177
  };
179
- //# sourceMappingURL=upgrade-command-BN3EHAOI.js.map
178
+ //# sourceMappingURL=upgrade-command-GYEIMJBG.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  fetchReadOrExplain
3
- } from "./chunk-3IGNIP6R.js";
3
+ } from "./chunk-NDP7OUPS.js";
4
4
 
5
5
  // src/deploy/workspace.ts
6
6
  var RESOLVE_TIMEOUT_MS = 3e4;
@@ -46,4 +46,4 @@ ${text}`);
46
46
  export {
47
47
  resolveScopedWorkspaceId
48
48
  };
49
- //# sourceMappingURL=workspace-2COHDBM3.js.map
49
+ //# sourceMappingURL=workspace-33KHFLX3.js.map
@@ -11,41 +11,39 @@ import {
11
11
  } from "./chunk-NUQCEOKA.js";
12
12
  import {
13
13
  fetchWorkspaceBundle
14
- } from "./chunk-HYBN4H3F.js";
15
- import "./chunk-ZQ2PKR6R.js";
14
+ } from "./chunk-PLE5QQOZ.js";
15
+ import "./chunk-RQ3FXV4K.js";
16
16
  import "./chunk-K5IOND4K.js";
17
17
  import {
18
18
  ENV_WORKSPACE,
19
19
  getAccessToken
20
- } from "./chunk-QKM4U5UK.js";
20
+ } from "./chunk-TJS2AF5Y.js";
21
21
  import "./chunk-FE5I6S6N.js";
22
- import "./chunk-7ZYW652H.js";
22
+ import "./chunk-YHS6VVLJ.js";
23
23
  import "./chunk-ZUTSMMAG.js";
24
24
  import "./chunk-X4DVXBFY.js";
25
25
  import {
26
26
  fetchOrExplain,
27
27
  httpFailure
28
- } from "./chunk-3IGNIP6R.js";
29
- import "./chunk-4IF54NU5.js";
30
- import "./chunk-KA6G2L7U.js";
28
+ } from "./chunk-NDP7OUPS.js";
29
+ import "./chunk-VPAWRBK5.js";
30
+ import "./chunk-VK26K7AY.js";
31
31
  import "./chunk-ZSYZTGJH.js";
32
- import "./chunk-G4EJMQLD.js";
33
- import "./chunk-LU7TRWMC.js";
34
- import "./chunk-YBC3IKMF.js";
35
- import "./chunk-ANUDXFEX.js";
36
- import "./chunk-Q77KNEUL.js";
37
- import "./chunk-BSK7ELHU.js";
38
- import "./chunk-UOZMSF4C.js";
32
+ import "./chunk-YX22LKQE.js";
33
+ import "./chunk-6VNRMKFJ.js";
34
+ import "./chunk-RQNMTDXD.js";
35
+ import "./chunk-PR7OXHGZ.js";
36
+ import "./chunk-GSP2BY4F.js";
39
37
  import "./chunk-EHP3WPEG.js";
40
- import "./chunk-3INK4Y4E.js";
41
- import "./chunk-BC2C5GVI.js";
42
- import "./chunk-RCT7UX7B.js";
43
- import "./chunk-CTD5ZCV6.js";
44
- import "./chunk-XEOX6AM7.js";
38
+ import "./chunk-AE3PDSDS.js";
39
+ import "./chunk-DCMANKMX.js";
40
+ import "./chunk-XT3XQ4PF.js";
41
+ import "./chunk-7WRJPKGK.js";
42
+ import "./chunk-3LQGF2WS.js";
45
43
  import {
46
44
  removedSubcommand,
47
45
  unknownSubcommand
48
- } from "./chunk-F6CYJ7TN.js";
46
+ } from "./chunk-Z2ZIE5CO.js";
49
47
  import "./chunk-GNPVYOPB.js";
50
48
  import {
51
49
  formatFields,
@@ -53,12 +51,12 @@ import {
53
51
  step,
54
52
  success
55
53
  } from "./chunk-EZG76F7R.js";
56
- import "./chunk-OWGCOGKK.js";
57
- import "./chunk-DIA7CT7J.js";
58
- import "./chunk-QK7ZQJLP.js";
59
- import "./chunk-WHOJWOSV.js";
60
- import "./chunk-VAF6A3YD.js";
61
- import "./chunk-5XZ744TS.js";
54
+ import "./chunk-QYSQ3UDO.js";
55
+ import "./chunk-V5Y7D4LH.js";
56
+ import "./chunk-N74KDCBD.js";
57
+ import "./chunk-EETVJZAZ.js";
58
+ import "./chunk-XWFRNJMQ.js";
59
+ import "./chunk-2ZCUO2UG.js";
62
60
  import "./chunk-OHX6MIUZ.js";
63
61
 
64
62
  // src/emit/workspace-command.ts
@@ -72,7 +70,7 @@ async function runWorkspaceCommand(args) {
72
70
  case "export":
73
71
  return runExport(args);
74
72
  case "branch": {
75
- const { runBranchCommand } = await import("./branch-commands-2BLOC2GR.js");
73
+ const { runBranchCommand } = await import("./branch-commands-5KMPAAZV.js");
76
74
  return runBranchCommand(args);
77
75
  }
78
76
  case "deploy":
@@ -162,4 +160,4 @@ async function runExport(args) {
162
160
  export {
163
161
  runWorkspaceCommand
164
162
  };
165
- //# sourceMappingURL=workspace-command-MNK7Y7MQ.js.map
163
+ //# sourceMappingURL=workspace-command-2ZTGT26W.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  exportWorkspaceBundle
3
- } from "./chunk-7ZYW652H.js";
3
+ } from "./chunk-YHS6VVLJ.js";
4
4
  import "./chunk-ZUTSMMAG.js";
5
5
  import "./chunk-X4DVXBFY.js";
6
- import "./chunk-3IGNIP6R.js";
6
+ import "./chunk-NDP7OUPS.js";
7
7
  export {
8
8
  exportWorkspaceBundle
9
9
  };
10
- //# sourceMappingURL=workspace-export-DURY5WYL.js.map
10
+ //# sourceMappingURL=workspace-export-MLYTYZS5.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The shared tagged-value primitive (KTD-2). Every place a function references
2
+ * The shared tagged-value primitive. Every place a function references
3
3
  * data — input bindings, statement context, response — uses this `{value, tag,
4
4
  * filters}` shape. Built and tested once here, reused everywhere.
5
5
  */
@@ -10,7 +10,7 @@ type Value = TaggedValue;
10
10
  * A {@link Value} that also carries, **at the type level only**, the name of the
11
11
  * stack variable it references (`ref("user")` → `RefValue<"user">`). The `__ref`
12
12
  * carrier is phantom — never present at runtime — and required (not optional) so
13
- * `InferResponse`'s trace (U5) matches only real refs, never a plain `Value`.
13
+ * `InferResponse`'s trace matches only real refs, never a plain `Value`.
14
14
  * Because it is a subtype of `Value`, every existing `ref(...)` use — filter
15
15
  * args, `db.query` `where`, response fields — keeps type-checking unchanged.
16
16
  */
@@ -41,7 +41,7 @@ type FilteredValue<Base = unknown, Chain extends readonly unknown[] = readonly u
41
41
  * an editor's hover distinguishes a pattern from the text beside it.
42
42
  *
43
43
  * It is deliberately NOT used to restrict `s.expect.to_match` or the `fl.regex_*`
44
- * pattern slot, which is what issue #213 proposed. A nominal restriction there
44
+ * pattern slot. A nominal restriction there
45
45
  * would have to reject `Value`, and `inp()`, `env()`, `auth()` and `sys.*` all
46
46
  * return exactly `Value` — the same type `c.text` does — so it would reject every
47
47
  * DYNAMIC pattern along with the broken constant ones. The enforcement is instead
@@ -63,15 +63,15 @@ type FlattenFilters<Fs extends readonly unknown[]> = Fs extends readonly [
63
63
  * *statically rejected* where it would silently fail at runtime: inside a
64
64
  * `db.edit`/`db.add` `row`, `{tag:"col"}` does not resolve to the row's stored
65
65
  * value — it evaluates to `null`, so a following `fl.add(1)` computes `null + 1`
66
- * and the engine aborts ("Numbers are required for mathematical operations",
67
- * issue #32). `col()` is only meaningful in a `db.query` `where`/view expression.
66
+ * and the engine aborts ("Numbers are required for mathematical operations").
67
+ * `col()` is only meaningful in a `db.query` `where`/view expression.
68
68
  */
69
69
  type ColValue = Value & {
70
70
  readonly __col: true;
71
71
  };
72
72
  /**
73
73
  * The error branch surfaced when a tagged {@link Value} is nested inside a
74
- * `c.obj`/`c.array` literal (issue #42). The long message is the *property key*
74
+ * `c.obj`/`c.array` literal. The long message is the *property key*
75
75
  * so TypeScript prints it verbatim in the "property … is missing" diagnostic; a
76
76
  * `Value` has no such key, so intersecting it here makes the offending position
77
77
  * fail to type-check. The runtime guard ({@link assertPlainJson}) carries the
@@ -107,7 +107,7 @@ type BlankTag = Exclude<Extract<Tag, `const${string}`>, "const" | "const:obj">;
107
107
  /**
108
108
  * Tags {@link c.null} can spell — the two the engine stores a `"null"` value
109
109
  * under. `const:null` is the plain one; `const:obj` is the object-typed null a
110
- * `db.*` statement's `@meta` slot carries (issue #151).
110
+ * `db.*` statement's `@meta` slot carries.
111
111
  *
112
112
  * Listed rather than derived: a `"null"` value under any OTHER constant tag is
113
113
  * unobserved, and inventing spellings for it would offer an authoring form for
@@ -141,7 +141,7 @@ declare const c: {
141
141
  * A `number` that is not an integer, or is past `Number.MAX_SAFE_INTEGER`,
142
142
  * **throws**: by the time such a literal reaches here its precision is already
143
143
  * gone, so encoding it would write a value the caller did not type. The throw
144
- * names the string form. (issue #153)
144
+ * names the string form.
145
145
  */
146
146
  int(n: number | bigint | string): Value;
147
147
  /**
@@ -181,7 +181,7 @@ declare const c: {
181
181
  * null** the engine writes into a `db.*` statement's `@meta` slot —
182
182
  * `c.null("const:obj")` → `{tag:"const:obj", value:"null"}`, 16 of them across
183
183
  * eight ordinary queries in the survey corpus, which had no spelling and came
184
- * back as `rawValue` (issue #151).
184
+ * back as `rawValue`.
185
185
  *
186
186
  * ⚠ Not the same bytes as `c.obj(null)`, which is the BLANK object
187
187
  * (`value: ""`). Both evaluate to null — the engine JSON-decodes the stored
@@ -195,7 +195,7 @@ declare const c: {
195
195
  * (`fl.regex_test`/`regex_match`/`regex_replace`/…). Xano runs PHP `preg_*`, so
196
196
  * the pattern MUST be delimiter-wrapped — a bare `c.text("^…$")` is an invalid
197
197
  * PCRE and the filter then matches *nothing* for every input, so a precondition
198
- * built on it silently rejects all values (issue #128). This wraps the raw
198
+ * built on it silently rejects all values. This wraps the raw
199
199
  * pattern in `/…/`, escapes any interior `/`, and appends `flags`, so the result
200
200
  * is always valid; `withFilters` rejects a bare `c.text` pattern and points here.
201
201
  *
@@ -216,8 +216,13 @@ declare const c: {
216
216
  * `text("now") |to_epoch_ms` chain; both evaluate to the same epoch-ms number
217
217
  * on a live engine, and both persist verbatim, but the native tag is what the
218
218
  * editor writes and needs no filter to get there. Chain math onto it as usual:
219
- * `withFilters(c.now(), fl.epochms_add_ms(c.int(-maxAgeMs)))`. (issues #120,
220
- * #145)
219
+ * `withFilters(c.now(), fl.epochms_add_ms(c.int(-maxAgeMs)))`.
220
+ *
221
+ * Inside an `obj()` the tag is flattened into an expression string, where the
222
+ * identifier `now` alone is the STRING "now" — so `obj()` writes the
223
+ * `now|to_epochms` coercion that restores this tag's meaning. A `c.now()` used
224
+ * as a FILTER ARGUMENT there is refused: the coercion cannot be written in
225
+ * that position. Hoist it with `s.set_var` and `ref()` it.
221
226
  */
222
227
  now(): Value;
223
228
  /**
@@ -226,7 +231,6 @@ declare const c: {
226
231
  * compile error and throws at runtime — it would serialize as internal
227
232
  * representation the engine can't decode. For a computed/multi-key object
228
233
  * response use a record of values (`response: { key: value }`), not `c.obj`.
229
- * See issue #42.
230
234
  *
231
235
  * Called with no argument it is the **empty object**, `{}` — the same default
232
236
  * the editor gives a new object variable, and the only empty form current
@@ -243,7 +247,7 @@ declare const c: {
243
247
  * same bytes instead of being quietly re-pointed at `{}`.
244
248
  *
245
249
  * A **populated** object is stored the way the editor stores one: an empty
246
- * `{}` base carrying one `set` filter per key (issue #248). It is NOT a
250
+ * `{}` base carrying one `set` filter per key. It is NOT a
247
251
  * populated JSON string — that form fails the request at runtime, see
248
252
  * {@link objSetFilters}.
249
253
  *
@@ -257,7 +261,7 @@ declare const c: {
257
261
  /**
258
262
  * Array constant → JSON-string value with `tag:"const:array"`. Takes **plain
259
263
  * JSON literals only** — like {@link obj}, a nested tagged value is a compile
260
- * error and throws at runtime. See issue #42.
264
+ * error and throws at runtime.
261
265
  */
262
266
  array<const T extends readonly unknown[]>(a: T & RejectValues<T>): Value;
263
267
  /**
@@ -308,7 +312,7 @@ interface RefOptions {
308
312
  * compiles to the raw var path `$owner.user_id`, which the engine resolves in
309
313
  * a single lookup — so when the base var `owner` is null (e.g. a `db.get` that
310
314
  * matched no row), it raises a runtime `ERROR_FATAL` "Unable to locate var"
311
- * (HTTP 500) instead of yielding null (issue #47).
315
+ * (HTTP 500) instead of yielding null.
312
316
  *
313
317
  * With `safe: true` the path compiles through the `get` filter
314
318
  * (`$owner|get:"user_id"`), which walks the remaining path and resolves to
@@ -324,7 +328,7 @@ interface RefOptions {
324
328
  *
325
329
  * Pass `{ safe: true }` to make a *nested* path null-safe: `ref("owner.user_id",
326
330
  * { safe: true })` resolves to null instead of 500ing when the base `owner` is
327
- * null (issue #47) — the intent-revealing opt-in for drilling into a `db.get`
331
+ * null — the intent-revealing opt-in for drilling into a `db.get`
328
332
  * result that may not exist.
329
333
  *
330
334
  * Picking a reference helper (these are easy to mix up):
@@ -339,7 +343,7 @@ declare function ref<const Name extends string>(name: Name, opts?: RefOptions):
339
343
  /**
340
344
  * The second parameter every non-`ref` reference creator declares, so that
341
345
  * passing `{ safe: true }` to one is a COMPILE error whose expected type is the
342
- * explanation (issue #80).
346
+ * explanation.
343
347
  *
344
348
  * `safe` was silently dropped before this: the creators took one parameter, so
345
349
  * JavaScript discarded the extra argument and TypeScript never saw it. An
@@ -353,7 +357,7 @@ declare function inp(name: string, opts?: SafeIsRefOnly): Value;
353
357
  * Reference a table **column**: `{tag:"col", value}` (used in `db.query` `where` +
354
358
  * table views). See {@link ref} for the full picker. The return is branded
355
359
  * {@link ColValue} so it is a *compile error* to pass `col()` into a `db.edit`/
356
- * `db.add` `row` — where it would resolve to `null` at runtime (issue #32).
360
+ * `db.add` `row` — where it would resolve to `null` at runtime.
357
361
  */
358
362
  declare function col(name: string, opts?: SafeIsRefOnly): ColValue;
359
363
  /**
@@ -422,7 +426,7 @@ type ToolsetPath = "token" | "params" | `params.${string}`;
422
426
  * - `toolset("params.tenant")` — one parameter out of that object.
423
427
  *
424
428
  * Only bound while a tool runs under its toolset; anywhere else it reads empty
425
- * (issue #152).
429
+ *.
426
430
  */
427
431
  declare function toolset(path: ToolsetPath, opts?: SafeIsRefOnly): Value;
428
432
  /**
@@ -745,7 +749,7 @@ declare function test(def: TestDef): TestDef;
745
749
  * The SDK emits only authored fields. Server/auto-generated keys (`id`,
746
750
  * `created_at`, `_xsid`, `@guid`, `@index`, `guid`, `_draft`, `stack_id`, …)
747
751
  * are generated by the engine on import and are NEVER emitted here; the test
748
- * normalizer (U6) strips them from the fixture before comparison.
752
+ * normalizer strips them from the fixture before comparison.
749
753
  */
750
754
  /**
751
755
  * Xano tagged-value tags (verified against the Xano engine). The runtime
@@ -779,7 +783,7 @@ interface FilterXdo<N extends string = string> {
779
783
  * Required here until now, which made a pulled workspace fail its own
780
784
  * type-check: codegen emits a stored filter verbatim when no `fl.*` form
781
785
  * reproduces it, and a verbatim emit of a filter stored without the key does
782
- * not have it (issue #134). Writing `disabled: false` in would have been the
786
+ * not have it. Writing `disabled: false` in would have been the
783
787
  * other repair, and it is the wrong one — it ADDS a key the engine did not
784
788
  * store, so the re-encode would no longer match the stored bytes and the value
785
789
  * would degrade further rather than round-trip.
@@ -810,7 +814,7 @@ interface MethodXdo {
810
814
  }
811
815
  /**
812
816
  * A field-bearing entry — the shape shared by function/query inputs and table
813
- * columns (KTD-6). In the persisted form the two are byte-identical: both use
817
+ * columns. In the persisted form the two are byte-identical: both use
814
818
  * `customize:{}`, numeric `market_item` ids, an `_xsid` placeholder, and a
815
819
  * present `description`. (The older parser generation emitted `customize:""` /
816
820
  * string ids for inputs; the live `mvp_query`/`mvp_dbo` rows show they converge.)
@@ -137,6 +137,13 @@ auto-assigned `id`/`created_at`, `s.db.del` binds `null`, and `edit`/`del` **thr
137
137
  the engine reads the operand as a text literal and fails at runtime with a parse error
138
138
  naming the *other* operand, so `db.query` rejects that spelling at export instead.
139
139
 
140
+ - **A join adds no columns to the returned row.** With or without a `bind`, a row is the
141
+ queried table's columns — which is what `InferResponse` types, so there is no `row.author`
142
+ to read. To bring a joined column back, project it with an `eval` whose `name` is the dotted
143
+ path: `eval: [{ name: "author.name", as: "author_name" }]` puts `author_name` on the row and
144
+ on the inferred type. A *bare* name there fails at runtime (it qualifies to the base table),
145
+ and a dotted joined column in `output` is dropped with no error.
146
+
140
147
  - **Paging changes the response shape.** Supplying `paging` with metadata on (the default)
141
148
  returns a **paging envelope** — `{ items, curPage, nextPage, prevPage, offset, perPage,
142
149
  itemsReceived }`, plus totals when `totals: true` — instead of a bare `Row[]`, and
@@ -194,8 +201,10 @@ auto-assigned `id`/`created_at`, `s.db.del` binds `null`, and `edit`/`del` **thr
194
201
  `lower`, and the `epochms_add_day` / `epochms_sub_month` family). The request-time timestamp
195
202
  filters do not — `epochms_transform`, `epochms_add_ms`, `epochms_add_secs`, `epochms_date`,
196
203
  `epochms_from_format` — and the engine doesn't degrade gracefully: the request dies with a
197
- bare fatal naming nothing. For a relative cutoff, use the SQL-side spelling or compute it in
198
- an earlier `s.set_var` and `ref()` that. `export()` warns; a bare `c.now()` is always fine.
204
+ bare fatal naming nothing. For a relative cutoff, use the SQL-side spelling by raw name,
205
+ since `fl.*` carries no builder for that family: `filter("epochms_add_day", …)` or the raw
206
+ `{ name, arg }` form — or compute it in an earlier `s.set_var` and `ref()` that. `export()`
207
+ warns; a bare `c.now()` is always fine.
199
208
  - **A `s.switch` case without `break: true` falls through.** The engine's default is
200
209
  fallthrough, so a matched case also runs every *later* case body — and the `default` block
201
210
  too. Whatever those bodies write gets written two or three times, at HTTP 200, with `tsc`
@@ -157,10 +157,10 @@ also pulls whatever its `stack` builds — the `s.*`/`c.*` factory *calls* run a
157
157
  to construct the def, so they can't be tree-shaken out. Types are free (`InferInput`/
158
158
  `InferRow` erase to nothing — use `import type`). That cost is a **floor**, not a function of
159
159
  how lean the def is. Measured on a Vite lib build against the published package: one
160
- `apiGroup` + one empty `query`, imported for a single `getPath()`, is **289 kB minified
161
- (57 kB gzipped)** against 56 B for a hand-written path string. A realistic def — two
160
+ `apiGroup` + one empty `query`, imported for a single `getPath()`, is **267 kB minified
161
+ (65 kB gzipped)** against 56 B for a hand-written path string. A realistic def — two
162
162
  tables, a foreign key, a typed input, a `db.query` with a `where` and a sort, plus a
163
- second endpoint — measures 290 kB. That 1 kB spread is the point: the floor is the SDK
163
+ second endpoint — measures 269 kB. That 2 kB spread is the point: the floor is the SDK
164
164
  runtime itself, so splitting modules or simplifying a def does not move it, and the cost
165
165
  is paid by importing any def at all.
166
166
 
@@ -172,7 +172,7 @@ xanots routes ./xano/index.ts --emit xano/routes.gen.ts
172
172
  ```
173
173
 
174
174
  The emitted file is plain data plus one interpolator and imports nothing at all — the same
175
- app builds to 1.3 kB, a 214x saving, with no SDK code in the output. Route names and their `{param}` keys are still checked at compile
175
+ app builds to 1.3 kB, a ~200x saving, with no SDK code in the output. Route names and their `{param}` keys are still checked at compile
176
176
  time, so a backend rename is a compile error rather than a 404:
177
177
 
178
178
  ```ts
@@ -34,13 +34,14 @@ primary key `id`):
34
34
  - ⚠ `like`/`ilike` take the operand as the PATTERN, verbatim: a bare term matches only an exact whole-string equal, and the endpoint answers HTTP 200 with zero rows — nothing reports a problem, so a search box that matches nothing ships. For substring matching use `includes`/`not includes`, which wrap the operand in `%…%` themselves and match case-INSENSITIVELY. Prefer them over a hand-built `"%" + term + "%"`, which is non-empty even for an empty term and so defeats `ignoreEmpty`; `includes` composes with it. `contains`/`@>`/`overlaps` are JSON/array containment, not text — on a text column they 400 `ParseError: Invalid value for param`.
35
35
  - Compose nested boolean logic with `and(...)` / `or(...)` groups (also available on `addon()` `where`).
36
36
  - An operand may be a bare value (`col`/`inp`/`ref`/`auth`/`c.*`) OR a **filtered** value (`withFilters(...)`) inline — the engine compiles the string and arithmetic filters (`trim`, `concat`, `upper`, `lower`, …) into the SQL.
37
- - ⚠ The REQUEST-TIME timestamp filters have no SQL form and kill the request with a bare fatal naming nothing: `epochms_transform`, `epochms_add_ms`, `epochms_add_secs`, `epochms_date`, `epochms_from_format`. For a relative cutoff use the SQL-side family instead (`epochms_add_day`, `epochms_sub_month`, `epochms_year`, …) or compute it in an earlier `s.set_var` and `ref()` that. `export --strict` reports it; a bare `c.now()` operand is always fine.
37
+ - ⚠ The REQUEST-TIME timestamp filters have no SQL form and kill the request with a bare fatal naming nothing: `epochms_transform`, `epochms_add_ms`, `epochms_add_secs`, `epochms_date`, `epochms_from_format`. For a relative cutoff use the SQL-side family instead (`epochms_add_day`, `epochms_sub_month`, `epochms_year`, …) — by raw name, since `fl.*` has no builder for them — or compute it in an earlier `s.set_var` and `ref()` that. `export --strict` reports it; a bare `c.now()` operand is always fine.
38
38
  - `bind: [{ table, as?, join?, where? }]` — joins (`context.bind[]`). `join` defaults to `"inner"`. `as` defaults to the table name; two joins to the same table need distinct aliases.
39
39
  - ⚠ In `where`/`sort`/`eval` a JOINED column takes a dotted path (`col("team_row.id")`); THIS query's own columns stay **bare** (`col("team")`). Qualifying your own by table name needs `tableAlias` (same rule as `aggregate`) — without it the engine reads the operand as text and 400s `ParseError: Invalid value for param` naming the OTHER operand, so it throws at export instead.
40
40
  - `bind: [{ table: team, as: "team_row", join: "left", where: expr(col("team"), "=", col("team_row.id")) }]`
41
+ - ⚠ A join does NOT put the joined table's columns on the returned row — with or without a `bind`, a row is the QUERIED table's columns, which is what `InferResponse` types. There is no `row.team_row`. To read a joined column, PROJECT it with an `eval` whose `name` is the dotted path: `eval: [{ name: "team_row.name", as: "team_name" }]` puts `team_name` on the row and on the inferred type. A bare `name` there is `Unsupported parameter reference` at runtime (it qualifies to the base table), and a dotted joined column in `output` is dropped with no error.
41
42
  - `returnType` — `"list"` (default) | `"single"` | `"count"` | `"exists"` | `"stream"` | `"aggregate"`. Drives `context.return.type` AND the `InferResponse` shape: `count`→`number`, `exists`→`boolean`, `single`→`Row|null`, `stream`→`Row[]` (pageable, no envelope), `list`→`Row[]`/envelope, `aggregate`→rows keyed by the `aggregate.group`/`eval` aliases. ⚠ A bare `count` of ZERO serializes as an EMPTY body, not `0` — a client parsing JSON gets a parse error on the one result it most needs to handle. Wrap it: `response: { count: ref("n") }`.
42
43
  - `eval: [{ name, as, filters? }]` — computed columns (`context.eval[]`). Each `as` grafts onto the row as an `unknown` key in `InferResponse`; shadowing a real column throws. Write `name` **bare** (`"embedding"`) — it is alias-qualified on emit exactly like `aggregate` (a bare eval name is `Unsupported param format` at runtime), and the statement declares the alias it used. An `as` alias is `sort`able in the SAME query.
43
- - An `eval`/`sort`/`where` filter pipeline compiles to **SQL**, so it resolves a DIFFERENT registry than `fl.*` (which runs in the request): the vector family, geo `distance`/`within`/`covers`, `search_rank`, the aggregators. Exported as `QUERY_EXPRESSION_FILTERS`/`VECTOR_FILTERS`.
44
+ - An `eval`/`sort`/`where` filter pipeline compiles to **SQL**, so it resolves a DIFFERENT registry than `fl.*` (which runs in the request): the vector family, geo `distance`/`within`/`covers`, `search_rank`, the aggregators. `fl.*` carries NO builder for these — reach them by raw name, `filter("epochms_add_day", …)` or the `{ name, arg }` form. The name lists are `QUERY_EXPRESSION_FILTERS`/`VECTOR_FILTERS` on `@xanots/sdk/internal`.
44
45
  - **Vector similarity search** — the ONLY way to query an `f.vector` column (no `SearchOp` does distance). `eval: [{ name: "embedding", as: "distance", filters: [{ name: "vector_cos_distance", arg: [inp("q")] }] }]` + `sort: [{ sortBy: "distance", dir: "asc" }]` ranks in the DATABASE over the column's index. Match the filter to the index `op` (`vector_cos_distance`↔`vector_cosine_ops`, `vector_l2_distance`↔`vector_l2_ops`, `vector_l1_distance`↔`vector_l1_ops`, `vector_inner_product`↔`vector_ip_ops`); `vector_cos_similarity` is the inverse, so sort it `desc`. The same filter on a `where` operand cuts off BY distance instead of by row count.
45
46
  - `aggregate: { group?, eval?, sort?, paging? }` (with `returnType:"aggregate"`) builds `context.return.aggregate`. `group`/`eval` are `{ name, as, filters? }`, an aggregator like `sum`/`count` riding `filters`. Some aggregators resolve ONLY here, not in a runtime value pipeline: `count_distinct`, `median`, `to_list`/`to_distinct_list` (each with `_asc`/`_desc`), and `vector_distance`.
46
47
  - ⚠ Write each `name` as a **bare** column (`"status"`). It is alias-qualified to `"<alias>.status"` on emit — the engine rejects an unqualified column in an aggregate with `Unsupported param format`. An already-dotted `name` (a `bind`ed/joined column) passes through.
package/llms/values.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - `c.array(a: Json[]) => Value` — Array constant (JSON string) → tag "const:array". Plain JSON literals only — a nested tagged value is rejected, same as c.obj.
13
13
  - `c.expression(source: string) => Value` — Xano Expression Engine source, passed through VERBATIM → tag "const:expr2". The string IS the expression: c.expression('"Hi, " ~ $input.name'), c.expression("$var.price * $var.qty"). ⚠️ NOT VALIDATED — never parsed or type-checked, invisible to InferResponse, and untouched by a rename that updates every typed ref(); a typo surfaces at runtime or as a wrong answer. Use it ONLY for syntax the typed surfaces cannot express (~ concatenation, inline arithmetic, conditionals) — prefer ref/inp/col, withFilters+fl.*, and obj() (which BUILDS a checked expression). Not the expr() condition builder.
14
14
  - `c.now() => Value` — Current time as epoch-ms — the engine-native const:epochms constant (no filter). Valid inline as a where/cmp operand. For cutoff math (cutoff = now - max_age) either compare inline or, for reuse/readability, hoist it into an s.set_var and compare against the var.
15
- - `obj(fields: Record<string, Value | nested>) => ObjValue<typeof fields>` — Dynamic object value → tag "const:expr2" (an object-literal expression string). The dynamic sibling of c.obj: members may be inp/ref/auth/col values, env()/setting()/sys.*, c.now(), c.* constants, nested records, or arrays — and each member may carry a FILTER CHAIN (withFilters + fl.*), which renders as the expression pipe `$var.row|get:"a.b"`. That matters most for the null-safe drill: db.get binds null on a miss, so ref(path, { safe: true }) inside an obj() is the normal shape, not a workaround — you do NOT need a preceding s.set_var to hoist it. The member record rides the return type, so InferResponse resolves each member the way it resolves a top-level response key — `response: { user: obj({ id: ref("row.id") }) }` derives `{ user: { id: Col | null } }`, and a raw nested object literal (`response: { user: { id: ref("row.id") } }`, auto-wrapped through this) derives the same. A constant record or list has two spellings that both work and render identically: bare (`{ a: 1 }`, `[]`) or `c.obj(...)`/`c.array([...])` — reach for the bare form, it is shorter. NEST WITH A RAW RECORD, not an inner obj() call: an inner call yields a const:expr2 value, which the expression serializer has no spelling for and THROWS. The legacy blank `c.obj(null)` is refused here (it evaluates to null, not {}) — write c.null() or c.obj(). Still rejected: a filter ARGUMENT carrying its own chain (a trailing | binds to the whole value, not one argument), a DISABLED filter (an expression string cannot record that), and the output/response/toolset/reg tags — build those in a prior step and ref() them. Use for e.g. s.ai.agent.run args.
15
+ - `obj(fields: Record<string, Value | nested>) => ObjValue<typeof fields>` — Dynamic object value → tag "const:expr2" (an object-literal expression string). The dynamic sibling of c.obj: members may be inp/ref/auth/col values, env()/setting()/sys.*, c.now(), c.* constants, nested records, or arrays — and each member may carry a FILTER CHAIN (withFilters + fl.*), which renders as the expression pipe `$var.row|get:"a.b"`. That matters most for the null-safe drill: db.get binds null on a miss, so ref(path, { safe: true }) inside an obj() is the normal shape, not a workaround — you do NOT need a preceding s.set_var to hoist it. The member record rides the return type, so InferResponse resolves each member the way it resolves a top-level response key — `response: { user: obj({ id: ref("row.id") }) }` derives `{ user: { id: Col | null } }`, and a raw nested object literal (`response: { user: { id: ref("row.id") } }`, auto-wrapped through this) derives the same. A constant record or list has two spellings that both work and render identically: bare (`{ a: 1 }`, `[]`) or `c.obj(...)`/`c.array([...])` — prefer the bare form. NEST WITH A RAW RECORD, not an inner obj() call: an inner call yields a const:expr2 value, which the expression serializer has no spelling for and THROWS. The legacy blank `c.obj(null)` is refused here (it evaluates to null, not {}) — write c.null() or c.obj(). Still rejected: a filter ARGUMENT that carries its own chain or is a c.now() (a trailing | binds to the whole value, not one argument, and c.now() needs one), a DISABLED filter (an expression string cannot record that), and the output/response/toolset/reg tags — build those in a prior step and ref() them. Use for e.g. s.ai.agent.run args.
16
16
  - `ref(name: string, opts?: { safe?: boolean }) => Value` — Reference a stack variable → tag "var". Pass { safe: true } for null-safe nested access — a dotted ref("owner.user_id", { safe: true }) compiles through the get filter so it resolves to null instead of raising "Unable to locate var" when the base is null.
17
17
  - `inp(name: string) => Value` — Reference a function/endpoint input → tag "input". Resolves ONLY against the `input` block of the def it sits in — a value produced earlier in the stack is `ref("var.field")`, not `inp("field")`. A name that is not declared here deploys clean and fails at runtime with ERROR_FATAL "Unable to locate input: <name>" on every branch that reads it; `export()` warns, and `--strict` fails the build. Sending the name in the request does NOT rescue it — an undeclared input is never bound, so the call fails identically with the value present. A dotted path drills INTO a declared input (`inp("action.amount")` needs a declared `action`).
18
18
  - `col(name: string) => Value` — Reference a table column → tag "col".
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # xanots v0.0.11
1
+ # xanots v0.0.12
2
2
 
3
3
  > TypeScript SDK that compiles a typed Xano workspace into the importable packageExport JSON bundle.
4
4
 
@@ -16,27 +16,26 @@ caller, `c.*` a constant — resolved at request time; JS operators over them do
16
16
  compute (see Gotchas). Requests share no memory — state persists in tables or redis.
17
17
 
18
18
  Coverage: object kinds 25/31, statement surfaces 214/214, filters 226 (226 typed).
19
- Not authorable here: branch, market_item, realtime_channel, run.job, run.service, tablemap — these cannot be authored and do not survive a pull; see `coverage.objectKinds.unmodeled` in `manifest.json`.
19
+ Not authorable here: branch, market_item, realtime_channel, run.job, run.service, tablemap — these cannot be authored and do not survive a pull; reasons in `coverage.objectKinds.unmodeled`.
20
20
 
21
21
  This file is the whole always-read surface: the mental model, the deploy contract,
22
22
  every gotcha, and control flow. Per-surface detail lives in the topic files listed
23
- below — open the one whose condition matches the task, and skip the rest. For
24
- exhaustive per-entry detail in NEITHER — a statement's full field schema with engine
25
- defaults, a filter's complete argument list, the engine `storedName` mapping — do a
26
- TARGETED lookup in the shipped `manifest.json` (a program imports it as
27
- `@xanots/sdk/manifest.json`, which Node ESM needs `with { type: "json" }` on;
28
- grep or `jq` the one entry you need;
29
- it is ~65k tokens, so never read it whole). Its top-level keys are `name`, `version`, `description`, `coverage`, `values`, `objectKinds`, `fieldTypes`, `statements`, `filters`, `cli`, `cliGlobalFlags`. `statements` and `filters` are ARRAYS, not maps — SELECT, do not index:
23
+ below — open the one whose condition matches the task, skip the rest. For
24
+ exhaustive per-entry detail in NEITHER — a statement's field schema with engine
25
+ defaults, a filter's full argument list, the `storedName` mapping — do a TARGETED
26
+ lookup in the shipped `manifest.json` (a program imports it as
27
+ `@xanots/sdk/manifest.json`, needing `with { type: "json" }` in Node ESM;
28
+ it is ~60k tokens, so never read it whole). Its top-level keys are `name`, `version`, `description`, `coverage`, `values`, `objectKinds`, `fieldTypes`, `statements`, `filters`, `cli`, `cliGlobalFlags`. `statements` and `filters` are ARRAYS, not maps — SELECT, do not index:
30
29
  jq '.statements[] | select(.sPath=="db.get")' manifest.json
31
30
  jq '.filters[] | select(.name=="json_decode")' manifest.json
31
+ ⚠ `fields` is null on the 65 `declarative: false` statements (`db.get`, …): typed wrappers whose arguments are the factory's `.d.ts` signature. Null ≠ missing.
32
32
  Select a statement on `sPath` (the `s.*` path you write), NOT `surface` (the XanoScript term): 24 of 214 differ — `var`→`set_var`, `break`→`foreach_break`, `foreach.remove`→`foreach_remove`, and every `expect.*`.
33
33
 
34
34
  ## Topic files
35
35
 
36
- Each line is a condition on the task. Open the files whose condition matches and
37
- skip the rest paths are relative to this file (`node_modules/@xanots/sdk/` once
38
- installed), so a plain file read resolves them at the version you have.
39
- `llms-full.txt` is everything concatenated — one fetch, for a reader that cannot open files.
36
+ Paths are relative to this file (`node_modules/@xanots/sdk/` once installed), so a
37
+ plain file read resolves them at the version you have.
38
+ `llms-full.txt` is everything concatenated one fetch for a reader that cannot open files.
40
39
 
41
40
  - [Object kinds](llms/object-kinds.md): Read when deciding WHAT to build — every authorable primitive (tables, endpoints, functions, tasks, agents, MCP, realtime, microservices, …), what each is, and which factory + register method build it. Read it too when a `register*` call will not typecheck because the array was built by `.flatMap()`/`.concat()` across modules.
42
41
  - [Core def shapes](llms/kinds-core.md): Read when authoring a function, query, api group, task, workflow test, middleware, or tool — and for the `response` and `expr` shapes every one of them uses.
@@ -336,9 +335,9 @@ Non-obvious authoring rules:
336
335
  no Node built-ins, so a bundler drops unused SDK exports. But importing a **def** for its
337
336
  `getPath()`/`verb`/`getUrl()`/`getChannel()` also pulls whatever its `stack` references:
338
337
  the `s.*`/`c.*` factory CALLS run at module load to BUILD it. Types are free.
339
- ⚠ A FLOOR — **~289 kB minified (~57 kB gzipped)** for the FIRST def; splitting modules
338
+ ⚠ A FLOOR — **~267 kB minified (~65 kB gzipped)** for the FIRST def; splitting modules
340
339
  never removes it. The floor is the RUNTIME, not the def: a second def, or a much richer
341
- one, adds ~1 kB — so reducing what a def does will not reduce it.
340
+ one, adds ~2 kB — so reducing what a def does will not reduce it.
342
341
  Fix: `xanots routes <entry> --emit xano/routes.gen.ts` (`paths` is an accepted alias) — verbs, paths, and sockets as
343
342
  plain data importing NOTHING, still compile-checked: `routePath("blog/{slug}", { slug })`,
344
343
  `channelPath("rooms/{room_id}", { room_id })`, `socketUrl("chat", baseUrl)` (tenant base
@@ -815,13 +814,14 @@ primary key `id`):
815
814
  - ⚠ `like`/`ilike` take the operand as the PATTERN, verbatim: a bare term matches only an exact whole-string equal, and the endpoint answers HTTP 200 with zero rows — nothing reports a problem, so a search box that matches nothing ships. For substring matching use `includes`/`not includes`, which wrap the operand in `%…%` themselves and match case-INSENSITIVELY. Prefer them over a hand-built `"%" + term + "%"`, which is non-empty even for an empty term and so defeats `ignoreEmpty`; `includes` composes with it. `contains`/`@>`/`overlaps` are JSON/array containment, not text — on a text column they 400 `ParseError: Invalid value for param`.
816
815
  - Compose nested boolean logic with `and(...)` / `or(...)` groups (also available on `addon()` `where`).
817
816
  - An operand may be a bare value (`col`/`inp`/`ref`/`auth`/`c.*`) OR a **filtered** value (`withFilters(...)`) inline — the engine compiles the string and arithmetic filters (`trim`, `concat`, `upper`, `lower`, …) into the SQL.
818
- - ⚠ The REQUEST-TIME timestamp filters have no SQL form and kill the request with a bare fatal naming nothing: `epochms_transform`, `epochms_add_ms`, `epochms_add_secs`, `epochms_date`, `epochms_from_format`. For a relative cutoff use the SQL-side family instead (`epochms_add_day`, `epochms_sub_month`, `epochms_year`, …) or compute it in an earlier `s.set_var` and `ref()` that. `export --strict` reports it; a bare `c.now()` operand is always fine.
817
+ - ⚠ The REQUEST-TIME timestamp filters have no SQL form and kill the request with a bare fatal naming nothing: `epochms_transform`, `epochms_add_ms`, `epochms_add_secs`, `epochms_date`, `epochms_from_format`. For a relative cutoff use the SQL-side family instead (`epochms_add_day`, `epochms_sub_month`, `epochms_year`, …) — by raw name, since `fl.*` has no builder for them — or compute it in an earlier `s.set_var` and `ref()` that. `export --strict` reports it; a bare `c.now()` operand is always fine.
819
818
  - `bind: [{ table, as?, join?, where? }]` — joins (`context.bind[]`). `join` defaults to `"inner"`. `as` defaults to the table name; two joins to the same table need distinct aliases.
820
819
  - ⚠ In `where`/`sort`/`eval` a JOINED column takes a dotted path (`col("team_row.id")`); THIS query's own columns stay **bare** (`col("team")`). Qualifying your own by table name needs `tableAlias` (same rule as `aggregate`) — without it the engine reads the operand as text and 400s `ParseError: Invalid value for param` naming the OTHER operand, so it throws at export instead.
821
820
  - `bind: [{ table: team, as: "team_row", join: "left", where: expr(col("team"), "=", col("team_row.id")) }]`
821
+ - ⚠ A join does NOT put the joined table's columns on the returned row — with or without a `bind`, a row is the QUERIED table's columns, which is what `InferResponse` types. There is no `row.team_row`. To read a joined column, PROJECT it with an `eval` whose `name` is the dotted path: `eval: [{ name: "team_row.name", as: "team_name" }]` puts `team_name` on the row and on the inferred type. A bare `name` there is `Unsupported parameter reference` at runtime (it qualifies to the base table), and a dotted joined column in `output` is dropped with no error.
822
822
  - `returnType` — `"list"` (default) | `"single"` | `"count"` | `"exists"` | `"stream"` | `"aggregate"`. Drives `context.return.type` AND the `InferResponse` shape: `count`→`number`, `exists`→`boolean`, `single`→`Row|null`, `stream`→`Row[]` (pageable, no envelope), `list`→`Row[]`/envelope, `aggregate`→rows keyed by the `aggregate.group`/`eval` aliases. ⚠ A bare `count` of ZERO serializes as an EMPTY body, not `0` — a client parsing JSON gets a parse error on the one result it most needs to handle. Wrap it: `response: { count: ref("n") }`.
823
823
  - `eval: [{ name, as, filters? }]` — computed columns (`context.eval[]`). Each `as` grafts onto the row as an `unknown` key in `InferResponse`; shadowing a real column throws. Write `name` **bare** (`"embedding"`) — it is alias-qualified on emit exactly like `aggregate` (a bare eval name is `Unsupported param format` at runtime), and the statement declares the alias it used. An `as` alias is `sort`able in the SAME query.
824
- - An `eval`/`sort`/`where` filter pipeline compiles to **SQL**, so it resolves a DIFFERENT registry than `fl.*` (which runs in the request): the vector family, geo `distance`/`within`/`covers`, `search_rank`, the aggregators. Exported as `QUERY_EXPRESSION_FILTERS`/`VECTOR_FILTERS`.
824
+ - An `eval`/`sort`/`where` filter pipeline compiles to **SQL**, so it resolves a DIFFERENT registry than `fl.*` (which runs in the request): the vector family, geo `distance`/`within`/`covers`, `search_rank`, the aggregators. `fl.*` carries NO builder for these — reach them by raw name, `filter("epochms_add_day", …)` or the `{ name, arg }` form. The name lists are `QUERY_EXPRESSION_FILTERS`/`VECTOR_FILTERS` on `@xanots/sdk/internal`.
825
825
  - **Vector similarity search** — the ONLY way to query an `f.vector` column (no `SearchOp` does distance). `eval: [{ name: "embedding", as: "distance", filters: [{ name: "vector_cos_distance", arg: [inp("q")] }] }]` + `sort: [{ sortBy: "distance", dir: "asc" }]` ranks in the DATABASE over the column's index. Match the filter to the index `op` (`vector_cos_distance`↔`vector_cosine_ops`, `vector_l2_distance`↔`vector_l2_ops`, `vector_l1_distance`↔`vector_l1_ops`, `vector_inner_product`↔`vector_ip_ops`); `vector_cos_similarity` is the inverse, so sort it `desc`. The same filter on a `where` operand cuts off BY distance instead of by row count.
826
826
  - `aggregate: { group?, eval?, sort?, paging? }` (with `returnType:"aggregate"`) builds `context.return.aggregate`. `group`/`eval` are `{ name, as, filters? }`, an aggregator like `sum`/`count` riding `filters`. Some aggregators resolve ONLY here, not in a runtime value pipeline: `count_distinct`, `median`, `to_list`/`to_distinct_list` (each with `_asc`/`_desc`), and `vector_distance`.
827
827
  - ⚠ Write each `name` as a **bare** column (`"status"`). It is alias-qualified to `"<alias>.status"` on emit — the engine rejects an unqualified column in an aggregate with `Unsupported param format`. An already-dotted `name` (a `bind`ed/joined column) passes through.
@@ -939,7 +939,7 @@ Microservices (the `microservice()` def and the statement that calls it):
939
939
  - `c.array(a: Json[]) => Value` — Array constant (JSON string) → tag "const:array". Plain JSON literals only — a nested tagged value is rejected, same as c.obj.
940
940
  - `c.expression(source: string) => Value` — Xano Expression Engine source, passed through VERBATIM → tag "const:expr2". The string IS the expression: c.expression('"Hi, " ~ $input.name'), c.expression("$var.price * $var.qty"). ⚠️ NOT VALIDATED — never parsed or type-checked, invisible to InferResponse, and untouched by a rename that updates every typed ref(); a typo surfaces at runtime or as a wrong answer. Use it ONLY for syntax the typed surfaces cannot express (~ concatenation, inline arithmetic, conditionals) — prefer ref/inp/col, withFilters+fl.*, and obj() (which BUILDS a checked expression). Not the expr() condition builder.
941
941
  - `c.now() => Value` — Current time as epoch-ms — the engine-native const:epochms constant (no filter). Valid inline as a where/cmp operand. For cutoff math (cutoff = now - max_age) either compare inline or, for reuse/readability, hoist it into an s.set_var and compare against the var.
942
- - `obj(fields: Record<string, Value | nested>) => ObjValue<typeof fields>` — Dynamic object value → tag "const:expr2" (an object-literal expression string). The dynamic sibling of c.obj: members may be inp/ref/auth/col values, env()/setting()/sys.*, c.now(), c.* constants, nested records, or arrays — and each member may carry a FILTER CHAIN (withFilters + fl.*), which renders as the expression pipe `$var.row|get:"a.b"`. That matters most for the null-safe drill: db.get binds null on a miss, so ref(path, { safe: true }) inside an obj() is the normal shape, not a workaround — you do NOT need a preceding s.set_var to hoist it. The member record rides the return type, so InferResponse resolves each member the way it resolves a top-level response key — `response: { user: obj({ id: ref("row.id") }) }` derives `{ user: { id: Col | null } }`, and a raw nested object literal (`response: { user: { id: ref("row.id") } }`, auto-wrapped through this) derives the same. A constant record or list has two spellings that both work and render identically: bare (`{ a: 1 }`, `[]`) or `c.obj(...)`/`c.array([...])` — reach for the bare form, it is shorter. NEST WITH A RAW RECORD, not an inner obj() call: an inner call yields a const:expr2 value, which the expression serializer has no spelling for and THROWS. The legacy blank `c.obj(null)` is refused here (it evaluates to null, not {}) — write c.null() or c.obj(). Still rejected: a filter ARGUMENT carrying its own chain (a trailing | binds to the whole value, not one argument), a DISABLED filter (an expression string cannot record that), and the output/response/toolset/reg tags — build those in a prior step and ref() them. Use for e.g. s.ai.agent.run args.
942
+ - `obj(fields: Record<string, Value | nested>) => ObjValue<typeof fields>` — Dynamic object value → tag "const:expr2" (an object-literal expression string). The dynamic sibling of c.obj: members may be inp/ref/auth/col values, env()/setting()/sys.*, c.now(), c.* constants, nested records, or arrays — and each member may carry a FILTER CHAIN (withFilters + fl.*), which renders as the expression pipe `$var.row|get:"a.b"`. That matters most for the null-safe drill: db.get binds null on a miss, so ref(path, { safe: true }) inside an obj() is the normal shape, not a workaround — you do NOT need a preceding s.set_var to hoist it. The member record rides the return type, so InferResponse resolves each member the way it resolves a top-level response key — `response: { user: obj({ id: ref("row.id") }) }` derives `{ user: { id: Col | null } }`, and a raw nested object literal (`response: { user: { id: ref("row.id") } }`, auto-wrapped through this) derives the same. A constant record or list has two spellings that both work and render identically: bare (`{ a: 1 }`, `[]`) or `c.obj(...)`/`c.array([...])` — prefer the bare form. NEST WITH A RAW RECORD, not an inner obj() call: an inner call yields a const:expr2 value, which the expression serializer has no spelling for and THROWS. The legacy blank `c.obj(null)` is refused here (it evaluates to null, not {}) — write c.null() or c.obj(). Still rejected: a filter ARGUMENT that carries its own chain or is a c.now() (a trailing | binds to the whole value, not one argument, and c.now() needs one), a DISABLED filter (an expression string cannot record that), and the output/response/toolset/reg tags — build those in a prior step and ref() them. Use for e.g. s.ai.agent.run args.
943
943
  - `ref(name: string, opts?: { safe?: boolean }) => Value` — Reference a stack variable → tag "var". Pass { safe: true } for null-safe nested access — a dotted ref("owner.user_id", { safe: true }) compiles through the get filter so it resolves to null instead of raising "Unable to locate var" when the base is null.
944
944
  - `inp(name: string) => Value` — Reference a function/endpoint input → tag "input". Resolves ONLY against the `input` block of the def it sits in — a value produced earlier in the stack is `ref("var.field")`, not `inp("field")`. A name that is not declared here deploys clean and fails at runtime with ERROR_FATAL "Unable to locate input: <name>" on every branch that reads it; `export()` warns, and `--strict` fails the build. Sending the name in the request does NOT rescue it — an undeclared input is never bound, so the call fails identically with the value present. A dotted path drills INTO a declared input (`inp("action.amount")` needs a declared `action`).
945
945
  - `col(name: string) => Value` — Reference a table column → tag "col".