@xanots/sdk 0.0.9 → 0.0.11

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 (116) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +66 -21
  3. package/dist/.build-fingerprint +1 -1
  4. package/dist/{agent-file-refresh-4W37DGXS.js → agent-file-refresh-QNKN5RYD.js} +3 -3
  5. package/dist/bin.js +10 -8
  6. package/dist/{branch-commands-PBQ36L4A.js → branch-commands-2BLOC2GR.js} +13 -11
  7. package/dist/bundle.d.ts +218 -0
  8. package/dist/bundle.js +143 -0
  9. package/dist/{capture-7PO6SGB4.js → capture-4WVJY4DQ.js} +2 -2
  10. package/dist/{chunk-OW2QZEWL.js → chunk-22TKBSDV.js} +2 -2
  11. package/dist/{chunk-ZOYMZZ3S.js → chunk-3INK4Y4E.js} +1 -1
  12. package/dist/{chunk-NDZFBZHC.js → chunk-3UABJA4X.js} +1 -1
  13. package/dist/{chunk-6Z5CNWFC.js → chunk-4IF54NU5.js} +37 -6
  14. package/dist/{chunk-KSV7VOEX.js → chunk-4Q7ZOHH7.js} +3 -3
  15. package/dist/{chunk-QTNO2WD6.js → chunk-7ZYW652H.js} +1 -1
  16. package/dist/{chunk-4PTSTDIG.js → chunk-AIZKXUNP.js} +2 -2
  17. package/dist/{chunk-P5C2YKAM.js → chunk-ANUDXFEX.js} +18 -27
  18. package/dist/{chunk-4BXJGVZ3.js → chunk-BC2C5GVI.js} +2 -69
  19. package/dist/chunk-BSK7ELHU.js +70 -0
  20. package/dist/chunk-CTD5ZCV6.js +28 -0
  21. package/dist/{chunk-5HCDZ2XN.js → chunk-DBFU47BJ.js} +2 -2
  22. package/dist/{chunk-OKNSR7MT.js → chunk-DIA7CT7J.js} +129 -150
  23. package/dist/chunk-EHP3WPEG.js +21 -0
  24. package/dist/{chunk-JQLR64UC.js → chunk-F6CYJ7TN.js} +21 -6
  25. package/dist/{chunk-BKDJN76G.js → chunk-FE5I6S6N.js} +1 -1
  26. package/dist/{chunk-5YQOIFT4.js → chunk-G4EJMQLD.js} +2 -2
  27. package/dist/{chunk-L6TPHWAU.js → chunk-HYBN4H3F.js} +60 -86
  28. package/dist/{chunk-QIWBCS7N.js → chunk-JGCWTCA7.js} +2 -2
  29. package/dist/{chunk-MDXR5E5Q.js → chunk-K5IOND4K.js} +2 -2
  30. package/dist/{chunk-4PE4ZAB7.js → chunk-KA6G2L7U.js} +5 -5
  31. package/dist/chunk-OHX6MIUZ.js +184 -0
  32. package/dist/{chunk-UDL46O35.js → chunk-OWGCOGKK.js} +155 -3
  33. package/dist/{chunk-QONOJO45.js → chunk-QK7ZQJLP.js} +140 -20
  34. package/dist/{chunk-3QW5NFZJ.js → chunk-QKM4U5UK.js} +3 -3
  35. package/dist/{chunk-7OXFMCTX.js → chunk-QYMAZRAU.js} +7 -7
  36. package/dist/{chunk-K3YSQDXJ.js → chunk-RCT7UX7B.js} +92 -75
  37. package/dist/{chunk-T4XPCJRF.js → chunk-TCFIPDB3.js} +1 -1
  38. package/dist/{chunk-ZZLPTHT5.js → chunk-UOZMSF4C.js} +15 -8
  39. package/dist/{chunk-7JDT4PBU.js → chunk-VAF6A3YD.js} +5 -179
  40. package/dist/{chunk-6DHBYBTO.js → chunk-VNQM3V2C.js} +1 -2
  41. package/dist/chunk-WGPXT2G2.js +22 -0
  42. package/dist/{chunk-6AAT2AYQ.js → chunk-WP4OZZV4.js} +2 -2
  43. package/dist/{chunk-OYMR5AMJ.js → chunk-XEOX6AM7.js} +2 -2
  44. package/dist/{chunk-MU3O43L2.js → chunk-YBC3IKMF.js} +2 -2
  45. package/dist/chunk-ZQ2PKR6R.js +40 -0
  46. package/dist/cli.d.ts +57 -31
  47. package/dist/cli.js +9 -7
  48. package/dist/codegen-command-FUT2KJB6.js +49 -0
  49. package/dist/codegen.d.ts +2 -1
  50. package/dist/{completion-A6BZ3XGU.js → completion-WF46272M.js} +2 -2
  51. package/dist/{config-NL33PN4D.js → config-476F3PT5.js} +1 -1
  52. package/dist/{deploy-command-5TTI4TUP.js → deploy-command-IP7V7GT4.js} +69 -100
  53. package/dist/{ephemeral-command-7TK52YQM.js → ephemeral-command-U4AQ3TXX.js} +25 -28
  54. package/dist/index.d.ts +6 -4
  55. package/dist/index.js +10 -9
  56. package/dist/init-command-NPVL32L6.js +34 -0
  57. package/dist/{onboard-command-EHOHKQQU.js → init-web-3JNFG6GI.js} +10 -10
  58. package/dist/internal.d.ts +4 -4
  59. package/dist/internal.js +86 -73
  60. package/dist/io-P2H75UV2.js +12 -0
  61. package/dist/{live-diff-QK4KC2FJ.js → live-diff-IXKBVG4K.js} +3 -3
  62. package/dist/{lock-GFXD6G2E.js → lock-46FWYE4D.js} +3 -2
  63. package/dist/{lock-commands-ET7NIOLH.js → lock-commands-ZZKZ4LZJ.js} +23 -21
  64. package/dist/{login-command-DFCURZLC.js → login-command-Z6CHTA57.js} +7 -7
  65. package/dist/{logout-command-WXVZQCAV.js → logout-command-ER6IKAYJ.js} +2 -2
  66. package/dist/{loop-7SAIGRCZ.js → loop-D5NPL4VH.js} +3 -3
  67. package/dist/{marketplace-command-BCOAAN2I.js → marketplace-command-P4IPLJ6J.js} +6 -6
  68. package/dist/{meta-client-57ZWVHST.js → meta-client-K2J4XH64.js} +6 -6
  69. package/dist/node.d.ts +5 -4
  70. package/dist/node.js +15 -13
  71. package/dist/{validate-command-SK5FPCV7.js → preflight-command-K346GPTY.js} +20 -18
  72. package/dist/{profile-command-QJAAUZIV.js → profile-command-LJSBDV2L.js} +5 -5
  73. package/dist/{release-command-4EXVLYV2.js → release-command-IMNTIVWK.js} +42 -38
  74. package/dist/response-BQVQ24l1.d.ts +844 -0
  75. package/dist/{routes-manifest-SP3ZXLMR.js → routes-manifest-PWZHDOI5.js} +2 -2
  76. package/dist/scaffold.js +2 -2
  77. package/dist/{static-host-D6KS7X45.js → static-host-3WMV7IZO.js} +1 -1
  78. package/dist/status-command-AL47VG7H.js +162 -0
  79. package/dist/{store-CUCBSYLj.d.ts → store-BLyNeQ8S.d.ts} +21 -8
  80. package/dist/{test-command-2F5LKMI6.js → test-command-YAZLKLGQ.js} +45 -28
  81. package/dist/{upgrade-command-QL523I62.js → upgrade-command-BN3EHAOI.js} +16 -14
  82. package/dist/{workspace-K72NP7SX.js → workspace-2COHDBM3.js} +1 -1
  83. package/dist/{workspace-command-HYA6JEKK.js → workspace-command-MNK7Y7MQ.js} +36 -34
  84. package/dist/{workspace-export-AJMGN3CQ.js → workspace-export-DURY5WYL.js} +2 -2
  85. package/dist/{response-CVAE2kMj.d.ts → xdo-BjJj5W_E.d.ts} +1 -837
  86. package/guides/README.md +2 -2
  87. package/guides/cli.md +17 -19
  88. package/guides/codegen.md +21 -5
  89. package/guides/coverage.md +1 -1
  90. package/guides/deploying.md +31 -34
  91. package/guides/environment.md +4 -4
  92. package/guides/object-kinds.md +3 -3
  93. package/guides/project-structure.md +1 -1
  94. package/guides/scaffold.md +25 -2
  95. package/guides/typed-frontend.md +2 -2
  96. package/llms/filters.md +11 -10
  97. package/llms/kinds-agent-mcp.md +1 -1
  98. package/llms/kinds-core.md +2 -2
  99. package/llms/kinds-realtime.md +1 -1
  100. package/llms/lock.md +5 -5
  101. package/llms/statements-data.md +3 -1
  102. package/llms/tests.md +1 -1
  103. package/llms/values.md +1 -1
  104. package/llms-full.txt +65 -63
  105. package/llms.txt +40 -41
  106. package/manifest.json +96 -427
  107. package/package.json +7 -2
  108. package/dist/chunk-3EYUR3TX.js +0 -100
  109. package/dist/chunk-JQJPFUZI.js +0 -118
  110. package/dist/chunk-YNZFIY4J.js +0 -78
  111. package/dist/codegen-command-YSCSCVT5.js +0 -43
  112. package/dist/env-target-4T7AT347.js +0 -16
  113. package/dist/init-command-OVFFW4QS.js +0 -30
  114. package/dist/io-M7XZEMK7.js +0 -11
  115. package/dist/sandbox-details-command-EDO56IY3.js +0 -18
  116. package/dist/sandbox-export-command-MO6DEZ7Z.js +0 -24
package/dist/internal.js CHANGED
@@ -1,11 +1,7 @@
1
1
  import {
2
2
  FIELD_METHODS,
3
- IMPLEMENTED_STATEMENTS,
4
- STATEMENT_SURFACES,
5
- SUPERSEDED_STATEMENTS,
6
- TOTAL_STATEMENTS,
7
- sPathOf
8
- } from "./chunk-4BXJGVZ3.js";
3
+ SUPERSEDED_STATEMENTS
4
+ } from "./chunk-BSK7ELHU.js";
9
5
  import {
10
6
  addonKind,
11
7
  agentKind,
@@ -33,7 +29,7 @@ import {
33
29
  taskKind,
34
30
  toolKind,
35
31
  workflowTestKind
36
- } from "./chunk-7OXFMCTX.js";
32
+ } from "./chunk-QYMAZRAU.js";
37
33
  import {
38
34
  GENERATED_SPECS,
39
35
  GENERATED_STATEMENT_NAMES,
@@ -55,7 +51,13 @@ import {
55
51
  registerSpec,
56
52
  triggerKind,
57
53
  workspaceKind
58
- } from "./chunk-ZZLPTHT5.js";
54
+ } from "./chunk-UOZMSF4C.js";
55
+ import {
56
+ IMPLEMENTED_STATEMENTS,
57
+ STATEMENT_SURFACES,
58
+ TOTAL_STATEMENTS,
59
+ sPathOf
60
+ } from "./chunk-BC2C5GVI.js";
59
61
  import {
60
62
  COMMANDS,
61
63
  FLAGS,
@@ -63,7 +65,7 @@ import {
63
65
  flagKey,
64
66
  flagSummary,
65
67
  visibleFlags
66
- } from "./chunk-OKNSR7MT.js";
68
+ } from "./chunk-DIA7CT7J.js";
67
69
  import {
68
70
  DECODE_ONLY_STATEMENTS,
69
71
  FILTER_NAMES,
@@ -109,13 +111,8 @@ import {
109
111
  serializeBundle,
110
112
  tableKind,
111
113
  toNestedFields
112
- } from "./chunk-QONOJO45.js";
114
+ } from "./chunk-QK7ZQJLP.js";
113
115
  import "./chunk-WHOJWOSV.js";
114
- import {
115
- encodeStatement,
116
- getStatementFactory,
117
- isRegisteredStatement
118
- } from "./chunk-5XZ744TS.js";
119
116
  import {
120
117
  LOCK_VERSION,
121
118
  REFERENCEABLE_KINDS,
@@ -137,7 +134,13 @@ import {
137
134
  resolveRef,
138
135
  serializeLock,
139
136
  validateLockModel
140
- } from "./chunk-7JDT4PBU.js";
137
+ } from "./chunk-VAF6A3YD.js";
138
+ import {
139
+ encodeStatement,
140
+ getStatementFactory,
141
+ isRegisteredStatement
142
+ } from "./chunk-5XZ744TS.js";
143
+ import "./chunk-OHX6MIUZ.js";
141
144
 
142
145
  // src/statements/byte-evidence.ts
143
146
  var UNCONFIRMED_STATEMENTS = /* @__PURE__ */ new Set([]);
@@ -313,7 +316,7 @@ var VALUE_CONSTRUCTORS = [
313
316
  { name: "c.expression", signature: "(source: string) => Value", description: `Xano Expression Engine source, passed through VERBATIM \u2192 tag "const:expr2". The string IS the expression: c.expression('"Hi, " ~ $input.name'), c.expression("$var.price * $var.qty"). \u26A0\uFE0F NOT VALIDATED \u2014 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) \u2014 prefer ref/inp/col, withFilters+fl.*, and obj() (which BUILDS a checked expression). Not the expr() condition builder.` },
314
317
  { name: "c.expressionLegacy", signature: "(source: string) => Value", legacy: true, description: "the older `const:expr` expression form, emitted by codegen for workspaces that still hold one \u2014 author `c.expression` instead." },
315
318
  { name: "c.now", signature: "() => Value", description: "Current time as epoch-ms \u2014 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." },
316
- { name: "obj", signature: "(fields: Record<string, Value | nested>) => ObjValue<typeof fields>", description: 'Dynamic object value \u2192 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 \u2014 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 \u2014 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 \u2014 `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. 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. 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 \u2014 build those in a prior step and ref() them. Use for e.g. s.ai.agent.run args.' },
319
+ { name: "obj", signature: "(fields: Record<string, Value | nested>) => ObjValue<typeof fields>", description: 'Dynamic object value \u2192 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 \u2014 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 \u2014 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 \u2014 `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([...])` \u2014 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 {}) \u2014 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 \u2014 build those in a prior step and ref() them. Use for e.g. s.ai.agent.run args.' },
317
320
  { name: "ref", signature: "(name: string, opts?: { safe?: boolean }) => Value", description: 'Reference a stack variable \u2192 tag "var". Pass { safe: true } for null-safe nested access \u2014 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.' },
318
321
  {
319
322
  name: "inp",
@@ -541,6 +544,14 @@ var FILTER_NOTES = {
541
544
  ends_with: "piped value is the subject text; the arg is the substring searched for",
542
545
  starts_with: "piped value is the subject text; the arg is the substring searched for",
543
546
  icontains: "case-insensitive; piped value is the subject, the arg is the substring",
547
+ // The regex family REVERSES that direction, which is the whole reason it needs
548
+ // its own note: the pattern is piped and the subject is the argument. Written
549
+ // the other way round it reads correctly, type-checks, and answers false for
550
+ // every input, so the note has to state the order and the symptom.
551
+ regex_test: 'piped value is the PATTERN (build it with `c.regex(...)`); the arg is the subject \u2014 the REVERSE of `contains`/`starts_with`. Swapped, it answers false for every input with no error, so write `withFilters(c.regex("^a+$"), fl.regex_test(inp("s")))` (or name the arg: `fl.regex_test({ subject: inp("s") })`). A pattern found in the subject slot is refused at build time',
552
+ regex_match: "piped value is the PATTERN, the arg is the subject \u2014 see `regex_test`",
553
+ regex_match_all: "piped value is the PATTERN, the arg is the subject \u2014 see `regex_test`",
554
+ regex_replace: "piped value is the PATTERN, `subject` is the text searched \u2014 see `regex_test`. The replacement comes FIRST",
544
555
  iends_with: "case-insensitive; piped value is the subject, the arg is the substring",
545
556
  istarts_with: "case-insensitive; piped value is the subject, the arg is the substring",
546
557
  // "empty" is a specific set of values, not just null.
@@ -774,7 +785,7 @@ function renderTestsSection() {
774
785
  "",
775
786
  "`xanots test run-all` runs the unit tests AND the `workflowTest()` objects an environment carries. It takes no entry file and compiles nothing: it runs what is DEPLOYED, so deploy before testing.",
776
787
  "",
777
- "- `--dest ephemeral` (DEFAULT, `--name <env>` to pick one), `--dest sandbox`, or `--dest workspace`. Unlike `deploy`, `workspace` is allowed here \u2014 running a test reads.",
788
+ "- `--env ephemeral` (DEFAULT \u2014 the one this project last deployed to), `--env ephemeral:<name>`, or `--env workspace`. Same grammar as `init --from`. `deploy` takes no `--env` at all; `test` does, and `workspace` is allowed here because running a test only reads.",
778
789
  '- `xanots test list` shows what is there without running it; `xanots test run "<name>"` runs one. When a name is ambiguous the error prints the qualified `function:math/happy path` form, which `run` also accepts.',
779
790
  "- `--kind unit|workflow` narrows to one family. `--concurrency <n>` defaults to 1: tests share the environment database.",
780
791
  "- A failing suite exits 5, distinct from a crash. Tests that could not be REACHED exit 6 \u2014 retry that one, investigate the other. An environment with no tests is success, not failure.",
@@ -811,7 +822,9 @@ function renderSections(m) {
811
822
  "below \u2014 open the one whose condition matches the task, and skip the rest. For",
812
823
  "exhaustive per-entry detail in NEITHER \u2014 a statement's full field schema with engine",
813
824
  "defaults, a filter's complete argument list, the engine `storedName` mapping \u2014 do a",
814
- "TARGETED lookup in the shipped `manifest.json` (grep or `jq` the one entry you need;",
825
+ "TARGETED lookup in the shipped `manifest.json` (a program imports it as",
826
+ '`@xanots/sdk/manifest.json`, which Node ESM needs `with { type: "json" }` on;',
827
+ "grep or `jq` the one entry you need;",
815
828
  "it is ~65k tokens, so never read it whole). Its top-level keys are `" + Object.keys(m).join("`, `") + "`. `statements` and `filters` are ARRAYS, not maps \u2014 SELECT, do not index:",
816
829
  ` jq '.statements[] | select(.sPath=="db.get")' manifest.json`,
817
830
  ` jq '.filters[] | select(.name=="json_decode")' manifest.json`,
@@ -881,7 +894,7 @@ function renderSections(m) {
881
894
  "",
882
895
  "Set `canonical` on every `apiGroup`. The engine mints the URL token server-side, so",
883
896
  "without one a group's client paths are unresolvable until a lock exists: the bundle",
884
- "exports fine and `xanots paths` / `getPath()` then fail on the very queries it just",
897
+ "exports fine and `xanots routes` / `getPath()` then fail on the very queries it just",
885
898
  "built. An explicit `canonical` resolves them from the source alone.",
886
899
  "",
887
900
  "Build warnings: `export()` prints the shapes that deploy clean and then do the wrong",
@@ -900,7 +913,7 @@ function renderSections(m) {
900
913
  "To rename an object: rename in code, export (stderr prints the exact fix-up), run",
901
914
  "`xanots lock rename <kind> <old> <new>`, export again \u2014 the original guid is emitted",
902
915
  "under the new name, so the engine renames in place instead of delete+create. Taking",
903
- "over an existing workspace: `xanots lock adopt <its-packageExport.json>` first, then",
916
+ "over an existing workspace: `xanots lock import <its-packageExport.json>` first, then",
904
917
  "export. Pruning, programmatic seeding, and which commands write the lock:",
905
918
  "`llms/lock.md`.",
906
919
  "",
@@ -913,19 +926,19 @@ function renderSections(m) {
913
926
  "AND records \u2014 before importing. The blast radius is a disposable environment, not a",
914
927
  "production workspace, but confirm with the user before the first run.",
915
928
  "",
916
- "**Two destinations, and the choice changes more than the target.**",
929
+ "**One destination, and no flag for it.**",
917
930
  "",
918
- "- `--dest ephemeral` (DEFAULT) \u2014 a NAMED, workspace-scoped, auto-expiring tenant",
919
- " (~1h; `--expires-hours` 1\u201372 at create time). The active one is tracked in",
931
+ "- `xanots deploy` writes to a NAMED, workspace-scoped, auto-expiring ephemeral tenant",
932
+ " (~1h; `--expires-hours` 1\u201372 at create time), and to nothing else \u2014 an `--env` here",
933
+ " is a usage error, not a choice. The active env is tracked in",
920
934
  " `./.xano/ephemeral.json`, so deploying again REFRESHES it and the URL is unchanged;",
921
935
  " if it expired or was swept, a fresh one is created and the new URL is called out.",
922
936
  " `--static` puts the frontend ON THE EPHEMERAL, so backend and frontend share one",
923
937
  " disposable environment.",
924
938
  " \u26A0 Only the BACKEND URL survives a refresh: the replace clears static hosting too,",
925
939
  " so `--static` publishes a NEW host every run and the previous URL stops serving.",
926
- "- `--dest sandbox` \u2014 your single throwaway tenant, no expiry. `--static` puts the",
927
- " frontend on your OWN (parent) workspace instead, because the sandbox tenant does",
928
- " not serve static hosting.",
940
+ "- `xanots status` names the env this project last deployed to, its URL and its expiry,",
941
+ " without your having to remember which one it was.",
929
942
  "- `xanots release` promotes to your INSTANCE workspace and MERGES, not replaces:",
930
943
  " adds/updates what you define, deletes nothing, writes no rows. Destruction is",
931
944
  " opt-in per flag, previewed + confirmed, and can drop a table WITH its rows.",
@@ -948,7 +961,7 @@ function renderSections(m) {
948
961
  '\u26A0 It is INJECTED in bracket form \u2014 `window["XANO_HOST"]="\u2026"` \u2014 so verifying a deploy',
949
962
  "by grepping `window.XANO_HOST` matches nothing and reads as a failed inject. Grep the",
950
963
  "bare `XANO_HOST` token.",
951
- "\u26A0 `xanots validate` ignores the deploy login and reads `XANO_VALIDATE_INSTANCE` /",
964
+ "\u26A0 `xanots preflight` ignores the deploy login and reads `XANO_VALIDATE_INSTANCE` /",
952
965
  "`XANO_VALIDATE_TOKEN` (+ optional `XANO_VALIDATE_WORKSPACE_ID`) from the environment.",
953
966
  "**Displaying a stored file.** A file column comes back as `{ path, name, type, size,",
954
967
  "meta, access, url }`. \u26A0 Do NOT use its `url`: on a tenant-scoped environment that field",
@@ -1034,6 +1047,10 @@ function renderSections(m) {
1034
1047
  ' check-in \u2014 use `db.query({ where: [expr(col("habit"), "=", ...), expr(col("date"), "=", ...)], as })`',
1035
1048
  " (a `where` array is ANDed) and branch on the result, rather than pushing the",
1036
1049
  " check to the client.",
1050
+ "- **A column named `run` is reserved.** The table deploys and reads back fine, then",
1051
+ " EVERY `s.db.add` into it 400s \u2014 at any column type, with or without a value \u2014 and",
1052
+ " the error names the column while complaining about the VALUE. Use `run_id`. Exact,",
1053
+ " case-sensitive, one name: `Run`/`runs`/`run_id` are fine. `--strict` fails on it.",
1037
1054
  "- **System columns are auto-injected.** `id` + `created_at` are prepended to",
1038
1055
  " every table (`system: true` by default); declaring them by hand is redundant.",
1039
1056
  ' `id` is an `int` PK by default; pass `idType: "uuid"` on the table for a uuid key.',
@@ -1088,19 +1105,20 @@ function renderSections(m) {
1088
1105
  " `xanots export`/`deploy` CLI path. Seed rows are never emitted into the bundle",
1089
1106
  " either way; only `deploy` ships them. The `node:fs` writers",
1090
1107
  " (`writeBundle`/`writeArtifact`) and lock-file I/O import from `@xanots/sdk/node`,",
1091
- " NOT the browser-safe `@xanots/sdk` entry (which a frontend can import query",
1092
- " defs from to use `getPath()`/`InferInput` with no Node built-ins in the bundle).",
1108
+ " NOT the browser-safe `@xanots/sdk` entry (a frontend imports defs from it for",
1109
+ " `getPath()`/`InferInput`).",
1093
1110
  " The compiler machinery (per-kind `encode*`, the registries, the bundle serializer,",
1094
1111
  " the lock model) is on `@xanots/sdk/internal` and is never needed to author.",
1112
+ " READING a bundle back is `@xanots/sdk/bundle` \u2014 a statement walker (`2.if.0` paths),",
1113
+ " a structural hash, `mvp:*` catalog, `tableRefOf`.",
1095
1114
  "- **Client bundle size / tree-shaking.** `@xanots/sdk` is `sideEffects: false` and pulls",
1096
1115
  " no Node built-ins, so a bundler drops unused SDK exports. But importing a **def** for its",
1097
1116
  " `getPath()`/`verb`/`getUrl()`/`getChannel()` also pulls whatever its `stack` references:",
1098
1117
  " the `s.*`/`c.*` factory CALLS run at module load to BUILD it. Types are free.",
1099
1118
  " \u26A0 A FLOOR \u2014 **~289 kB minified (~57 kB gzipped)** for the FIRST def; splitting modules",
1100
1119
  " never removes it. The floor is the RUNTIME, not the def: a second def, or a much richer",
1101
- " one, adds ~1 kB. So the cost is paid by importing ANY def at all, and reducing what a",
1102
- " def does will not reduce it.",
1103
- " Fix: `xanots paths <entry> --emit xano/routes.gen.ts` (`routes` is an accepted alias) \u2014 verbs, paths, and sockets as",
1120
+ " one, adds ~1 kB \u2014 so reducing what a def does will not reduce it.",
1121
+ " Fix: `xanots routes <entry> --emit xano/routes.gen.ts` (`paths` is an accepted alias) \u2014 verbs, paths, and sockets as",
1104
1122
  ' plain data importing NOTHING, still compile-checked: `routePath("blog/{slug}", { slug })`,',
1105
1123
  ' `channelPath("rooms/{room_id}", { room_id })`, `socketUrl("chat", baseUrl)` (tenant base',
1106
1124
  " URLs lifted to `wss://h/ws/<tenant>:<canonical>`). A rename is a type error, not a 404.",
@@ -1153,17 +1171,18 @@ function renderSections(m) {
1153
1171
  " return.",
1154
1172
  "- **Build regex-filter patterns with `c.regex(body, flags?)`, never `c.text`.**",
1155
1173
  " The regex filters (`regex_test`/`regex_match`/`regex_replace`/\u2026) are pattern-piped",
1156
- " PHP `preg_*`: the piped value is the PATTERN and must be delimiter-wrapped. A bare",
1157
- ' `c.text("^[^@\\s]+@...$")` is an invalid pattern that matches *nothing* for every',
1158
- " input, so a precondition on it silently rejects all values (valid ones included).",
1159
- ' `c.regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", "i")` wraps + escapes it for you (accepts a',
1160
- " JS `RegExp` too: `c.regex(/^\u2026$/i)`), and `withFilters` throws on a bare `c.text`",
1161
- " pattern pointing here \u2014 from ANY position in the chain, so a normalizer in front",
1162
- ' (`withFilters(c.text("^hi"), fl.trim(), fl.regex_test(...))`) is refused too; nothing',
1163
- " upstream adds the delimiters. `s.expect.to_match`'s `value` is the same PATTERN slot,",
1164
- " refused the same way; a `ref`/`inp` pattern is passed through untouched, either place.",
1165
- " Better still, prefer a native typed input",
1166
- " (`input.email({...})`) over hand-rolled validation.",
1174
+ " PHP `preg_*`: the piped value is the PATTERN and must be delimiter-wrapped, and the",
1175
+ ' ARGUMENT is the subject. A bare `c.text("^\u2026$")` is an invalid pattern that matches',
1176
+ " *nothing* for every input, so a precondition on it silently rejects all values.",
1177
+ ' `c.regex(body, "i")` wraps + escapes it for you (a JS `RegExp` too: `c.regex(/^\u2026$/i)`).',
1178
+ " Reversed \u2014 subject piped, pattern in the argument \u2014 reads correctly, type-checks, and",
1179
+ " answers false for EVERY input, so an `if (matches) reject` guard admits what it",
1180
+ " refuses. `withFilters` throws on a bare `c.text` pattern from ANY position in",
1181
+ " the chain (a normalizer in front of the regex filter is refused too; nothing upstream",
1182
+ " adds the delimiters) and on a pattern found in the subject slot; `s.expect.to_match`",
1183
+ " is the same PATTERN slot, refused both ways; a `ref`/`inp` pattern is passed through untouched.",
1184
+ " `export()` warns on a reversed pair in stored bytes. Better still:",
1185
+ " a native typed input (`input.email`) over hand-rolled validation.",
1167
1186
  "- **Compose a rule set as SIBLINGS, not a folded chain.** `and(...rules)` takes any",
1168
1187
  " number of terms and encodes flat; `rules.reduce((acc, r) => and(acc, r))` nests one",
1169
1188
  " container per rule, which costs quadratic bytes (512 terms: 394 KiB flat, 21 MiB",
@@ -1187,19 +1206,10 @@ function renderSections(m) {
1187
1206
  ' credential the runner has. As a file that triple is `{ "type": "token",',
1188
1207
  ' "instance_base_url": \u2026, "workspace_id": <n>, "meta_api_token": \u2026 }`. The older',
1189
1208
  " `$XANO_REFRESH_TOKEN` + `$XANO_CLIENT_ID` pair still works but ROTATES: single-use.",
1190
- "- **Event-driven objects fire on an EPHEMERAL, not in the sandbox.** A `task`",
1191
- " (scheduled), an `mcpServer`, and every trigger \u2014 `tableTrigger` included \u2014 run normally",
1192
- " on an ephemeral env, which is `deploy`'s DEFAULT destination. So test an event-driven",
1193
- " design (screen-on-insert, cron cleanup, MCP tool call) by deploying it and letting it",
1194
- " run.",
1195
- " \u26A0 Under `--dest sandbox` they import cleanly but their stacks NEVER execute, and there",
1196
- " is no way to fire one manually \u2014 an insert on a bound table does not run its",
1197
- " `tableTrigger`, and the design silently does nothing. Only synchronously-invoked objects",
1198
- " (queries, functions, and the agents an endpoint calls with `s.ai.agent.run`) run there.",
1199
- " If you must stay on the sandbox, verify the logic out of band: factor the body into a",
1200
- " `defineFunction` (or a callable `query`) and invoke it directly \u2014 a `tableTrigger` that",
1201
- " screens a row on insert should delegate to a function a `query` can also call via",
1202
- " `s.function.run`, and you assert against that.",
1209
+ "- **Event-driven objects fire on an EPHEMERAL.** A `task` (scheduled), an `mcpServer`,",
1210
+ " and every trigger \u2014 `tableTrigger` included \u2014 run normally on an ephemeral env, which",
1211
+ " is where `deploy` sends them. So test an event-driven design (screen-on-insert, cron",
1212
+ " cleanup, MCP tool call) by deploying it and letting it run.",
1203
1213
  '- **Zero-based numeric keys make `c.obj` a LIST.** `c.obj({ "0": "a" })` evaluates to',
1204
1214
  ' `["a"]`: a numeric key IS an index in the engine\'s data model, so keys that are exactly',
1205
1215
  " `0..n-1` come back as a list with HTTP 200 and no error. Write `c.array([...])` when you",
@@ -1252,7 +1262,7 @@ function renderSections(m) {
1252
1262
  ' - `verb`: `"GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD"` (required), UPPERCASE. Anything else \u2014 most often a lowercase `"post"` \u2014 makes `query()` THROW, because Xano does NOT reject it: it stores the verb as NULL, a null verb serves as GET, and the endpoint then answers on the wrong method while the one you meant 404s `Unable to locate request.`',
1253
1263
  " - `apiGroup`: an `apiGroup()` def handle (or its name) \u2014 binds by guid, stable across syncs. Raw numeric `apiGroupId?` is the escape hatch and wins if both given.",
1254
1264
  ' - `auth`: `false` (no auth) or an auth-table id; `responseType`: `"standard" | "stream"` (default `standard`) \u2014 any other spelling THROWS, since Xano stores an unrecognized one as NULL and a null buffers as `standard`, so a misspelled stream quietly does not stream.',
1255
- ' - `name` is the endpoint PATH within the group. A `{param}` segment is a URL PATH PARAM bound to the input of the same name, and segments chain: `name: "blog/{slug}/review/{review_id}"` + `input: { slug: input.text(), review_id: input.int() }`. Read it with `inp("slug")` like any other input. Every `{param}` MUST have a matching input or `query()` THROWS \u2014 Xano treats an unbound marker as inert route text, so the endpoint would answer on the path and see nothing. A `{param}` need NOT be a whole segment (`"blog/post-{slug}"` routes fine), but its type must fit one segment (no object/list/json/file/geo/vector); there are no wildcards or patterns. `required: true` is NOT demanded (the engine\'s editor leaves path inputs unmarked). Inputs absent from the path are ordinary query-string/body params. Name charset is ONLY `A-Za-z0-9_-/{}`, max 200: a `.` (`"export.zip"`) is NOT rejected by Xano \u2014 it stores an EMPTY name that deploys clean then 404s forever, so `query()` THROWS. Use `"export_zip"` and set the extension in the response headers.',
1265
+ ' - `name` is the endpoint PATH within the group. A `{param}` segment is a URL PATH PARAM bound to the input of the same name, and segments chain: `name: "blog/{slug}/review/{review_id}"` + `input: { slug: input.text(), review_id: input.int() }`. Read it with `inp("slug")` like any other input. Every `{param}` MUST have a matching input or `query()` THROWS \u2014 Xano treats an unbound marker as inert route text, so the endpoint would answer on the path and see nothing. A `{param}` need NOT be a whole segment (`"blog/post-{slug}"` routes fine), but its type must fit one segment (no object/list/json/file/geo/vector); there are no wildcards or patterns. `required: true` is NOT demanded (the engine\'s editor leaves path inputs unmarked). The CONVERSE is warned, not enforced: an input a `GET`/`DELETE`/`HEAD` looks ONE ROW up by (`s.db.get`/`get_by_id`/`has`/by-field edit/patch/delete) belongs in the path \u2014 `export()` warns `query.path-segment-candidate`. It still serves `?blog_id=1`, but the route is not addressable and `getPath()` types STATIC, so a caller cannot pass the value positionally. A segment is any value naming WHICH resource is wanted, not just an id (`"shop/{country}"`). An input that NARROWS A LIST (`s.db.query`) stays a query-string param. Inputs absent from the path are ordinary query-string/body params. Name charset is ONLY `A-Za-z0-9_-/{}`, max 200: a `.` (`"export.zip"`) is NOT rejected by Xano \u2014 it stores an EMPTY name that deploys clean then 404s forever, so `query()` THROWS. Use `"export_zip"` and set the extension in the response headers.',
1256
1266
  ' - **Client recipe:** `q.getPath({ params: { slug: "hello" } })` \u2192 `/api:<canonical>/blog/hello` \u2014 never interpolate by hand. `getPath` percent-encodes each value (so `?`/`#`/spaces stay in their segment) and throws on what encoding cannot contain: a `/`, and a value that IS `.`/`..` (a URL parser drops those before routing \u2014 `%2e` counts \u2014 addressing a different endpoint). The keys are typed from the literal `name`, so a typo is a compile error. The HANDLE\'s `q.toSearchParams(input)` drops path params for a GET; the free `query.toSearchParams(input)` has no view of the route and keeps every key.',
1257
1267
  "- `apiGroup({ name, guid?, canonical?, description?, docs?, swagger?, apiGroupEnabled?, documentation?, cors? })` \u2014 a query container; register it and bind queries to it via their `apiGroup`.",
1258
1268
  " - `cors?`: `{ mode?, allowOrigins?: string[], allowHeaders?: string[], allowCredentials?, maxAge?, allowMethods?: { get?, post?, put?, patch?, delete?, head? } }`.",
@@ -1260,7 +1270,7 @@ function renderSections(m) {
1260
1270
  ' - \u26A0 Under `"custom"`, `allowOrigins` is matched as EXACT strings (scheme+host+port, no wildcard or subdomain expansion) and `"*"` is compared as a literal origin \u2014 it matches NOTHING. An unmatched origin gets no `access-control-*` headers at all, so the call fails in the browser on a missing `access-control-allow-origin` while export, deploy and the preflight all look fine. Name each origin, or use `mode: "default"` for any-origin. `allowMethods` gates the REAL response too: a verb left off gets no CORS headers back even though its preflight passes. Export warns on an empty origin list, a `"*"` entry, and a policy with no method enabled.',
1261
1271
  '- `defineFunction`/`query`/`apiGroup` above cover the queries+tables core; the four below are the "reach past that" primitives (tasks, workflow tests, middleware, tools). Agents and MCP servers are the same family and live in `llms/kinds-agent-mcp.md`. Same envelope conventions (`guid?`, `description?`, `docs?`, `tags?`, `history?`) unless noted.',
1262
1272
  "- `task({ name, guid?, description?, docs?, datasource?, active?, tags?, history?, schedule?, stack?, middleware? })` \u2014 a scheduled background job (function-like `stack`, no `input`/`response`).",
1263
- ' - `schedule?`: a `ScheduleDef[]` (NOT a single object) \u2014 `{ startsOn, freq?, repeatEnabled?, endsOn?, endsEnabled? }`. `startsOn`/`endsOn` are **timestamp strings** validated at encode time \u2014 `"2026-01-01T00:00:00Z"`, or the space-separated `"2026-01-01 00:00:00+0000"` a pulled workspace carries \u2014 never epoch numbers, and never zoneless; `freq` is the repeat interval **in seconds** (default `86400` = daily); `endsOn` present \u21D2 the schedule has an end. `endsEnabled?` defaults to that and is recovery-only \u2014 state it to reproduce a stored schedule that remembers an end date with the gate OFF. Fires on an ephemeral; does NOT fire in the sandbox (see Gotchas).',
1273
+ ' - `schedule?`: a `ScheduleDef[]` (NOT a single object) \u2014 `{ startsOn, freq?, repeatEnabled?, endsOn?, endsEnabled? }`. `startsOn`/`endsOn` are **timestamp strings** validated at encode time \u2014 `"2026-01-01T00:00:00Z"`, or the space-separated `"2026-01-01 00:00:00+0000"` a pulled workspace carries \u2014 never epoch numbers, and never zoneless; `freq` is the repeat interval **in seconds** (default `86400` = daily); `endsOn` present \u21D2 the schedule has an end. `endsEnabled?` defaults to that and is recovery-only \u2014 state it to reproduce a stored schedule that remembers an end date with the gate OFF. Fires on an ephemeral (see Gotchas).',
1264
1274
  '- `workflowTest({ name, guid?, description?, docs?, datasource?, active?, tags?, stack? })` \u2014 an end-to-end test. NO `input`/`response`: `.call` something with an `as`, then assert on that var \u2014 `s.function.call({ fn, input, as: "r" })`, `s.expect.to_equal({ expr: ref("r"), value: c.int(42) })`. `s.expect.*` belongs here \u2014 it is not inert elsewhere (a failure 500s the request), so treat one in a query/function/task as a mistake to remove. `active?` defaults `true`; chain tests with `s.workflow_test.call({ workflowTest: <def handle> })`.',
1265
1275
  ' - `datasource?`: **the trap.** Default `""` is an EMPTY datasource (recommended), not "no datasource". Any non-empty name makes the engine CLONE it before EVERY run \u2014 against production-sized data, slow enough to fail the run. `"live"` warns at compile time; other names don\'t.',
1266
1276
  "- `middleware({ name, guid?, description?, docs?, resultStrategy?, exceptionPolicy?, tags?, history?, input?, stack?, response?, responseShape?, tests? })` \u2014 a pre/post interceptor (function-like `stack`); attach it via a host's `middleware: { pre, post }`. \u26A0 `input` ENCODES but an ATTACHED middleware never has it bound \u2014 the host request binds its own inputs, so `inp()` inside pre/post fails at runtime with `Unable to locate input` and a declared default does not stand in (`export()` warns). Read the request body with `s.util.get_all_input` instead; it yields a `{ type, vars }` envelope. `s.middleware.call` is the one path that DOES bind the declared map.",
@@ -1327,7 +1337,7 @@ function renderSections(m) {
1327
1337
  "- `name` takes letters, digits, and `/ _ - { } . ` or a space; anything else is an export error."
1328
1338
  ];
1329
1339
  const kindsAgentMcp = [
1330
- "- `mcpServer({ name, guid?, description?, instructions?, docs?, enabled?, canonical?, spec?, tags?, history?, tools?, llm?, output? })` \u2014 an MCP toolset. `llm?`/`output?` are the same blocks `agent()` takes and are usually absent: an MCP server and an agent are ONE stored object distinguished by `type`. Returns a handle with `getPath()`/`getUrl(baseUrl)` for the Streamable-HTTP endpoint \u2014 `getUrl` is NOT idempotent, a `baseUrl` already carrying an `/x2/mcp/<\u2026>/stream` path THROWS, so resolve ONCE from the instance base URL. Fires on an ephemeral; not in sandbox.",
1340
+ "- `mcpServer({ name, guid?, description?, instructions?, docs?, enabled?, canonical?, spec?, tags?, history?, tools?, llm?, output? })` \u2014 an MCP toolset. `llm?`/`output?` are the same blocks `agent()` takes and are usually absent: an MCP server and an agent are ONE stored object distinguished by `type`. Returns a handle with `getPath()`/`getUrl(baseUrl)` for the Streamable-HTTP endpoint \u2014 `getUrl` is NOT idempotent, a `baseUrl` already carrying an `/x2/mcp/<\u2026>/stream` path THROWS, so resolve ONCE from the instance base URL. Fires on an ephemeral.",
1331
1341
  " - `tools?`: a `ToolsetToolEntry[]`. Pass the `tool()` HANDLES directly (`tools: [saveNote]`), like every other collection in the SDK; use the `{ tool, enabled?, auth? }` wrapper only when a tool needs `enabled: false` or per-tool `auth`. `auth` names an auth **table** (a `table({ auth: true })` handle or its name) \u2014 Xano's ONLY MCP auth surface (per-tool; there is no server-level gate). An entry that names no tool (no handle, no `id`) THROWS at export rather than emitting the `id: 0` null reference it used to; a deliberate raw `id: 0` warns and is carried through, so a pulled workspace still round-trips.",
1332
1342
  "- `agent({ name, guid?, description?, docs?, enabled?, canonical?, tags?, history?, llm, tools?, output? })` \u2014 an LLM orchestrator. No top-level `instructions`/`prompt`/`spec` \u2014 the prompt lives under `llm`. Invoke from a stack with `s.ai.agent.run({ agent, args })`.",
1333
1343
  ' - `llm` (REQUIRED): typed provider settings, a discriminated union on `type` (`"xano-free" | "anthropic" | "openai" | "google-genai"`). Shared fields: `systemPrompt?`, `maxSteps?` (default `5`), and `prompt?` XOR `messages?` (genuinely exclusive: both is a type error and throws \u2014 the engine stores ONE `prompt_type`, so one would be dropped); plus provider fields (`apiKey?`, `model?`, `temperature?`, `reasoningEffort?`, \u2026). String fields accept Twig placeholders \u2014 `{{ $args.x }}` for run inputs (the `args` of `s.ai.agent.run`), `{{ $env.NAME }}` for env vars.',
@@ -1381,7 +1391,7 @@ function renderSections(m) {
1381
1391
  " - **Tenant instances (isolated DB):** a tenant's realtime objects live in the TENANT's database, so BOTH halves of a client must name the tenant.",
1382
1392
  " - Socket: `server.getUrl(base, { tenant })` \u2192 `/ws/<tenant>:<canonical>`. \u26A0 A bare canonical on a tenant host resolves against the INSTANCE workspace instead.",
1383
1393
  " - That colon form is PECULIAR TO THE SOCKET. Every other tenant URL gives the tenant its OWN segment \u2014 the HTTP half of the same client is `https://<host>/tenant/<tenant>/api:<canonical>/\u2026`. NO request header is required for either.",
1384
- " - Because the shapes differ, `getUrl` TRANSLATES a tenant base URL instead of concatenating: pass the `https://<host>/tenant/<name>` that `sandbox details` prints (and that deploy injects as `window.XANO_HOST`) and the tenant is LIFTED into the socket form. So `getUrl(window.XANO_HOST)` needs no `{ tenant }`, and a CONFLICTING `{ tenant }` alongside it throws.",
1394
+ " - Because the shapes differ, `getUrl` TRANSLATES a tenant base URL instead of concatenating: pass the `https://<host>/tenant/<name>` that `xanots status` prints (and that deploy injects as `window.XANO_HOST`) and the tenant is LIFTED into the socket form. So `getUrl(window.XANO_HOST)` needs no `{ tenant }`, and a CONFLICTING `{ tenant }` alongside it throws.",
1385
1395
  " - \u26A0 `getUrl`/`socketUrl` are NOT idempotent \u2014 a `baseUrl` that already carries a `/ws/<\u2026>` path (an earlier result of either) THROWS. Resolve ONCE from the http(s) base; pass that result to `new WebSocket`, never back in as a base.",
1386
1396
  " - Still pass `{ tenant }` explicitly for a tenant on its OWN DOMAIN \u2014 the hostname carries it for HTTP, but there is nothing in the URL for the socket to lift.",
1387
1397
  " - \u26A0 Tokens are tenant-scoped (audience `<tenant>:<license>`, not the bare license), so one minted through the instance workspace is REJECTED by a tenant's realtime server \u2014 authenticate and dial through the same tenant.",
@@ -1577,12 +1587,13 @@ function renderSections(m) {
1577
1587
  "which the DB applies atomically. Reserve the pipeline form for low-contention counters",
1578
1588
  "where a rare lost update is acceptable.",
1579
1589
  "\u26A0 `direct_query` needs the table's PHYSICAL Postgres name, which the typed surface",
1580
- "does NOT expose: the engine derives a physical name from workspace + table ids (of the",
1581
- "form `x<workspace_id>_<table_id>`, e.g. `x6_203970`), ids assigned at import \u2014 not knowable",
1582
- "from a `table()` def (identity is a name + guid, not the numeric id), and `sql_name`",
1583
- "persists empty. So the safe counter drops out of the typed surface: hardcode",
1584
- "the physical name after inspecting the deployed table. A typed atomic path needs an",
1585
- "engine change.",
1590
+ "does NOT expose: the engine derives it from ids assigned at import \u2014 not knowable from a",
1591
+ "`table()` def (identity is a name + guid, not the numeric id) \u2014 and `sql_name` persists",
1592
+ "empty. The derived name is also NOT STABLE: a deploy is a full replace, so every table is",
1593
+ "created afresh and the id in its name moves each time, on the same unchanged project. So",
1594
+ "the safe counter drops out of the typed surface: resolve the physical name from",
1595
+ "`information_schema` inside the request that uses it, and never store, cache or hardcode",
1596
+ "one. A typed atomic path needs an engine change.",
1586
1597
  ""
1587
1598
  );
1588
1599
  const argFilters = m.filters.filter((fl) => fl.args?.length);
@@ -1653,7 +1664,7 @@ function renderSections(m) {
1653
1664
  "",
1654
1665
  "- `s.db.get({ table, fieldName?, fieldValue, lock?, output?, as? })` \u2014 one row by field match; `output` restricts returned columns (and overrides column visibility \u2014 it can pull `internal` columns like a password hash).",
1655
1666
  "- `s.db.get_by_id({ table, id, output?, addon?, tableAlias?, as? })` \u2014 get by primary key. Takes `id`, NOT `fieldName`/`fieldValue`; binds the row or `null` for an id that names no row. Both spellings are live in pulled workspaces.",
1656
- ' - \u26A0 `id` is validated `>= 1`, so the `0` sentinel an optional `f.tableRef` stores fails the request with HTTP 400 `Value is less than the minimum value of 1` \u2014 it does NOT bind `null`. The throw is not scoped to the lookup: inside a `foreach` it kills the whole request, so one unset FK loses every other row\'s work. Read a nullable FK with the field-match form, which binds `null` on `0` and lets the loop finish: `s.db.get({ table, fieldName: "id", fieldValue: ref("row.fk"), as })`.',
1667
+ ' - \u26A0 `id` is validated `>= 1`, so the `0` sentinel an optional `f.tableRef` stores fails the request with HTTP 400 `Value is less than the minimum value of 1` \u2014 it does NOT bind `null`. The throw is not scoped to the lookup: inside a `foreach` it kills the whole request, so one unset FK loses every other row\'s work. Read a nullable FK with the field-match form, which binds `null` on `0` and lets the loop finish: `s.db.get({ table, fieldName: "id", fieldValue: ref("row.fk"), as })`. `export()` warns when the `id` is statically a `0` \u2014 the literal `c.int(0)`, an `inp()` whose declared input default is `0`, or a `ref()` to a column declared `default: 0` \u2014 and `--strict` fails the build.',
1657
1668
  "- `s.db.has({ table, fieldName?, fieldValue, as? })` \u2014 existence test.",
1658
1669
  "- `s.db.del({ table, fieldName?, fieldValue, as? })` \u2014 delete by field match.",
1659
1670
  "- `s.db.add({ table, row?, data?, output?, as? })` \u2014 insert; `row` is a partial keyed by column.",
@@ -1695,6 +1706,8 @@ function renderSections(m) {
1695
1706
  ' - `distinct` \u2014 `"auto"` (default) | `"yes"` | `"no"`, riding `context.return.<list|stream>.distinct`.',
1696
1707
  "- `s.db.truncate({ table, reset?, as? })` \xB7 `s.db.schema({ table, path, as? })`.",
1697
1708
  '- `s.db.direct_query({ sql, responseType?, args?, parser?, as? })` \u2014 `sql` is a **raw string** (not a `Value`); binds go in `args: Value[]`. `parser: "template_engine"` renders the body as a template first \u2014 how a query interpolates a column or table name a bound arg cannot carry; omit it for the default.',
1709
+ " - Template placeholders are Twig over the request scope: `{{ $input.name }}` for an input, `{{ $var.name }}` for a stack variable. \u26A0 A BARE `{{ name }}` renders as the empty string \u2014 HTTP 200, no error, a query that silently ran with a blank where the value belonged. A bound `?` arg carries a VALUE without the template at all.",
1710
+ " - \u26A0 A table's PHYSICAL name is **not stable across deploys**. A deploy is a full replace, so every table is created afresh and the id in its name moves every time \u2014 the same unchanged project redeployed three times gave one table three different names. Never store, cache, hardcode or fixture one: resolve it from `information_schema` inside the same request that uses it.",
1698
1711
  "- `s.db.external.<engine>.direct_query({ sql, connectionString, responseType?, args?, parser?, as? })` \u2014 same shape against an EXTERNAL database; `<engine>` is `postgres`/`mysql`/`mssql`/`oracle`/`snowflake`. `connectionString` is a `Value` \u2014 reach for `env(...)`, not a literal \u2014 stored as `context.connection_string_flex`. A bare string stores the older `context.connection_string` instead (an env-var name unless it looks like a URL); each form round-trips as itself.",
1699
1712
  "- `s.db.transaction({ body, as? })` \u2014 run a `Statement[]` atomically. `as` binds whatever the block returned.",
1700
1713
  "- `s.db.bulk.add({ table, items, allowIdField?, as? })` / `s.db.bulk.update` / `s.db.bulk.patch` \u2014 `items` is an array `Value`.",
@@ -1764,11 +1777,11 @@ function renderSections(m) {
1764
1777
  "- `xanots lock rename <kind> <old> <new>` \u2014 `kind` is the payload key (or `table`/`api_group`).",
1765
1778
  " Run it after renaming in code; the next export emits the original guid under the new name.",
1766
1779
  "- `xanots lock prune <entry-file> [keys\u2026] --yes` \u2014 drops orphaned entries. Finding orphans",
1767
- " RUNS the entry's module scope (env assertions included); `--no-verify --yes <kind:name>\u2026`",
1780
+ " RUNS the entry's module scope (env assertions included); `--identity-only --yes <kind:name>\u2026`",
1768
1781
  " prunes named keys with no evaluation and no orphan check.",
1769
- "- `xanots lock adopt <live-bundle.json> [--yes]` \u2014 seed the lock from an engine",
1782
+ "- `xanots lock import <live-bundle.json> [--yes]` \u2014 seed the lock from an engine",
1770
1783
  " packageExport when taking over an existing workspace.",
1771
- "- Every lock subcommand accepts `--lock=<path>`. `rename`/`adopt` take no entry file, so",
1784
+ "- Every lock subcommand accepts `--lock=<path>`. `rename`/`import` take no entry file, so",
1772
1785
  " from outside the lock's directory pass `--lock` (or `--entry=<entry-file>` to derive it).",
1773
1786
  "- Programmatic use: call `seedLockOverrides(readLockFile(path))` BEFORE importing any def",
1774
1787
  " module \u2014 references bake guids at import time, so late seeding is a silent no-op",
@@ -1776,7 +1789,7 @@ function renderSections(m) {
1776
1789
  "",
1777
1790
  "What writes the lock: `export`/`deploy` of an ENTRY FILE update it via the shared compile",
1778
1791
  "step \u2014 only when a lock exists or `--lock` is passed. Nothing from a DEPLOY is written",
1779
- "back beyond that (an ephemeral/sandbox is a separate workspace, so its identities must",
1792
+ "back beyond that (an ephemeral is a separate workspace, so its identities must",
1780
1793
  "not pollute yours). The one write-back is `release --replace`, which mints fresh",
1781
1794
  "identities in the workspace the lock describes: it re-pins the lock from the rebuilt",
1782
1795
  "workspace, because otherwise the next release matches nothing and duplicates every",
@@ -2031,7 +2044,7 @@ function renderDocs(m) {
2031
2044
  {
2032
2045
  path: "llms/lock.md",
2033
2046
  title: "Lock file",
2034
- readWhen: "Read when a `xano.lock` exists or should \u2014 renaming/pruning/adopting identities, seeding the lock programmatically, or asking which commands write it.",
2047
+ readWhen: "Read when a `xano.lock` exists or should \u2014 renaming/pruning/importing identities, seeding the lock programmatically, or asking which commands write it.",
2035
2048
  body: s.lock,
2036
2049
  dropLeadingHeading: true
2037
2050
  },
@@ -0,0 +1,12 @@
1
+ import {
2
+ readLockFile,
3
+ writeLockFile
4
+ } from "./chunk-XEOX6AM7.js";
5
+ import "./chunk-GNPVYOPB.js";
6
+ import "./chunk-VAF6A3YD.js";
7
+ import "./chunk-OHX6MIUZ.js";
8
+ export {
9
+ readLockFile,
10
+ writeLockFile
11
+ };
12
+ //# sourceMappingURL=io-P2H75UV2.js.map
@@ -2,11 +2,11 @@ import {
2
2
  compareToLive,
3
3
  diffAgainstLive,
4
4
  sharedSchemaChanges
5
- } from "./chunk-5HCDZ2XN.js";
6
- import "./chunk-ZOYMZZ3S.js";
5
+ } from "./chunk-DBFU47BJ.js";
6
+ import "./chunk-3INK4Y4E.js";
7
7
  export {
8
8
  compareToLive,
9
9
  diffAgainstLive,
10
10
  sharedSchemaChanges
11
11
  };
12
- //# sourceMappingURL=live-diff-QK4KC2FJ.js.map
12
+ //# sourceMappingURL=live-diff-IXKBVG4K.js.map
@@ -17,7 +17,8 @@ import {
17
17
  resolvePayloadKey,
18
18
  serializeLock,
19
19
  validateLockModel
20
- } from "./chunk-7JDT4PBU.js";
20
+ } from "./chunk-VAF6A3YD.js";
21
+ import "./chunk-OHX6MIUZ.js";
21
22
  export {
22
23
  CANONICAL_PAYLOAD_KEYS,
23
24
  LOCK_PAYLOAD_KEYS,
@@ -38,4 +39,4 @@ export {
38
39
  serializeLock,
39
40
  validateLockModel
40
41
  };
41
- //# sourceMappingURL=lock-GFXD6G2E.js.map
42
+ //# sourceMappingURL=lock-46FWYE4D.js.map
@@ -1,24 +1,24 @@
1
1
  import {
2
2
  loadDefault
3
- } from "./chunk-K3YSQDXJ.js";
3
+ } from "./chunk-RCT7UX7B.js";
4
+ import "./chunk-CTD5ZCV6.js";
5
+ import {
6
+ readLockFile,
7
+ writeLockFile
8
+ } from "./chunk-XEOX6AM7.js";
4
9
  import {
5
10
  UsageError,
6
11
  missingArgument,
7
12
  unknownSubcommand
8
- } from "./chunk-JQLR64UC.js";
9
- import {
10
- readLockFile,
11
- writeLockFile
12
- } from "./chunk-OYMR5AMJ.js";
13
+ } from "./chunk-F6CYJ7TN.js";
13
14
  import "./chunk-GNPVYOPB.js";
14
15
  import "./chunk-EZG76F7R.js";
15
16
  import {
16
17
  Xano
17
- } from "./chunk-UDL46O35.js";
18
- import "./chunk-OKNSR7MT.js";
19
- import "./chunk-QONOJO45.js";
18
+ } from "./chunk-OWGCOGKK.js";
19
+ import "./chunk-DIA7CT7J.js";
20
+ import "./chunk-QK7ZQJLP.js";
20
21
  import "./chunk-WHOJWOSV.js";
21
- import "./chunk-5XZ744TS.js";
22
22
  import {
23
23
  adoptFromBundle,
24
24
  createLockContext,
@@ -27,7 +27,9 @@ import {
27
27
  resetLockOverrides,
28
28
  resolvePayloadKey,
29
29
  seedLockOverrides
30
- } from "./chunk-7JDT4PBU.js";
30
+ } from "./chunk-VAF6A3YD.js";
31
+ import "./chunk-5XZ744TS.js";
32
+ import "./chunk-OHX6MIUZ.js";
31
33
 
32
34
  // src/emit/lock-commands.ts
33
35
  import { existsSync, readFileSync } from "fs";
@@ -39,7 +41,7 @@ async function runLockCommand(args) {
39
41
  return lockRename(args);
40
42
  case "prune":
41
43
  return lockPrune(args);
42
- case "adopt":
44
+ case "import":
43
45
  return lockAdopt(args);
44
46
  default:
45
47
  throw unknownSubcommand("lock", sub);
@@ -101,7 +103,7 @@ async function observedLockKeys(entryFile, lock) {
101
103
  throw new Error(
102
104
  `lock prune evaluated "${entryFile}" to find out which lock entries are still live, and loading it failed:
103
105
  ${reason}
104
- Finding orphans requires running your workspace source \u2014 anything the entry file does at module scope (asserting env vars, reading config) runs too. To drop entries you can name yourself, no evaluation is needed: \`xanots lock prune --no-verify --yes <kind:name>\u2026\` with \`--lock=<path>\` or \`--entry=<path>\` to locate the lock.`,
106
+ Finding orphans requires running your workspace source \u2014 anything the entry file does at module scope (asserting env vars, reading config) runs too. To drop entries you can name yourself, no evaluation is needed: \`xanots lock prune --identity-only --yes <kind:name>\u2026\` with \`--lock=<path>\` or \`--entry=<path>\` to locate the lock.`,
105
107
  { cause: err }
106
108
  );
107
109
  }
@@ -114,7 +116,7 @@ Finding orphans requires running your workspace source \u2014 anything the entry
114
116
  }
115
117
  async function lockPrune(args) {
116
118
  const [, ...rest] = args.positionals;
117
- if (args.noVerify) return lockPruneNamed(args, rest);
119
+ if (args.identityOnly) return lockPruneNamed(args, rest);
118
120
  const [entryFile, ...keys] = rest;
119
121
  if (!entryFile) {
120
122
  throw missingArgument("entry-file", { command: "lock", subcommand: "prune" });
@@ -146,7 +148,7 @@ async function lockPrune(args) {
146
148
  function lockPruneNamed(args, keys) {
147
149
  if (keys.length === 0) {
148
150
  throw new UsageError(
149
- "`lock prune --no-verify` prunes the entries you name, so it needs at least one key (`table:users`). Without keys there is nothing to prune, because finding orphans is exactly the step --no-verify skips \u2014 drop the flag and pass the entry file to do that.",
151
+ "`lock prune --identity-only` prunes the entries you name, so it needs at least one key (`table:users`). Without keys there is nothing to prune, because finding orphans is exactly the step --identity-only skips \u2014 drop the flag and pass the entry file to do that.",
150
152
  { helpFor: { command: "lock", subcommand: "prune" } }
151
153
  );
152
154
  }
@@ -158,7 +160,7 @@ function lockPruneNamed(args, keys) {
158
160
  }
159
161
  }
160
162
  process.stderr.write(
161
- `xanots: --no-verify \u2014 nothing was evaluated, so nothing confirms ${keys.length === 1 ? "this key is" : "these keys are"} orphaned. A key the workspace still exports gets a NEW identity at the next export.
163
+ `xanots: --identity-only \u2014 nothing was evaluated, so nothing confirms ${keys.length === 1 ? "this key is" : "these keys are"} orphaned. A key the workspace still exports gets a NEW identity at the next export.
162
164
  `
163
165
  );
164
166
  applyPrune(args, lockPath, lock, keys);
@@ -184,9 +186,9 @@ ${canonicalNote}`);
184
186
  function lockAdopt(args) {
185
187
  const [, bundlePath] = args.positionals;
186
188
  if (!bundlePath) {
187
- throw missingArgument("bundle.json", { command: "lock", subcommand: "adopt" });
189
+ throw missingArgument("bundle.json", { command: "lock", subcommand: "import" });
188
190
  }
189
- const lockPath = commandLockPath(args, "adopt");
191
+ const lockPath = commandLockPath(args, "import");
190
192
  const lock = existsSync(lockPath) ? readLockFile(lockPath) : emptyLock();
191
193
  let bundle;
192
194
  try {
@@ -206,11 +208,11 @@ function lockAdopt(args) {
206
208
  process.stdout.write(`Adoption would overwrite ${changed.length} existing lock entr(y/ies):
207
209
  ${lines}
208
210
  `);
209
- throw new Error("lock adopt overwrites pinned identities \u2014 re-run with --yes to apply.");
211
+ throw new Error("lock import overwrites pinned identities \u2014 re-run with --yes to apply.");
210
212
  }
211
213
  if (vaultCount > 0) {
212
214
  process.stderr.write(
213
- `xanots: WARNING \u2014 ${bundlePath} contains ${vaultCount} vault entr(y/ies) (secrets). Do NOT commit the bundle file; delete it once adoption is done.
215
+ `xanots: WARNING \u2014 ${bundlePath} contains ${vaultCount} vault entr(y/ies) (secrets). Do NOT commit the bundle file; delete it once the import is done.
214
216
  `
215
217
  );
216
218
  }
@@ -229,4 +231,4 @@ ${lines}
229
231
  export {
230
232
  runLockCommand
231
233
  };
232
- //# sourceMappingURL=lock-commands-ET7NIOLH.js.map
234
+ //# sourceMappingURL=lock-commands-ZZKZ4LZJ.js.map
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-W5NOKYEG.js";
6
6
  import {
7
7
  promptLine
8
- } from "./chunk-4PTSTDIG.js";
8
+ } from "./chunk-AIZKXUNP.js";
9
9
  import {
10
10
  envMetaCredentialVarsSet
11
- } from "./chunk-3QW5NFZJ.js";
11
+ } from "./chunk-QKM4U5UK.js";
12
12
  import {
13
13
  CALLBACK_PATH,
14
14
  DEFAULT_PORT,
@@ -23,10 +23,10 @@ import {
23
23
  resolveAuthHost,
24
24
  resolveScope,
25
25
  writeCredential
26
- } from "./chunk-BKDJN76G.js";
26
+ } from "./chunk-FE5I6S6N.js";
27
27
  import {
28
28
  UsageError
29
- } from "./chunk-JQLR64UC.js";
29
+ } from "./chunk-F6CYJ7TN.js";
30
30
  import "./chunk-GNPVYOPB.js";
31
31
  import {
32
32
  blank,
@@ -37,7 +37,7 @@ import {
37
37
  success,
38
38
  warn
39
39
  } from "./chunk-EZG76F7R.js";
40
- import "./chunk-OKNSR7MT.js";
40
+ import "./chunk-DIA7CT7J.js";
41
41
 
42
42
  // src/emit/login-command.ts
43
43
  import * as client from "openid-client";
@@ -177,7 +177,7 @@ async function attemptLogin(p) {
177
177
  `Could not determine the instance from the issued token (no readable \`aud\` claim). This is unexpected \u2014 please report it.`
178
178
  );
179
179
  }
180
- const { resolveScopedWorkspaceId } = await import("./workspace-K72NP7SX.js");
180
+ const { resolveScopedWorkspaceId } = await import("./workspace-2COHDBM3.js");
181
181
  detail("Resolving the workspace this token is scoped to\u2026");
182
182
  const workspaceId = await resolveScopedWorkspaceId({
183
183
  access_token: tokens.access_token,
@@ -267,4 +267,4 @@ async function acquireByPaste(p) {
267
267
  export {
268
268
  runLoginCommand
269
269
  };
270
- //# sourceMappingURL=login-command-DFCURZLC.js.map
270
+ //# sourceMappingURL=login-command-Z6CHTA57.js.map