@xanots/sdk 0.0.13 → 0.0.14

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 (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/{agent-file-refresh-GQWAAOBV.js → agent-file-refresh-2LZ2YRFZ.js} +3 -3
  4. package/dist/bin.js +2 -2
  5. package/dist/{chunk-2AY3PKF4.js → chunk-CII2TOE4.js} +2 -2
  6. package/dist/{chunk-2VTJSI6X.js → chunk-F3MRG7NI.js} +2 -2
  7. package/dist/{chunk-RLI6XD4O.js → chunk-LISUHLWI.js} +9 -9
  8. package/dist/{chunk-S3DOJOW4.js → chunk-ORZINVGQ.js} +4 -4
  9. package/dist/{chunk-SS2V2QOG.js → chunk-UQMMFW32.js} +4 -4
  10. package/dist/{chunk-OONT4ZL4.js → chunk-UXPDQFG2.js} +2 -2
  11. package/dist/{chunk-VK26K7AY.js → chunk-XBIFQWZM.js} +2 -2
  12. package/dist/cli.js +1 -1
  13. package/dist/{codegen-command-ZDHMGFWZ.js → codegen-command-5A6FVKUA.js} +5 -5
  14. package/dist/{deploy-command-CGVKRWUE.js → deploy-command-VBB7JYXH.js} +3 -3
  15. package/dist/{init-command-DNDONP3O.js → init-command-SIJUH2DD.js} +4 -4
  16. package/dist/internal.js +357 -259
  17. package/dist/{lock-commands-6U7UIJGR.js → lock-commands-4TX3BONI.js} +2 -2
  18. package/dist/node.js +1 -1
  19. package/dist/{preflight-command-UYE7SUQV.js → preflight-command-MTYGPNVT.js} +3 -3
  20. package/dist/{release-command-CMMYT6XK.js → release-command-NXCPQDFF.js} +5 -5
  21. package/dist/{upgrade-command-A75DOIUH.js → upgrade-command-GSH3M3N6.js} +5 -5
  22. package/dist/{workspace-command-YELP47SJ.js → workspace-command-XUOPLVQ4.js} +5 -5
  23. package/guides/README.md +1 -1
  24. package/guides/coverage.md +3 -2
  25. package/llms/client.md +44 -0
  26. package/llms/errors.md +33 -0
  27. package/llms/fields.md +26 -1
  28. package/llms/filters.md +4 -8
  29. package/llms/kinds-agent-mcp.md +1 -0
  30. package/llms/kinds-core.md +16 -2
  31. package/llms/kinds-knowledge.md +1 -1
  32. package/llms/kinds-realtime.md +1 -1
  33. package/llms/lambda.md +1 -1
  34. package/llms/legacy.md +1 -1
  35. package/llms/object-kinds.md +2 -2
  36. package/llms/statements-calls.md +25 -4
  37. package/llms/statements-catalog.md +1 -1
  38. package/llms/statements-runtime.md +20 -5
  39. package/llms/tests.md +2 -2
  40. package/llms/triggers.md +14 -2
  41. package/llms/values.md +2 -2
  42. package/llms-full.txt +359 -267
  43. package/llms.txt +163 -234
  44. package/manifest.json +4 -4
  45. package/package.json +1 -1
package/llms/triggers.md CHANGED
@@ -15,8 +15,20 @@ realtimeChannelTrigger, mcpServerTrigger, agentTrigger, workspaceTrigger,
15
15
  errorTrigger}({ name, guid?, description?, active?, tags?, ... })`.
16
16
 
17
17
  - `tableTrigger({ name, table?, datasources?, actions?: {insert?,update?,delete?,truncate?}, stack })` — database/table trigger. `t.new` / `t.old` are the row **after** / **before** the change; `t.action` (`insert|update|delete|truncate`), `t.datasource`. Bind `table` to a `table()` handle and `t.new("col")` / `t.old("col")` are typed to that row (misspelled column = compile error). Nullability follows the enabled actions: insert → `old` is null, delete → `new` is null, update → both, truncate → neither. Config-only (no response).
18
- - `realtimeServerTrigger({ name, realtimeServer, actions?: {connect?,disconnect?}, stack?, response?, responseShape? })` — realtime SERVER lifecycle (a client connecting to / disconnecting from the server, not a message). Inputs: `t.action` (`connect|disconnect`), `t.realtime_server`, `t.client`. Bind `realtimeServer` to a `realtimeServer()` handle (or its name). `connect` GATES the connection — a denial sends an `error` and CLOSES the socket with code 4401 before it is ever ready, so it is a real front door, not an observer; same return shape as a channel `join` below (EMPTY/FALSY DENIES — INCLUDING a gating trigger with NO `response`, which returns nothing and so refuses every client). A CRASH DENIES too — a gate that cannot answer must not admit. Both failure modes lock the door, so plan for a self-inflicted LOCKOUT (an unguarded drill into a null `db.get` raises → everyone refused), not a breach. Gating is OPT-IN: a server with no `connect` trigger accepts every connection. `disconnect` is OBSERVATIONAL (return ignored, throws swallowed — cleanup must always complete). Both are SERVER-scoped, so `s.realtime.get_session` works but carries no channel path and no bound params.
19
- - `realtimeChannelTrigger({ name, channel, actions?: {join?,leave?,deliver?}, stack?, response?, responseShape? })` realtime CHANNEL lifecycle. Inputs: `t.action` (`join|leave|deliver`), `t.channel`, `t.payload`, `t.client`. Bind `channel` to a `realtimeChannel()` handle — a bare path is NOT accepted (it is unique only within its server). The three actions have DIFFERENT postures, and the posture decides what the stack should return: `join` GATES the join (it runs BEFORE the client becomes a member, so a denial means it never sees a fan-out) — return `{ allowed: c.bool(true) }` (optional `reason` reaches the client) or any truthy value to admit, and an EMPTY OR FALSY RETURN DENIES, so a stack that just falls through or a gating trigger with NO `response` — refuses everyone, and a CRASH DENIES too. ONCE the object carries an `allowed` key admission needs STRICTLY `true` — a computed `1`/`"yes"` there DENIES. That is the inverse of a crashing message, which still delivers, and of `deliver` below. A lifecycle trigger's inputs are PINNED to those four, so a channel PATH PARAM is NOT among them — `inp("room_id")` RAISES, which crashes the gate and so REFUSES every client; take the param from `s.realtime.get_session` (`ref("session.params.room_id")`). A gate establishes NO auth, so `ref("auth.id")` reads 0 even when authenticated — identity is `t.client("permissions.dbo_id")` or the session. A SERVER connect/disconnect has no channel, so no params at all; `leave` is OBSERVATIONAL (return ignored, throws swallowed); `deliver` GATES delivery PER RECIPIENT — the per-viewer redaction tool and the most expensive action here (a stack per recipient per message), and it needs `delivery.perRecipient` on the channel to run at all — BOTH HALVES are required, so a `deliver` trigger on a channel without the flag NEVER RUNS and every subscriber receives the UNREDACTED payload (no error, no log line); `export()` warns on each half alone. **`deliver`'s RETURN VALUES DO NOT READ LIKE A FILTER:** ONLY an explicit NULL drops the message for that recipient; an OBJECT replaces that recipient's payload; ANYTHING ELSE — INCLUDING `false`, `0`, `""` — DELIVERS IT UNCHANGED, as does a crash. So `return false` from a yes/no redaction check SENDS the message it was written to suppress — return null instead. The delivered payload arrives NESTED, so read `t.payload("<field>")`, and `t.client` is the SENDER while `s.realtime.get_session` describes the RECIPIENT this run is for.
18
+ - `realtimeServerTrigger({ name, realtimeServer, actions?: {connect?,disconnect?}, stack?, response?, responseShape? })` — realtime SERVER lifecycle (a client connecting to / disconnecting from the server, not a message). Inputs: `t.action` (`connect|disconnect`), `t.realtime_server`, `t.client`. Bind `realtimeServer` to a `realtimeServer()` handle (or its name).
19
+ - `connect` GATES the connectiona denial sends an `error` and CLOSES the socket with code 4401 before it is ever ready, so it is a real front door, not an observer; same return shape as a channel `join` below (EMPTY/FALSY DENIES — INCLUDING a gating trigger with NO `response`, which returns nothing and so refuses every client).
20
+ - A CRASH DENIES too — a gate that cannot answer must not admit. Both failure modes lock the door, so plan for a self-inflicted LOCKOUT (an unguarded drill into a null `db.get` raises → everyone refused), not a breach.
21
+ - Gating is OPT-IN: a server with no `connect` trigger accepts every connection.
22
+ - `disconnect` is OBSERVATIONAL (return ignored, throws swallowed — cleanup must always complete).
23
+ - Both are SERVER-scoped, so `s.realtime.get_session` works but carries no channel path and no bound params.
24
+ - `realtimeChannelTrigger({ name, channel, actions?: {join?,leave?,deliver?}, stack?, response?, responseShape? })` — realtime CHANNEL lifecycle. Inputs: `t.action` (`join|leave|deliver`), `t.channel`, `t.payload`, `t.client`. Bind `channel` to a `realtimeChannel()` handle — a bare path is NOT accepted (it is unique only within its server). The three actions have DIFFERENT postures, and the posture decides what the stack should return:
25
+ - `join` GATES the join (it runs BEFORE the client becomes a member, so a denial means it never sees a fan-out) — return `{ allowed: c.bool(true) }` (optional `reason` reaches the client) or any truthy value to admit, and an EMPTY OR FALSY RETURN DENIES, so a stack that just falls through — or a gating trigger with NO `response` — refuses everyone, and a CRASH DENIES too. ONCE the object carries an `allowed` key admission needs STRICTLY `true` — a computed `1`/`"yes"` there DENIES. That is the inverse of a crashing message, which still delivers, and of `deliver` below.
26
+ - A lifecycle trigger's inputs are PINNED to those four, so a channel PATH PARAM is NOT among them — `inp("room_id")` RAISES, which crashes the gate and so REFUSES every client; take the param from `s.realtime.get_session` (`ref("session.params.room_id")`).
27
+ - A gate establishes NO auth, so `ref("auth.id")` reads 0 even when authenticated — identity is `t.client("permissions.dbo_id")` or the session. A SERVER connect/disconnect has no channel, so no params at all.
28
+ - `leave` is OBSERVATIONAL (return ignored, throws swallowed).
29
+ - `deliver` GATES delivery PER RECIPIENT — the per-viewer redaction tool and the most expensive action here (a stack per recipient per message), and it needs `delivery.perRecipient` on the channel to run at all — BOTH HALVES are required, so a `deliver` trigger on a channel without the flag NEVER RUNS and every subscriber receives the UNREDACTED payload (no error, no log line); `export()` warns on each half alone.
30
+ - **`deliver`'s RETURN VALUES DO NOT READ LIKE A FILTER:** ONLY an explicit NULL drops the message for that recipient; an OBJECT replaces that recipient's payload; ANYTHING ELSE — INCLUDING `false`, `0`, `""` — DELIVERS IT UNCHANGED, as does a crash. So `return false` from a yes/no redaction check SENDS the message it was written to suppress — return null instead.
31
+ - The delivered payload arrives NESTED, so read `t.payload("<field>")`, and `t.client` is the SENDER while `s.realtime.get_session` describes the RECIPIENT this run is for.
20
32
  - `mcpServerTrigger({ name, mcpServer, stack?, response?, responseShape? })` / `agentTrigger({ name, agent, stack?, response?, responseShape? })` — toolset connection. Bind with the `mcpServer()`/`agent()` def handle (or its name) — it resolves to the toolset guid at export. Raw numeric `objId` is the escape hatch, rarely right: ids are assigned at import, so a handle passed to `objId` is a type error, and binding nothing deploys a trigger that never fires. Inputs: `t.toolset` (`t.toolset("name")`), `t.tools`. Response-bearing; the default stack copies `toolset`/`tools` into vars and returns them.
21
33
  - `workspaceTrigger({ name, actions?: {branch_live?,branch_merge?,branch_new?}, stack? })` — branch lifecycle. Inputs: `t.to_branch`, `t.from_branch`, `t.action`. Config-only.
22
34
  - `errorTrigger({ name, stack? })` — error-signature trigger. Inputs: `t.event` (`new|regression|fixed`), `t.id`, `t.signature`, `t.error` (`t.error("code")`/`t.error("message")`), `t.caller`, `t.statement`, `t.actor`, `t.count`, `t.first_seen`, `t.last_seen`, `t.fixed_at`. Config-only.
package/llms/values.md CHANGED
@@ -8,11 +8,11 @@
8
8
  - `c.blank(tag: "const:<type>") => Value` — The editor's UNCONFIGURED value box (stored value ""), emitted by codegen for a pulled workspace — do not author it. NOT a zero or an empty collection: the engine reads "" and "0" differently, so c.blank("const:int") ≠ c.int(0) and neither canonicalizes into the other. Constant tags except const/const:obj, whose blanks are c.text("")/c.obj(null).
9
9
  - `c.bool(b: boolean) => Value` — Boolean constant → tag "const:bool".
10
10
  - `c.null(tag?: "const:null" | "const:obj") => Value` — Null constant, stored value "null". Bare c.null() is tag "const:null". c.null("const:obj") is the OBJECT-TYPED null the engine writes into a db.* statement's @meta slot — different stored bytes from c.obj(null), which is the blank object (value ""), though both evaluate to null. Codegen emits whichever spelling the workspace stored; do not swap one for the other.
11
- - `c.obj(o?: Json | null) => Value` — Object constant → tag "const:obj". A populated one stores an empty {} carrying one `set` filter per key — the editor's form, and the only populated form the engine reads back (a populated JSON string arrives truncated and fails the request with ERROR_FATAL "Unable to decode."). ⚠ a ZERO-BASED numeric key is an INDEX in the engine's data model, so c.obj({"0":"a"}) evaluates to the list ["a"] (a non-zero-based one like {"2":…} survives as a key) — that is the platform, not this encoding. No argument = the empty object {} — use this one. Explicit null = the legacy blank form the engine evaluates to null, NOT {}; it exists only so a pulled workspace round-trips, do not author it. Plain JSON literals only — a nested tagged value (inp/ref/auth/c.*) is rejected; for a computed object response use a record of values, not c.obj.
11
+ - `c.obj(o?: Json | null) => Value` — Object constant → tag "const:obj". A populated one stores an empty {} carrying one `set` filter per key — the editor's form, and the only populated form the engine reads back (a populated JSON string arrives truncated and fails the request with ERROR_FATAL "Unable to decode."). ⚠ a ZERO-BASED numeric key is an INDEX in the engine's data model, so c.obj({"0":"a"}) evaluates to the list ["a"] (a non-zero-based one like {"2":…} survives as a key) — that is the platform, not this encoding. Write c.array([...]) for a list, or prefix the keys ("k0") for an object; `export()` warns. No argument = the empty object {} — use this one. Explicit null = the legacy blank form the engine evaluates to null, NOT {}; it exists only so a pulled workspace round-trips, do not author it. Plain JSON literals only — a nested tagged value (inp/ref/auth/c.*) is rejected; for a computed object response use a record of values, not c.obj.
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([...])` — 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.
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"`. A null-safe ref(path, { safe: true }) works inside an obj() member directly. 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".