@xanots/sdk 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +3 -3
- package/dist/.build-fingerprint +1 -1
- package/dist/{agent-file-refresh-IJ7GN2FJ.js → agent-file-refresh-4W37DGXS.js} +4 -4
- package/dist/bin.js +8 -8
- package/dist/branch-commands-PBQ36L4A.js +134 -0
- package/dist/{chunk-CYJ7R3AD.js → chunk-2V3JUXTF.js} +24 -11
- package/dist/{chunk-OR43PDCW.js → chunk-3EYUR3TX.js} +2 -2
- package/dist/{chunk-SG4UBJ47.js → chunk-3QW5NFZJ.js} +26 -5
- package/dist/{chunk-7SISR3CS.js → chunk-4PE4ZAB7.js} +3 -3
- package/dist/{chunk-PGBK64U5.js → chunk-4PTSTDIG.js} +2 -2
- package/dist/{live-diff-FP4SFNT4.js → chunk-5HCDZ2XN.js} +67 -6
- package/dist/{chunk-OARSPAIE.js → chunk-5PDOAR77.js} +58 -38
- package/dist/{chunk-5WGEURVI.js → chunk-6AAT2AYQ.js} +21 -1
- package/dist/{chunk-QA5ICJ4M.js → chunk-7BUTRVKA.js} +3 -3
- package/dist/{chunk-YUBJLB6G.js → chunk-7JDT4PBU.js} +75 -8
- package/dist/{chunk-LLWK6H77.js → chunk-BKDJN76G.js} +31 -1
- package/dist/{chunk-MS3BRZAZ.js → chunk-CL2H2WYE.js} +50 -27
- package/dist/{chunk-KXCPZUUM.js → chunk-DBHE35JM.js} +8 -67
- package/dist/chunk-EQW3YT5U.js +159 -0
- package/dist/{chunk-BPEEGJJA.js → chunk-FAWNUE2U.js} +39 -8
- package/dist/{chunk-ZGA5MUNC.js → chunk-FDKR4YMN.js} +2 -2
- package/dist/{chunk-APOR6TCV.js → chunk-H2DEDWQF.js} +4 -4
- package/dist/{chunk-PXXLBXOP.js → chunk-JAVB27HL.js} +2 -2
- package/dist/{chunk-AAFBSOSD.js → chunk-JQJPFUZI.js} +3 -3
- package/dist/{chunk-IVP6TS26.js → chunk-JQLR64UC.js} +6 -5
- package/dist/{chunk-FN4OQT2J.js → chunk-JUHKEAHJ.js} +4 -4
- package/dist/{chunk-WJYB7DSI.js → chunk-MDXR5E5Q.js} +2 -2
- package/dist/{chunk-WGDAOOXG.js → chunk-MU3O43L2.js} +3 -3
- package/dist/{chunk-YUPQOLFX.js → chunk-NJGNUVXR.js} +15 -4
- package/dist/{chunk-F5T45NAM.js → chunk-OKNSR7MT.js} +45 -2
- package/dist/{chunk-MFIHIS6B.js → chunk-OYMR5AMJ.js} +2 -2
- package/dist/{chunk-QUUB7HYK.js → chunk-P5C2YKAM.js} +2 -2
- package/dist/{chunk-6DHJVLYP.js → chunk-Q77KNEUL.js} +1 -1
- package/dist/{chunk-D6OGU2AI.js → chunk-QIWBCS7N.js} +2 -2
- package/dist/cli.d.ts +28 -2
- package/dist/cli.js +7 -7
- package/dist/{codegen-command-BLOS3GR3.js → codegen-command-DYH4H4TD.js} +18 -18
- package/dist/{completion-GHP5RRLQ.js → completion-A6BZ3XGU.js} +8 -7
- package/dist/{deploy-command-XOH76USO.js → deploy-command-HM57BINV.js} +15 -15
- package/dist/{env-target-POGJMD6Q.js → env-target-WA3BH5YU.js} +6 -6
- package/dist/{ephemeral-command-46SL27GJ.js → ephemeral-command-SD2KLX4P.js} +10 -10
- package/dist/index.d.ts +69 -26
- package/dist/index.js +6 -6
- package/dist/init-command-VIPWAQYS.js +30 -0
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +120 -62
- package/dist/{io-7VIA5SON.js → io-M7XZEMK7.js} +3 -3
- package/dist/live-diff-QK4KC2FJ.js +12 -0
- package/dist/{lock-KXOJIGCG.js → lock-GFXD6G2E.js} +6 -2
- package/dist/{lock-commands-6UONZW26.js → lock-commands-7VJKAXV6.js} +8 -8
- package/dist/{login-command-SG7IWTHW.js → login-command-DFCURZLC.js} +6 -6
- package/dist/{logout-command-J2AG5NKC.js → logout-command-WXVZQCAV.js} +2 -2
- package/dist/{marketplace-command-NKTQ3VPS.js → marketplace-command-BCOAAN2I.js} +3 -3
- package/dist/{meta-client-OW5WKWW7.js → meta-client-57ZWVHST.js} +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +10 -10
- package/dist/{profile-command-ZPC2DPFV.js → profile-command-QJAAUZIV.js} +3 -3
- package/dist/{release-command-FKQ6E3TD.js → release-command-3IET7YII.js} +206 -26
- package/dist/{sandbox-details-command-DMY2KGA2.js → sandbox-details-command-EDO56IY3.js} +4 -4
- package/dist/{sandbox-export-command-R6QMEK3D.js → sandbox-export-command-MO6DEZ7Z.js} +5 -5
- package/dist/scaffold.d.ts +10 -13
- package/dist/scaffold.js +3 -3
- package/dist/{store-BJONDJoZ.d.ts → store-DfuWu1tE.d.ts} +1 -1
- package/dist/{test-command-56IAEYHX.js → test-command-S7SMRWMP.js} +8 -8
- package/dist/{upgrade-command-M2DZ3ZPD.js → upgrade-command-TZYSGXP7.js} +13 -13
- package/dist/{validate-command-CIBQJND7.js → validate-command-MMXEVHH3.js} +10 -10
- package/dist/{workspace-command-HZS43PHJ.js → workspace-command-X6L7HTXO.js} +24 -20
- package/guides/environment.md +6 -3
- package/guides/scaffold.md +3 -2
- package/llms/fields.md +8 -0
- package/llms/kinds-core.md +2 -1
- package/llms/lambda.md +1 -1
- package/llms/legacy.md +1 -1
- package/llms/lock.md +32 -0
- package/llms/object-kinds.md +2 -2
- package/llms/statements-calls.md +10 -3
- package/llms/statements-data.md +2 -1
- package/llms-full.txt +108 -58
- package/llms.txt +49 -49
- package/manifest.json +2 -2
- package/package.json +1 -1
- package/dist/init-command-NCRPVFGE.js +0 -30
package/llms-full.txt
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
# xanots v0.0.
|
|
1
|
+
# xanots v0.0.7
|
|
2
2
|
|
|
3
3
|
> TypeScript SDK that compiles a typed Xano workspace into the importable packageExport JSON bundle.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Xano is a hosted backend platform: one WORKSPACE serves a managed PostgreSQL
|
|
6
|
+
database, HTTP API endpoints, background tasks, realtime websocket channels, AI
|
|
7
|
+
agents and MCP servers, file storage, and redis. This SDK authors that workspace in
|
|
8
|
+
TypeScript — typed def objects registered on a `workspace(name)` — compiled to one
|
|
9
|
+
importable bundle by `xanots export ./xano/index.ts`, shipped live by `xanots deploy`.
|
|
10
|
+
|
|
11
|
+
Def modules execute at BUILD time only: `s.*` factories return data, and the engine
|
|
12
|
+
runs the compiled stack per request — statements in order, a statement's `as:`
|
|
13
|
+
naming a runtime variable, `response` the HTTP body. Every dynamic operand is a
|
|
14
|
+
TAGGED value — `ref("x")` a stack variable, `inp("x")` an input, `auth("id")` the
|
|
15
|
+
caller, `c.*` a constant — resolved at request time; JS operators over them do not
|
|
16
|
+
compute (see Gotchas). Requests share no memory — state persists in tables or redis.
|
|
8
17
|
|
|
9
18
|
Coverage: object kinds 25/31, statement surfaces 214/214, filters 226 (226 typed).
|
|
10
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`.
|
|
@@ -15,7 +24,7 @@ below — open the one whose condition matches the task, and skip the rest. For
|
|
|
15
24
|
exhaustive per-entry detail in NEITHER — a statement's full field schema with engine
|
|
16
25
|
defaults, a filter's complete argument list, the engine `storedName` mapping — do a
|
|
17
26
|
TARGETED lookup in the shipped `manifest.json` (grep or `jq` the one entry you need;
|
|
18
|
-
it is ~
|
|
27
|
+
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:
|
|
19
28
|
jq '.statements[] | select(.sPath=="db.get")' manifest.json
|
|
20
29
|
jq '.filters[] | select(.name=="json_decode")' manifest.json
|
|
21
30
|
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.*`.
|
|
@@ -24,11 +33,10 @@ Select a statement on `sPath` (the `s.*` path you write), NOT `surface` (the Xan
|
|
|
24
33
|
|
|
25
34
|
Each line is a condition on the task. Open the files whose condition matches and
|
|
26
35
|
skip the rest — paths are relative to this file (`node_modules/@xanots/sdk/` once
|
|
27
|
-
installed), so
|
|
28
|
-
|
|
29
|
-
that wants one fetch, not for an agent that can open the two files it needs.
|
|
36
|
+
installed), so a plain file read resolves them at the version you have.
|
|
37
|
+
`llms-full.txt` is everything concatenated — one fetch, for a reader that cannot open files.
|
|
30
38
|
|
|
31
|
-
- [Object kinds](llms/object-kinds.md): Read when
|
|
39
|
+
- [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.
|
|
32
40
|
- [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.
|
|
33
41
|
- [Saved unit tests, assertions, and mocks](llms/tests.md): Read when authoring a `workflowTest()` stack, when a query/function/middleware carries `tests`, when a statement should mock a value, or when running a deployed environment's tests.
|
|
34
42
|
- [Agent and MCP def shapes](llms/kinds-agent-mcp.md): Read when the workspace defines an `agent()` or an `mcpServer()`.
|
|
@@ -37,12 +45,13 @@ that wants one fetch, not for an agent that can open the two files it needs.
|
|
|
37
45
|
- [Triggers](llms/triggers.md): Read when authoring any trigger. A trigger's `stack` is a callback rather than the plain array every other kind takes, so the shape does not carry over.
|
|
38
46
|
- [Array and database statements](llms/statements-data.md): Read when the stack reads or writes rows (`s.db.*`), or transforms an array in place (`s.array.map`, `s.array.union`).
|
|
39
47
|
- [Statement runtime behavior](llms/statements-runtime.md): Read when you need to know what a statement's `as:` output actually holds, or why a bound variable is not the shape you expected.
|
|
40
|
-
- [Auth, cross-object calls, and microservices](llms/statements-calls.md): Read when the stack authenticates, calls another object (`s.function.run`, `s.api.request`, `s.tool.call`), or reaches a microservice.
|
|
48
|
+
- [Auth, cross-object calls, and microservices](llms/statements-calls.md): Read when the stack authenticates (the signup/login/token recipe is here), calls another object (`s.function.run`, `s.api.request`, `s.tool.call`), or reaches a microservice.
|
|
41
49
|
- [Value catalog](llms/values.md): Read when you need a literal, a reference, or a tag you have not used before — `c.*`, `ref`, `inp`, `auth`, `col`, and what each one encodes to.
|
|
42
50
|
- [Column and input types](llms/fields.md): Read when declaring a table column (`f.*`) or a function/query input (`input.*`) — a type's options and accessor methods, and the `s.precondition` error/status contract that rides the same catalog.
|
|
43
51
|
- [Filter catalog](llms/filters.md): Read when piping a value through `fl.*` — the full catalog with each filter's argument types.
|
|
44
|
-
- [Lambda bodies (JavaScript)](llms/lambda.md): Read when writing a JavaScript body, or weighing whether to reach for one at all — `lam.fn`, `fl.lambda`, `fl.reduce`, `s.lambda`. Lambda workers are a bounded shared resource, each surface binds a different set of identifiers
|
|
45
|
-
- [
|
|
52
|
+
- [Lambda bodies (JavaScript)](llms/lambda.md): Read when writing a JavaScript body, or weighing whether to reach for one at all — `lam.fn`, `fl.lambda`, `fl.reduce`, `s.lambda`. Lambda workers are a bounded shared resource, and each surface binds a different set of identifiers.
|
|
53
|
+
- [Lock file](llms/lock.md): Read when a `xano.lock` exists or should — renaming/pruning/adopting identities, seeding the lock programmatically, or asking which commands write it.
|
|
54
|
+
- [Legacy paradigms and retired statements](llms/legacy.md): Read when the code was PULLED from an existing Xano instance rather than authored here — how a codegen'd tree reads and the three shapes in it that must not be "fixed", plus a `raw({ name: "mvp:…" })` statement, a `realtimeTrigger()`, or any name the catalogs do not list.
|
|
46
55
|
- [Statement catalog](llms/statements-catalog.md): Read for the field signature of a specific statement — every surface, grouped by `s.*` namespace. Look here after the control-flow core in the router does not cover what you need.
|
|
47
56
|
|
|
48
57
|
## Quickstart
|
|
@@ -116,26 +125,18 @@ but nothing fails on a message no one reads, so in CI and in unattended agent bu
|
|
|
116
125
|
pass `--strict` (`emitBundle(app, { strict: true })` / `app.export({ strict: true })`):
|
|
117
126
|
every warning becomes a hard failure. Same bundle bytes either way.
|
|
118
127
|
|
|
119
|
-
Identity: object guids derive from `(type, name)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`xanots lock adopt <
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
silent no-op (`resetLockOverrides` exists for tests).
|
|
132
|
-
Development workflow: opt in EARLY — run `xanots export ./xano/index.ts --lock` once and COMMIT
|
|
133
|
-
`xano.lock` beside the entry file; every later export then keeps identities
|
|
134
|
-
stable across renames and environments. To rename an object: rename in code,
|
|
135
|
-
export (stderr prints the exact fix-up), run `xanots lock rename <kind> <old>
|
|
136
|
-
<new>`, export again — the original guid is emitted under the new name, so the
|
|
137
|
-
engine renames in place instead of delete+create. Taking over an existing
|
|
138
|
-
workspace: `xanots lock adopt <its-packageExport.json>` first, then export.
|
|
128
|
+
Identity: object guids derive from `(type, name)` — a query's from `(api group,
|
|
129
|
+
verb, name)` — so renames change identity.
|
|
130
|
+
Opt in EARLY: `xanots export ./xano/index.ts --lock` freezes every guid +
|
|
131
|
+
api-group/toolset canonical in a lock file written BESIDE THE ENTRY FILE —
|
|
132
|
+
`xano/xano.lock` for the standard scaffold, NOT the project root — which you COMMIT
|
|
133
|
+
(auto-read once present; CI guard `--frozen-lock` fails instead of changing it).
|
|
134
|
+
To rename an object: rename in code, export (stderr prints the exact fix-up), run
|
|
135
|
+
`xanots lock rename <kind> <old> <new>`, export again — the original guid is emitted
|
|
136
|
+
under the new name, so the engine renames in place instead of delete+create. Taking
|
|
137
|
+
over an existing workspace: `xanots lock adopt <its-packageExport.json>` first, then
|
|
138
|
+
export. Pruning, programmatic seeding, and which commands write the lock:
|
|
139
|
+
`llms/lock.md`.
|
|
139
140
|
|
|
140
141
|
## Deploy
|
|
141
142
|
|
|
@@ -166,14 +167,6 @@ production workspace, but confirm with the user before the first run.
|
|
|
166
167
|
(previewed against live). `--prune` deletes only what `xano.lock` records this
|
|
167
168
|
project released, and refuses without a lock.
|
|
168
169
|
|
|
169
|
-
Nothing from a DEPLOY is written back into `xano.lock` (an ephemeral/sandbox is a
|
|
170
|
-
separate workspace, so its identities must not pollute yours). The one write-back is
|
|
171
|
-
`release --replace`, which mints fresh identities in the workspace the lock describes:
|
|
172
|
-
it re-pins the lock from the rebuilt workspace, because otherwise the next release
|
|
173
|
-
matches nothing and duplicates every object. Deploying an ENTRY
|
|
174
|
-
FILE still updates the local lock via the shared compile step, exactly as `export`
|
|
175
|
-
does — only when a lock exists or `--lock` is passed.
|
|
176
|
-
|
|
177
170
|
**Frontend wiring.** `--static <dir>` injects the DEPLOYED env's backend URL as
|
|
178
171
|
`window.XANO_HOST` into EVERY html document in the build, before the app bundle runs,
|
|
179
172
|
so the frontend needs no rebuild to target an env. Every document, not just the root:
|
|
@@ -221,6 +214,12 @@ Non-obvious authoring rules:
|
|
|
221
214
|
- **Reference-helper picker:** `ref` = stack var (`as:` output), `inp` = input,
|
|
222
215
|
`col` = table column (in `db.query` `where`), `auth("id")` = the caller,
|
|
223
216
|
`c.*` = a constant. Pick by what you're pointing at.
|
|
217
|
+
- **Tagged values are DATA — a JS template literal cannot compose them.** `${ref(...)}`
|
|
218
|
+
(or `"a" + ref(...)`) stringifies the tag object at BUILD time: ``c.text(`Hi ${ref("u.name")}`)``
|
|
219
|
+
type-checks and encodes the literal text `Hi [object Object]`, served verbatim.
|
|
220
|
+
`export()` warns; `--strict` fails. Compose at RUNTIME:
|
|
221
|
+
`withFilters(c.text("Hi "), fl.concat(ref("u.name")))`, an `obj({...})`/record member,
|
|
222
|
+
or `c.expression('"Hi, " ~ $var.u.name')`.
|
|
224
223
|
- **`s.api.call` / `s.task.call` / `s.trigger.call` / `s.workflow_test.call` are
|
|
225
224
|
WORKFLOW-TEST ONLY.** Outside a `workflowTest({...})` stack the engine cannot reach
|
|
226
225
|
the target, so one in a query/function/task deploys clean and then answers the first
|
|
@@ -301,15 +300,16 @@ Non-obvious authoring rules:
|
|
|
301
300
|
- **Self-referencing tables** need the bare-name form: inside `tweets`'s own
|
|
302
301
|
schema, write `f.tableRef("tweets", { type: "int" })` — the `const tweets`
|
|
303
302
|
handle isn't assigned yet, so the handle form throws "used before declaration".
|
|
304
|
-
- **Same-name siblings collide
|
|
305
|
-
|
|
306
|
-
`export()` throws
|
|
307
|
-
`
|
|
308
|
-
`
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
303
|
+
- **Same-name siblings collide — queries excepted: their identity carries group + verb.**
|
|
304
|
+
Guids derive from `(type, name)`, so two functions (or tables, toolsets, …) sharing
|
|
305
|
+
a name derive ONE guid and `export()` throws — give them DISTINCT names. A QUERY
|
|
306
|
+
derives from `(api group, verb, name)`, the engine's own uniqueness: `GET items` +
|
|
307
|
+
`POST items`, and `items` across two groups, coexist, and the lock keys the same
|
|
308
|
+
composed identity (`query:blog|GET|items`) — changing a query's verb or group
|
|
309
|
+
changes its identity. An explicit `guid` on a colliding pair clears the throw but
|
|
310
|
+
is NOT a lasting fix: a lock entry cannot hold two guids, so `export --lock`
|
|
311
|
+
refuses the pair (`export()` warns even unlocked); it is for pinning identity
|
|
312
|
+
across a rename, not for sharing a name.
|
|
313
313
|
- **`export()` vs `emitBundle()` vs `writeBundle()`:** `writeBundle(app, path)`
|
|
314
314
|
writes it to disk; `export()` returns the bundle object;
|
|
315
315
|
`emitBundle()` returns the pretty JSON string. All three run the SAME build-time
|
|
@@ -485,7 +485,7 @@ Control flow & blocks (each nests a sub-stack; block specials name it `body`):
|
|
|
485
485
|
|
|
486
486
|
# Object kinds
|
|
487
487
|
|
|
488
|
-
> Read when
|
|
488
|
+
> 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.
|
|
489
489
|
|
|
490
490
|
Author with the factory, register on the Xano instance, lands under the payload key. Each line ends with a one-liner on what the primitive is.
|
|
491
491
|
|
|
@@ -512,7 +512,7 @@ Author with the factory, register on the Xano instance, lands under the payload
|
|
|
512
512
|
- realtime_server: `realtimeServer` → `Xano.registerRealtimeServers` → payload `realtime_server` — A realtime (websocket) server: the canonical-addressed container that owns realtime channels. Off until `enabled: true`. Returns a handle with `getUrl(baseUrl)`/`getPath()` for the client's socket URL (`wss://<host>/ws/<canonical>`).
|
|
513
513
|
- channel: `realtimeChannel` → `Xano.registerRealtimeChannels` → payload `channel` — A realtime channel: a joinable path on a realtime server (`rooms/{room_id}`) with typed path params, join/publish policy, a client-visible conversation transcript, and delivery semantics. Owns message handlers. Returns a handle with `getChannel(params)` for the path a client joins.
|
|
514
514
|
- message: `realtimeMessage` → `Xano.registerRealtimeMessages` → payload `message` — A realtime message handler: a named message type on a channel with its own typed payload and stack — the realtime analogue of a query. Pass the `realtimeChannel()` handle as `channel` and the owning server comes with it.
|
|
515
|
-
- microservice: `microservice` → `Xano.registerMicroservices` → payload `microservice` — A container workload deployed alongside the workspace, called from a stack with `s.microservice.request`.
|
|
515
|
+
- microservice: `microservice` → `Xano.registerMicroservices` → payload `microservice` — A container workload deployed alongside the workspace, called from a stack with `s.microservice.request`. Def shapes (`builtin` containers vs a `helm` chart) and the SECRET-handling rules its config carries: `llms/statements-calls.md`.
|
|
516
516
|
- knowledge: `knowledge` → `Xano.registerKnowledge` → payload `knowledge` — Markdown the workspace's AI agents read before they act — an `agents.md` of standing instructions, a `skill`, or a `doc`. The body is a real markdown FILE via `knowledgeFile("./x.md", import.meta.url)`, and `mode` decides how much of it reaches the agent.
|
|
517
517
|
- workspace: `workspaceConfig` → `Xano.registerWorkspace` → payload `workspace` — Workspace-level configuration such as default middleware chains and request-history defaults per host kind.
|
|
518
518
|
|
|
@@ -523,7 +523,8 @@ Author with the factory, register on the Xano instance, lands under the payload
|
|
|
523
523
|
The def-object passed to each factory. `?` = optional. `input` is keyed by
|
|
524
524
|
input name (`input.<type>(opts?)`); `stack` is `Statement[]` (`s.*`); `response`
|
|
525
525
|
is a `ResponseDef` (see **Responses** below). Object identity is `guid?` —
|
|
526
|
-
omit it and it derives from `name` (
|
|
526
|
+
omit it and it derives from `name` (for a query, from group + verb + name; set it
|
|
527
|
+
to survive a rename).
|
|
527
528
|
|
|
528
529
|
- `defineFunction({ name, guid?, description?, docs?, workspace?, input?, stack?, response?, tests? })`
|
|
529
530
|
- `query({ name, verb, apiGroup?, guid?, auth?, input?, stack?, response?, responseType?, apiEnabled?, disabled?, cache?, description?, docs?, tests?, example? })`
|
|
@@ -796,7 +797,8 @@ primary key `id`):
|
|
|
796
797
|
- `s.db.del({ table, fieldName?, fieldValue, as? })` — delete by field match.
|
|
797
798
|
- `s.db.add({ table, row?, data?, output?, as? })` — insert; `row` is a partial keyed by column.
|
|
798
799
|
- A row CELL takes a tagged `Value`, a nested object of sub-keys, or a bare JS literal typed against that column: `row: { is_hidden: true, notes: "…" }` encodes exactly as `{ is_hidden: c.bool(true), notes: c.text("…") }`. The tag comes from the COLUMN, not the literal — `10` on an `f.decimal()` column is `const:decimal`, not `const:int` — so a literal contradicting its column is a compile error on a `f.*`-schema table (`{ is_hidden: "yes" }` on an `f.bool()` column) and throws at encode on a raw-`ColumnDef[]` one. An `f.enum()` column keeps its member union. A column with no literal form — obj/json/list/geo/vector/file — still needs `c.obj`/`c.array`.
|
|
799
|
-
- `null` is accepted on EVERY column, including ones that refuse every other literal, and encodes `const:null` — a write OF null, not the same as omitting the key
|
|
800
|
+
- `null` is accepted on EVERY column, including ones that refuse every other literal, and encodes `const:null` — a write OF null, not the same as omitting the key. A column's `nullable` is not consulted at encode; the engine refuses a null it forbids.
|
|
801
|
+
- Omitting a key on `add` writes the column's type default — declared `default` if set, `[]` for a list, `{}` for obj/json, else `null`. That `null` is EMITTED, not what the row holds: the engine applies the column's nullability, so a `nullable` column keeps `null` and a non-nullable one lands on its type's zero value (`""`, `0`, `false`). Set the cell when the stored value matters. On `edit` an omitted key keeps its stored value.
|
|
800
802
|
- An `f.password()` cell takes the PLAINTEXT — the column hashes on write, so a pre-hashed value, or a hashing filter on the cell, stores a hash of a hash that `security.check_password` can never match.
|
|
801
803
|
- `s.db.edit({ table, fieldName?, fieldValue, row?, data?, output?, as? })` — update by field match.
|
|
802
804
|
- `s.db.patch({ table, fieldName?, fieldValue, data, output?, as? })` — merge a partial (`data` is an object value).
|
|
@@ -888,25 +890,32 @@ Runtime behavior (what the `as:` output holds, and misses):
|
|
|
888
890
|
|
|
889
891
|
# Auth, cross-object calls, and microservices
|
|
890
892
|
|
|
891
|
-
> Read when the stack authenticates, calls another object (`s.function.run`, `s.api.request`, `s.tool.call`), or reaches a microservice.
|
|
893
|
+
> Read when the stack authenticates (the signup/login/token recipe is here), calls another object (`s.function.run`, `s.api.request`, `s.tool.call`), or reaches a microservice.
|
|
892
894
|
|
|
893
895
|
Auth & calls:
|
|
894
896
|
|
|
895
897
|
- `s.security.create_auth_token({ table, id, extras?, expiration?, as? })` — `extras` defaults to `{}`, `expiration` to `86400`s (`0` = never).
|
|
898
|
+
- **Marketplace first:** `@xanots/auth` registers a complete auth surface — signup/login/me endpoints plus their `user`/`account`/`event_log` tables — via `registerAuth(ws, { canonical: "authn" })`. Authentication only, not authorization. Inspect with `xanots marketplace details @xanots/auth --prompt`.
|
|
899
|
+
- **The auth loop end to end** — what that module implements; author it directly for a custom flow. An auth TABLE (`table({ auth: true })`) backs identity; a PUBLIC login query verifies and mints a token; every protected query names that table as `auth:` and reads the caller with `auth(...)`.
|
|
900
|
+
- Signup: `s.db.add({ table: users, row: { email: inp("email"), password: inp("password") } })` — the `f.password()` column hashes on write; take the submission as `input.text()` on signup AND login (see Gotchas on double-hashing).
|
|
901
|
+
- Login: `s.db.get({ table: users, fieldName: "email", fieldValue: inp("email"), output: ["id", "password"], as: "u" })` — the `output` naming `password` is REQUIRED (the column is `access: "internal"` and absent from the row otherwise) → `s.precondition({ expr: expr(ref("u", { safe: true }), "!=", c.null()), error: c.text("No such user."), error_type: "notfound" })` → `s.security.check_password({ text_password: inp("password"), hash_password: ref("u.password"), as: "ok" })` → precondition on `ok` → `s.security.create_auth_token({ table: users, id: ref("u.id"), as: "token" })` → `response: { token: ref("token") }`.
|
|
902
|
+
- Client: send it as an `Authorization: Bearer <token>` header. A `query({ auth: users })` refuses a request without a valid one before its stack runs; inside the stack `auth("id")` is the caller's row id (`response: { id: auth("id") }` is the whole `me` endpoint).
|
|
896
903
|
- `s.security.create_guid({ as? })` — bind a fresh GUID string. Takes nothing else.
|
|
897
904
|
- `s.function.run({ fn, input?, as?, runtime? })` / `s.function.call({ fn, input?, as? })` — run another function; `input` is keyed by the target's input names.
|
|
898
905
|
- `runtime?` runs it in the BACKGROUND: `{ mode: "async-shared" }` or `{ mode: "async-dedicated", cpu?, memory?, timeout?, maxRetry? }` (resources read at dedicated only). An async call DOES NOT return the result — it dispatches and continues, so `as` binds nothing; collect with `s.await({ ids })`. Omit for a normal call. Same block on `s.ai.agent.run`.
|
|
899
|
-
- `s.api.call({ api, input?, headers?, auth?, as? })` — invoke an endpoint. `headers` REPLACES the request headers the callee sees and takes the same shapes as `s.api.request`'s — a `{ "Name": value }` record (values may be tagged), a `string[]` of `"Name: value"` lines, or one `Value`. `auth` is `{ token, ignoreExpiration? }`: `token` must be a BARE STRING — a tagged `Value` deploys clean and then fails the run with `Param: token - Text filter requires an integer, float, string or boolean value`, since the engine stores that slot as plain text and never evaluates it. Neither slot authenticates the call today; see `llms/tests.md` for what a workflow-test run actually sees. WORKFLOW-TEST ONLY (see Gotchas in `llms.txt`) — elsewhere it deploys clean and 500s the first real request.
|
|
906
|
+
- `s.api.call({ api, input?, headers?, auth?, as? })` — invoke an endpoint. `api` takes the `query()` def HANDLE (or a `{ name, guid }` pair) — a bare name is refused, because a query's identity is composed from its api group, verb, and name. `headers` REPLACES the request headers the callee sees and takes the same shapes as `s.api.request`'s — a `{ "Name": value }` record (values may be tagged), a `string[]` of `"Name: value"` lines, or one `Value`. `auth` is `{ token, ignoreExpiration? }`: `token` must be a BARE STRING — a tagged `Value` deploys clean and then fails the run with `Param: token - Text filter requires an integer, float, string or boolean value`, since the engine stores that slot as plain text and never evaluates it. Neither slot authenticates the call today; see `llms/tests.md` for what a workflow-test run actually sees. WORKFLOW-TEST ONLY (see Gotchas in `llms.txt`) — elsewhere it deploys clean and 500s the first real request.
|
|
900
907
|
- `s.api.request({ url, method?, params?, headers?, timeout?, follow_location?, verify_host?, verify_peer?, ca_certificate?, certificate?, certificate_pass?, private_key?, private_key_pass?, description?, output?, as? })` — external HTTP request (`mvp:api_request`). Ergonomic types, each also accepting a dynamic `Value`: `method` suggests the 7 verbs (GET/POST/PUT/DELETE/HEAD/OPTIONS/PATCH), `params` a plain JSON object **or** a FLAT record whose values are tagged `Value`s (`{ count: ref("count") }`, each lifted via a `set` filter — the same record-of-values shape `response: { key: value }` takes); a tagged value NESTED inside an object or array THROWS at encode, so wrap a structured body in `obj({...})`, which encodes any depth as one `const:expr2` (→ query string for GET/HEAD/OPTIONS, body otherwise), `headers` a `{ "Name": value }` record whose values may be tagged (`{ "x-api-key": env("KEY") }`, each pair joined to a `"Name: value"` line) **or** a `string[]` of full header lines — prefer a header over a `?key=` query param for a credential — a URL travels into access logs, proxies and `Referer`. ⚠ Neither spelling is envelope-safe: the `as` envelope's `request` half mirrors `url`, `params` AND `headers`, so never return it raw from a credentialed request — read `response.result`. A NAME outside the header-token charset is refused, and a LITERAL value carrying a newline is refused; a value may hold `:` and spaces (`Bearer a: b` is a valid value). A TAGGED value cannot be checked at build time, so strip CRLF from caller-controlled input before this slot. Literal pairs lead the emitted array and computed ones follow, so the wire order is not the record's key order, `timeout` a `number` in seconds (1–86400), and `follow_location`/`verify_host`/`verify_peer` booleans. `description` (Settings tab) and `output` filters (Output tab) ride the envelope. SSL cert interdependencies (certificate↔private_key, ca_certificate→verify_peer) are checked at build time when statically provable, else by the engine at runtime. The `as` result is typed as the `{request, response}` envelope (`response.status: number`, `response.result: unknown`), so `InferResponse` resolves a `ref` to it. Same typed result on `webflow.request` and `microservice.request`.
|
|
901
908
|
- `s.stream.from_request({ url, method?, …tls, as? })` — streaming external HTTP request (`mvp:streaming_api_request`); same typed field surface as `s.api.request` (no description/output envelope). `url` is REQUIRED — it shares `api.request`'s engine declaration, which has no default.
|
|
902
909
|
- `s.webflow.request({ path, method?, …tls, as? })` — Webflow API request (`mvp:connect_webflow_api_request`); like `s.api.request` but addressed by `path` (host is engine-supplied), and `path` is REQUIRED — the engine rejects an empty one. No `headers`: the engine builds its own from the workspace's Webflow connection and ignores an authored value.
|
|
903
910
|
- `s.task.call` / `s.tool.call` / `s.trigger.call` / `s.middleware.call` / `s.addon.call` — same `{ <target>, input?, as? }` shape against the named kind. `s.task.call` and `s.trigger.call` are WORKFLOW-TEST ONLY (see Gotchas in `llms.txt`); `s.tool.call`, `s.middleware.call` and `s.addon.call` run from any stack.
|
|
904
|
-
- `s.action.call({
|
|
911
|
+
- `s.action.call({ actionId, input?, registry?, as? })` / `s.action.package.call({ traceId, versionId, slug, input?, registry?, as? })` — invoke an installed action. Ids are SUPPLIED, never derived from a name: an action is installed onto the instance, so its identity is assigned at install and differs per instance — read them off a call in a pulled workspace. The package form needs all three parts; they are one composite and two of them address nothing. `registry` is the action's own settings, `input` the per-call arguments.
|
|
905
912
|
- `s.cloud.job({ image, command, args?, secret?, template?, await?, as? })` — launch a containerized job. `image`/`command` are REQUIRED; omitted → `Missing param`. ⚠ `await` is SECONDS (default 60), not a boolean.
|
|
906
913
|
- `s.cloud.job.status({ id, as? })` · `s.cloud.job.await({ ids, timeout, as? })` — poll one job, or block on several (`ids` a list `Value`, `timeout` in seconds; both required).
|
|
907
914
|
|
|
908
915
|
Microservices (the `microservice()` def and the statement that calls it):
|
|
909
916
|
|
|
917
|
+
- `microservice({ name, kind, … })` — two mutually exclusive shapes via `kind`: `builtin` declares containers (image/ports/resources/env/command/args) plus optional `ingresses`, and `helm` points at a chart and its `values` — passing both throws. EARLY SURFACE, expected to change — every export of a workspace declaring one prints a notice saying so. `configs`/`volumes` are typed and `@deprecated` but NOT deployable: the engine rejects an import carrying either, so `export()` fails the build rather than letting the deploy fatal. Put a value the workload reads in a container's `env`, and storage in a container's own `volumes` (`emptyDir`/`persistent`/`config`). Container names are free-form — they need not match the microservice name, which is what a stack addresses.
|
|
918
|
+
- SECRETS RIDE ALONG — `chart.values` and `registryAuth.dockerconfigjson` are carried into the bundle, and into a pulled tree, verbatim (they must be, or a pulled microservice could not be redeployed). Both are stored strings with NO deploy-time indirection: `process.env.X` in the def resolves at EXPORT and writes the literal into the bundle, so it is not a way to keep the credential out. Either leave `registryAuth` unset (public image, or a credential attached outside this workspace) or treat the bundle and any pulled tree as secret material — keep them out of git, or rotate after. Export prints a notice per microservice carrying either field; `--strict` does not promote it. For a secret a STACK reads, the mapped surface is `workspaceConfig({ env })` + `env("NAME")`.
|
|
910
919
|
- `s.microservice.request({ host, path, port?, method?, params?, headers?, timeout?, follow_location?, as? })` — in-cluster microservice call (`mvp:microservice_request`); no TLS fields. ONLY `host`+`path` required; the rest default to the engine's values (`GET`/`{}`/`[]`/`10`/`true`), always emitted. Pass the `microservice()` DEF as `host` — it binds by NAME (how the engine resolves it), so a rename fixes every call site and the port is checked before deploy. `port?` folds into `host` as `"name:port"`: a def exposing ONE `servicePort` resolves automatically, SEVERAL requires it. A raw `"name:port"` string works, unvalidated, and is the only way to reach an instance-level microservice. `tenantDeploy: "manual"` on the def imports the row without starting the workload.
|
|
911
920
|
- `s.workflow_test.call({ workflowTest, datasource?, as? })` — run another workflow test from inside one, which is the only place it runs (see Gotchas in `llms.txt`). The odd one out: NO `input` (a workflow test takes none), and it carries `datasource?` instead — same clone caveat as the kind's own field.
|
|
912
921
|
|
|
@@ -999,6 +1008,14 @@ the entry's own name. `hidden: ["created_at"]` drops columns from that expansion
|
|
|
999
1008
|
`input.list(element)` for arrays — wrap any element constructor, e.g.
|
|
1000
1009
|
`input.list(input.text())` or `input.list(input.object({ id: f.int() }))`. Prefer the
|
|
1001
1010
|
typed forms over `input.json()` when the shape is known.
|
|
1011
|
+
**A file reaches a column in two steps.** `input.file` is the RAW upload (the request's
|
|
1012
|
+
multipart/base64 bytes) and cannot be written to a file column directly: store it first —
|
|
1013
|
+
`s.storage.create_image({ as: "img", value: ref("input.avatar"), access: "public" })`
|
|
1014
|
+
(the request input bag is read as a dotted `ref("input.<name>")` here) — then write
|
|
1015
|
+
`ref("img")` into the `f.image()` cell with `s.db.add`/`edit`. `access` defaults to
|
|
1016
|
+
`"public"` (a guessable URL): pass `"private"` and hand out `s.storage.sign_private_url`
|
|
1017
|
+
results for anything user-scoped. `create_video`/`create_audio`/`create_attachment` are
|
|
1018
|
+
the same shape for the other file columns.
|
|
1002
1019
|
**Typed inputs validate/coerce on bind, before your stack runs** — so reach for the
|
|
1003
1020
|
specific type instead of hand-rolling checks. `input.email({ required: true })` rejects a
|
|
1004
1021
|
malformed address with a 400 (and trims; add `methods: ["lower"]` to downcase) — no
|
|
@@ -1231,7 +1248,7 @@ Variadic filters — they take arguments, but no declared list, so the count is
|
|
|
1231
1248
|
|
|
1232
1249
|
# Lambda bodies (JavaScript)
|
|
1233
1250
|
|
|
1234
|
-
> Read when writing a JavaScript body, or weighing whether to reach for one at all — `lam.fn`, `fl.lambda`, `fl.reduce`, `s.lambda`. Lambda workers are a bounded shared resource, each surface binds a different set of identifiers
|
|
1251
|
+
> Read when writing a JavaScript body, or weighing whether to reach for one at all — `lam.fn`, `fl.lambda`, `fl.reduce`, `s.lambda`. Lambda workers are a bounded shared resource, and each surface binds a different set of identifiers.
|
|
1235
1252
|
|
|
1236
1253
|
**A lambda is an escape hatch, not a default.** The body runs outside the request's own
|
|
1237
1254
|
runtime, and a workspace has a BOUNDED pool of lambda workers every lambda in it shares
|
|
@@ -1341,9 +1358,42 @@ Four hazards and the dependency route, all live-verified:
|
|
|
1341
1358
|
|
|
1342
1359
|
TypeScript annotations survive in the body, and top-level `await` works.
|
|
1343
1360
|
|
|
1361
|
+
# Lock file
|
|
1362
|
+
|
|
1363
|
+
> Read when a `xano.lock` exists or should — renaming/pruning/adopting identities, seeding the lock programmatically, or asking which commands write it.
|
|
1364
|
+
|
|
1365
|
+
`xano.lock` pins each object's guid and each api-group/toolset canonical, so renames
|
|
1366
|
+
stay renames (guids otherwise derive from `(type, name)`; a query's from `(api group,
|
|
1367
|
+
verb, name)`). Created by `xanots export <entry> --lock`, auto-read once present,
|
|
1368
|
+
committed. A query's entry is keyed by that composed identity —
|
|
1369
|
+
`query:<group>|<verb>|<name>` — the same seed its guid derives from; a legacy
|
|
1370
|
+
`query:<name>` entry keeps pinning and is migrated by the next locked export (the
|
|
1371
|
+
old key drops automatically once its guid re-lands under the composed one).
|
|
1372
|
+
|
|
1373
|
+
- `xanots lock rename <kind> <old> <new>` — `kind` is the payload key (or `table`/`api_group`).
|
|
1374
|
+
Run it after renaming in code; the next export emits the original guid under the new name.
|
|
1375
|
+
- `xanots lock prune <entry-file> [keys…] --yes` — drops orphaned entries. Finding orphans
|
|
1376
|
+
RUNS the entry's module scope (env assertions included); `--no-verify --yes <kind:name>…`
|
|
1377
|
+
prunes named keys with no evaluation and no orphan check.
|
|
1378
|
+
- `xanots lock adopt <live-bundle.json> [--yes]` — seed the lock from an engine
|
|
1379
|
+
packageExport when taking over an existing workspace.
|
|
1380
|
+
- Every lock subcommand accepts `--lock=<path>`. `rename`/`adopt` take no entry file, so
|
|
1381
|
+
from outside the lock's directory pass `--lock` (or `--entry=<entry-file>` to derive it).
|
|
1382
|
+
- Programmatic use: call `seedLockOverrides(readLockFile(path))` BEFORE importing any def
|
|
1383
|
+
module — references bake guids at import time, so late seeding is a silent no-op
|
|
1384
|
+
(`resetLockOverrides` exists for tests).
|
|
1385
|
+
|
|
1386
|
+
What writes the lock: `export`/`deploy` of an ENTRY FILE update it via the shared compile
|
|
1387
|
+
step — only when a lock exists or `--lock` is passed. Nothing from a DEPLOY is written
|
|
1388
|
+
back beyond that (an ephemeral/sandbox is a separate workspace, so its identities must
|
|
1389
|
+
not pollute yours). The one write-back is `release --replace`, which mints fresh
|
|
1390
|
+
identities in the workspace the lock describes: it re-pins the lock from the rebuilt
|
|
1391
|
+
workspace, because otherwise the next release matches nothing and duplicates every
|
|
1392
|
+
object. CI: `--frozen-lock` fails instead of changing the lock.
|
|
1393
|
+
|
|
1344
1394
|
# Legacy paradigms and retired statements
|
|
1345
1395
|
|
|
1346
|
-
> Read when the code was PULLED from an existing Xano instance rather than authored here — how a codegen'd tree reads and the three shapes in it that must not be "fixed", plus a `raw({ name: "mvp:…" })` statement, a `realtimeTrigger()`, or any name the catalogs do not list.
|
|
1396
|
+
> Read when the code was PULLED from an existing Xano instance rather than authored here — how a codegen'd tree reads and the three shapes in it that must not be "fixed", plus a `raw({ name: "mvp:…" })` statement, a `realtimeTrigger()`, or any name the catalogs do not list.
|
|
1347
1397
|
|
|
1348
1398
|
Older paradigms this SDK still supports and still emits when it decodes an existing
|
|
1349
1399
|
workspace. **Do not author these.** They are listed by name only so you recognize them
|
package/llms.txt
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
# xanots v0.0.
|
|
1
|
+
# xanots v0.0.7
|
|
2
2
|
|
|
3
3
|
> TypeScript SDK that compiles a typed Xano workspace into the importable packageExport JSON bundle.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Xano is a hosted backend platform: one WORKSPACE serves a managed PostgreSQL
|
|
6
|
+
database, HTTP API endpoints, background tasks, realtime websocket channels, AI
|
|
7
|
+
agents and MCP servers, file storage, and redis. This SDK authors that workspace in
|
|
8
|
+
TypeScript — typed def objects registered on a `workspace(name)` — compiled to one
|
|
9
|
+
importable bundle by `xanots export ./xano/index.ts`, shipped live by `xanots deploy`.
|
|
10
|
+
|
|
11
|
+
Def modules execute at BUILD time only: `s.*` factories return data, and the engine
|
|
12
|
+
runs the compiled stack per request — statements in order, a statement's `as:`
|
|
13
|
+
naming a runtime variable, `response` the HTTP body. Every dynamic operand is a
|
|
14
|
+
TAGGED value — `ref("x")` a stack variable, `inp("x")` an input, `auth("id")` the
|
|
15
|
+
caller, `c.*` a constant — resolved at request time; JS operators over them do not
|
|
16
|
+
compute (see Gotchas). Requests share no memory — state persists in tables or redis.
|
|
8
17
|
|
|
9
18
|
Coverage: object kinds 25/31, statement surfaces 214/214, filters 226 (226 typed).
|
|
10
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`.
|
|
@@ -15,7 +24,7 @@ below — open the one whose condition matches the task, and skip the rest. For
|
|
|
15
24
|
exhaustive per-entry detail in NEITHER — a statement's full field schema with engine
|
|
16
25
|
defaults, a filter's complete argument list, the engine `storedName` mapping — do a
|
|
17
26
|
TARGETED lookup in the shipped `manifest.json` (grep or `jq` the one entry you need;
|
|
18
|
-
it is ~
|
|
27
|
+
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:
|
|
19
28
|
jq '.statements[] | select(.sPath=="db.get")' manifest.json
|
|
20
29
|
jq '.filters[] | select(.name=="json_decode")' manifest.json
|
|
21
30
|
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.*`.
|
|
@@ -24,11 +33,10 @@ Select a statement on `sPath` (the `s.*` path you write), NOT `surface` (the Xan
|
|
|
24
33
|
|
|
25
34
|
Each line is a condition on the task. Open the files whose condition matches and
|
|
26
35
|
skip the rest — paths are relative to this file (`node_modules/@xanots/sdk/` once
|
|
27
|
-
installed), so
|
|
28
|
-
|
|
29
|
-
that wants one fetch, not for an agent that can open the two files it needs.
|
|
36
|
+
installed), so a plain file read resolves them at the version you have.
|
|
37
|
+
`llms-full.txt` is everything concatenated — one fetch, for a reader that cannot open files.
|
|
30
38
|
|
|
31
|
-
- [Object kinds](llms/object-kinds.md): Read when
|
|
39
|
+
- [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.
|
|
32
40
|
- [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.
|
|
33
41
|
- [Saved unit tests, assertions, and mocks](llms/tests.md): Read when authoring a `workflowTest()` stack, when a query/function/middleware carries `tests`, when a statement should mock a value, or when running a deployed environment's tests.
|
|
34
42
|
- [Agent and MCP def shapes](llms/kinds-agent-mcp.md): Read when the workspace defines an `agent()` or an `mcpServer()`.
|
|
@@ -37,12 +45,13 @@ that wants one fetch, not for an agent that can open the two files it needs.
|
|
|
37
45
|
- [Triggers](llms/triggers.md): Read when authoring any trigger. A trigger's `stack` is a callback rather than the plain array every other kind takes, so the shape does not carry over.
|
|
38
46
|
- [Array and database statements](llms/statements-data.md): Read when the stack reads or writes rows (`s.db.*`), or transforms an array in place (`s.array.map`, `s.array.union`).
|
|
39
47
|
- [Statement runtime behavior](llms/statements-runtime.md): Read when you need to know what a statement's `as:` output actually holds, or why a bound variable is not the shape you expected.
|
|
40
|
-
- [Auth, cross-object calls, and microservices](llms/statements-calls.md): Read when the stack authenticates, calls another object (`s.function.run`, `s.api.request`, `s.tool.call`), or reaches a microservice.
|
|
48
|
+
- [Auth, cross-object calls, and microservices](llms/statements-calls.md): Read when the stack authenticates (the signup/login/token recipe is here), calls another object (`s.function.run`, `s.api.request`, `s.tool.call`), or reaches a microservice.
|
|
41
49
|
- [Value catalog](llms/values.md): Read when you need a literal, a reference, or a tag you have not used before — `c.*`, `ref`, `inp`, `auth`, `col`, and what each one encodes to.
|
|
42
50
|
- [Column and input types](llms/fields.md): Read when declaring a table column (`f.*`) or a function/query input (`input.*`) — a type's options and accessor methods, and the `s.precondition` error/status contract that rides the same catalog.
|
|
43
51
|
- [Filter catalog](llms/filters.md): Read when piping a value through `fl.*` — the full catalog with each filter's argument types.
|
|
44
|
-
- [Lambda bodies (JavaScript)](llms/lambda.md): Read when writing a JavaScript body, or weighing whether to reach for one at all — `lam.fn`, `fl.lambda`, `fl.reduce`, `s.lambda`. Lambda workers are a bounded shared resource, each surface binds a different set of identifiers
|
|
45
|
-
- [
|
|
52
|
+
- [Lambda bodies (JavaScript)](llms/lambda.md): Read when writing a JavaScript body, or weighing whether to reach for one at all — `lam.fn`, `fl.lambda`, `fl.reduce`, `s.lambda`. Lambda workers are a bounded shared resource, and each surface binds a different set of identifiers.
|
|
53
|
+
- [Lock file](llms/lock.md): Read when a `xano.lock` exists or should — renaming/pruning/adopting identities, seeding the lock programmatically, or asking which commands write it.
|
|
54
|
+
- [Legacy paradigms and retired statements](llms/legacy.md): Read when the code was PULLED from an existing Xano instance rather than authored here — how a codegen'd tree reads and the three shapes in it that must not be "fixed", plus a `raw({ name: "mvp:…" })` statement, a `realtimeTrigger()`, or any name the catalogs do not list.
|
|
46
55
|
- [Statement catalog](llms/statements-catalog.md): Read for the field signature of a specific statement — every surface, grouped by `s.*` namespace. Look here after the control-flow core in the router does not cover what you need.
|
|
47
56
|
|
|
48
57
|
## Quickstart
|
|
@@ -116,26 +125,18 @@ but nothing fails on a message no one reads, so in CI and in unattended agent bu
|
|
|
116
125
|
pass `--strict` (`emitBundle(app, { strict: true })` / `app.export({ strict: true })`):
|
|
117
126
|
every warning becomes a hard failure. Same bundle bytes either way.
|
|
118
127
|
|
|
119
|
-
Identity: object guids derive from `(type, name)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`xanots lock adopt <
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
silent no-op (`resetLockOverrides` exists for tests).
|
|
132
|
-
Development workflow: opt in EARLY — run `xanots export ./xano/index.ts --lock` once and COMMIT
|
|
133
|
-
`xano.lock` beside the entry file; every later export then keeps identities
|
|
134
|
-
stable across renames and environments. To rename an object: rename in code,
|
|
135
|
-
export (stderr prints the exact fix-up), run `xanots lock rename <kind> <old>
|
|
136
|
-
<new>`, export again — the original guid is emitted under the new name, so the
|
|
137
|
-
engine renames in place instead of delete+create. Taking over an existing
|
|
138
|
-
workspace: `xanots lock adopt <its-packageExport.json>` first, then export.
|
|
128
|
+
Identity: object guids derive from `(type, name)` — a query's from `(api group,
|
|
129
|
+
verb, name)` — so renames change identity.
|
|
130
|
+
Opt in EARLY: `xanots export ./xano/index.ts --lock` freezes every guid +
|
|
131
|
+
api-group/toolset canonical in a lock file written BESIDE THE ENTRY FILE —
|
|
132
|
+
`xano/xano.lock` for the standard scaffold, NOT the project root — which you COMMIT
|
|
133
|
+
(auto-read once present; CI guard `--frozen-lock` fails instead of changing it).
|
|
134
|
+
To rename an object: rename in code, export (stderr prints the exact fix-up), run
|
|
135
|
+
`xanots lock rename <kind> <old> <new>`, export again — the original guid is emitted
|
|
136
|
+
under the new name, so the engine renames in place instead of delete+create. Taking
|
|
137
|
+
over an existing workspace: `xanots lock adopt <its-packageExport.json>` first, then
|
|
138
|
+
export. Pruning, programmatic seeding, and which commands write the lock:
|
|
139
|
+
`llms/lock.md`.
|
|
139
140
|
|
|
140
141
|
## Deploy
|
|
141
142
|
|
|
@@ -166,14 +167,6 @@ production workspace, but confirm with the user before the first run.
|
|
|
166
167
|
(previewed against live). `--prune` deletes only what `xano.lock` records this
|
|
167
168
|
project released, and refuses without a lock.
|
|
168
169
|
|
|
169
|
-
Nothing from a DEPLOY is written back into `xano.lock` (an ephemeral/sandbox is a
|
|
170
|
-
separate workspace, so its identities must not pollute yours). The one write-back is
|
|
171
|
-
`release --replace`, which mints fresh identities in the workspace the lock describes:
|
|
172
|
-
it re-pins the lock from the rebuilt workspace, because otherwise the next release
|
|
173
|
-
matches nothing and duplicates every object. Deploying an ENTRY
|
|
174
|
-
FILE still updates the local lock via the shared compile step, exactly as `export`
|
|
175
|
-
does — only when a lock exists or `--lock` is passed.
|
|
176
|
-
|
|
177
170
|
**Frontend wiring.** `--static <dir>` injects the DEPLOYED env's backend URL as
|
|
178
171
|
`window.XANO_HOST` into EVERY html document in the build, before the app bundle runs,
|
|
179
172
|
so the frontend needs no rebuild to target an env. Every document, not just the root:
|
|
@@ -221,6 +214,12 @@ Non-obvious authoring rules:
|
|
|
221
214
|
- **Reference-helper picker:** `ref` = stack var (`as:` output), `inp` = input,
|
|
222
215
|
`col` = table column (in `db.query` `where`), `auth("id")` = the caller,
|
|
223
216
|
`c.*` = a constant. Pick by what you're pointing at.
|
|
217
|
+
- **Tagged values are DATA — a JS template literal cannot compose them.** `${ref(...)}`
|
|
218
|
+
(or `"a" + ref(...)`) stringifies the tag object at BUILD time: ``c.text(`Hi ${ref("u.name")}`)``
|
|
219
|
+
type-checks and encodes the literal text `Hi [object Object]`, served verbatim.
|
|
220
|
+
`export()` warns; `--strict` fails. Compose at RUNTIME:
|
|
221
|
+
`withFilters(c.text("Hi "), fl.concat(ref("u.name")))`, an `obj({...})`/record member,
|
|
222
|
+
or `c.expression('"Hi, " ~ $var.u.name')`.
|
|
224
223
|
- **`s.api.call` / `s.task.call` / `s.trigger.call` / `s.workflow_test.call` are
|
|
225
224
|
WORKFLOW-TEST ONLY.** Outside a `workflowTest({...})` stack the engine cannot reach
|
|
226
225
|
the target, so one in a query/function/task deploys clean and then answers the first
|
|
@@ -301,15 +300,16 @@ Non-obvious authoring rules:
|
|
|
301
300
|
- **Self-referencing tables** need the bare-name form: inside `tweets`'s own
|
|
302
301
|
schema, write `f.tableRef("tweets", { type: "int" })` — the `const tweets`
|
|
303
302
|
handle isn't assigned yet, so the handle form throws "used before declaration".
|
|
304
|
-
- **Same-name siblings collide
|
|
305
|
-
|
|
306
|
-
`export()` throws
|
|
307
|
-
`
|
|
308
|
-
`
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
303
|
+
- **Same-name siblings collide — queries excepted: their identity carries group + verb.**
|
|
304
|
+
Guids derive from `(type, name)`, so two functions (or tables, toolsets, …) sharing
|
|
305
|
+
a name derive ONE guid and `export()` throws — give them DISTINCT names. A QUERY
|
|
306
|
+
derives from `(api group, verb, name)`, the engine's own uniqueness: `GET items` +
|
|
307
|
+
`POST items`, and `items` across two groups, coexist, and the lock keys the same
|
|
308
|
+
composed identity (`query:blog|GET|items`) — changing a query's verb or group
|
|
309
|
+
changes its identity. An explicit `guid` on a colliding pair clears the throw but
|
|
310
|
+
is NOT a lasting fix: a lock entry cannot hold two guids, so `export --lock`
|
|
311
|
+
refuses the pair (`export()` warns even unlocked); it is for pinning identity
|
|
312
|
+
across a rename, not for sharing a name.
|
|
313
313
|
- **`export()` vs `emitBundle()` vs `writeBundle()`:** `writeBundle(app, path)`
|
|
314
314
|
writes it to disk; `export()` returns the bundle object;
|
|
315
315
|
`emitBundle()` returns the pretty JSON string. All three run the SAME build-time
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xanots",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "TypeScript SDK that compiles a typed Xano workspace into the importable packageExport JSON bundle.",
|
|
5
5
|
"coverage": {
|
|
6
6
|
"objectKinds": {
|
|
@@ -357,7 +357,7 @@
|
|
|
357
357
|
"kind": "microservice",
|
|
358
358
|
"payloadKey": "microservice",
|
|
359
359
|
"authorFactory": "microservice",
|
|
360
|
-
"description": "A container workload deployed alongside the workspace, called from a stack with `s.microservice.request`.
|
|
360
|
+
"description": "A container workload deployed alongside the workspace, called from a stack with `s.microservice.request`. Def shapes (`builtin` containers vs a `helm` chart) and the SECRET-handling rules its config carries: `llms/statements-calls.md`.",
|
|
361
361
|
"registerMethod": "registerMicroservices",
|
|
362
362
|
"registered": true
|
|
363
363
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xanots/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "XanoTS — your Xano backend as TypeScript. `xanots deploy` ships your typed workspace (and an optional static frontend) to a live, auto-expiring ephemeral environment and prints its URL. init → deploy → URL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"xanots",
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
projectShellFiles,
|
|
3
|
-
runInitCommand,
|
|
4
|
-
sanitizeAppName
|
|
5
|
-
} from "./chunk-KXCPZUUM.js";
|
|
6
|
-
import {
|
|
7
|
-
resolveAiFlags
|
|
8
|
-
} from "./chunk-7SISR3CS.js";
|
|
9
|
-
import "./chunk-ZSYZTGJH.js";
|
|
10
|
-
import "./chunk-WGDAOOXG.js";
|
|
11
|
-
import "./chunk-QUUB7HYK.js";
|
|
12
|
-
import "./chunk-6DHJVLYP.js";
|
|
13
|
-
import "./chunk-MS3BRZAZ.js";
|
|
14
|
-
import "./chunk-IVP6TS26.js";
|
|
15
|
-
import "./chunk-MFIHIS6B.js";
|
|
16
|
-
import "./chunk-GNPVYOPB.js";
|
|
17
|
-
import "./chunk-EZG76F7R.js";
|
|
18
|
-
import "./chunk-BPEEGJJA.js";
|
|
19
|
-
import "./chunk-F5T45NAM.js";
|
|
20
|
-
import "./chunk-YUPQOLFX.js";
|
|
21
|
-
import "./chunk-WHOJWOSV.js";
|
|
22
|
-
import "./chunk-5XZ744TS.js";
|
|
23
|
-
import "./chunk-YUBJLB6G.js";
|
|
24
|
-
export {
|
|
25
|
-
projectShellFiles,
|
|
26
|
-
resolveAiFlags,
|
|
27
|
-
runInitCommand,
|
|
28
|
-
sanitizeAppName
|
|
29
|
-
};
|
|
30
|
-
//# sourceMappingURL=init-command-NCRPVFGE.js.map
|