@fadhilp/stateql 0.12.0 → 0.13.1
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/README.md +53 -749
- package/dist/src/adapters.d.ts +1 -0
- package/dist/src/adapters.js +60 -0
- package/dist/src/sql.d.ts +3 -1
- package/dist/src/sql.js +520 -26
- package/dist/src/sqlite-process.js +16 -0
- package/dist/src/stateql.js +14 -5
- package/docs/databases.md +225 -0
- package/docs/library.md +268 -0
- package/docs/usage.md +352 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# StateQL
|
|
2
2
|
|
|
3
3
|
StateQL is a stateful database CLI and TypeScript library for AI agents and
|
|
4
|
-
automation.
|
|
5
|
-
|
|
6
|
-
reusable and operations traceable across commands.
|
|
4
|
+
automation. Query, change, and inspect databases while keeping results reusable
|
|
5
|
+
and operations traceable across commands.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
- **SQL:** SQLite, PostgreSQL, and MySQL.
|
|
8
|
+
- **Native commands:** MongoDB (EJSON) and Redis (JSON).
|
|
9
|
+
- **Durable handles:** reuse, filter, page, count, alias, or export stored results
|
|
10
|
+
without rerunning the original query.
|
|
11
|
+
- **Tracked writes:** inspect operations, review plans, and stage transactions
|
|
12
|
+
where supported by the database.
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
2. Reuse, filter, page, count, alias, or export that stored result without
|
|
12
|
-
rerunning the original SQL.
|
|
13
|
-
3. Use operation, plan, and transaction handles to inspect and control writes.
|
|
14
|
-
|
|
15
|
-
Requires Node.js 22.16 or newer for the required `node:sqlite` APIs.
|
|
14
|
+
Requires **Node.js 22.16 or newer** for the required `node:sqlite` APIs.
|
|
16
15
|
|
|
17
16
|
## Quick start
|
|
18
17
|
|
|
@@ -22,11 +21,13 @@ Install the CLI:
|
|
|
22
21
|
npm install -g @fadhilp/stateql
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
Connect to an existing SQLite database
|
|
24
|
+
Connect to an existing SQLite database containing a `users` table. These shell
|
|
25
|
+
examples use Bash; see the [usage guide](docs/usage.md) for connection options and
|
|
26
|
+
SQL parameter handling.
|
|
26
27
|
|
|
27
28
|
```bash
|
|
28
29
|
export STQL_SESSION=audit
|
|
29
|
-
stql profile add local ./app.sqlite
|
|
30
|
+
stql profile add local ./app.sqlite --read-only
|
|
30
31
|
stql connect local
|
|
31
32
|
|
|
32
33
|
stql query \
|
|
@@ -37,759 +38,62 @@ stql query \
|
|
|
37
38
|
|
|
38
39
|
Parameters keep values separate from SQL. `ORDER BY` makes paging stable, and
|
|
39
40
|
`LIMIT` bounds work at the database. The default `agent` output is compact,
|
|
40
|
-
one-line JSON:
|
|
41
|
-
|
|
42
|
-
```json
|
|
43
|
-
{"ok":true,"handle":"q_k7m2v5x9c3d6f8h4j2n7p5r9tw","rows":[{"id":7,"name":"Ada","email":"ada@example.com"},{"id":12,"name":"Grace","email":"grace@example.com"},{"id":18,"name":"Linus","email":"linus@kernel.org"}],"truncated":false,"cached":false,"total":3,"next_offset":null}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
`q_k7m2v5x9c3d6f8h4j2n7p5r9tw` is a durable snapshot. Filter it locally without accessing the original
|
|
47
|
-
database:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
stql filter q_k7m2v5x9c3d6f8h4j2n7p5r9tw "email LIKE ?" --param "%@example.com"
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{"ok":true,"handle":"q_z4n8c2v6b3m7k5j9h2g4f6d8sa","rows":[{"id":7,"name":"Ada","email":"ada@example.com"},{"id":12,"name":"Grace","email":"grace@example.com"}],"truncated":false,"cached":false,"total":2,"next_offset":null}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The filtered snapshot receives its own handle. Give it a readable alias, page
|
|
58
|
-
through it, inspect its count, or export it without rerunning SQL:
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
stql alias set example-users q_z4n8c2v6b3m7k5j9h2g4f6d8sa
|
|
62
|
-
stql rows example-users --offset 0 --limit 1
|
|
63
|
-
stql rows example-users --offset 1 --limit 1
|
|
64
|
-
stql count example-users
|
|
65
|
-
stql export example-users --output example-users.csv --format csv
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Example first page:
|
|
41
|
+
one-line JSON. An example response:
|
|
69
42
|
|
|
70
43
|
```json
|
|
71
|
-
{"ok":true,"handle":"
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Running the same normalized query with the same parameters reuses `q_k7m2v5x9c3d6f8h4j2n7p5r9tw` while
|
|
75
|
-
its cache entry is valid. Use `--cache bypass` when a fresh read is required.
|
|
76
|
-
|
|
77
|
-
## Connections and profiles
|
|
78
|
-
|
|
79
|
-
A connection accepts exactly one source: a direct target, `--env`,
|
|
80
|
-
`--credential-ref`, or `--profile`. Library and batch callers may additionally
|
|
81
|
-
attach `passwordRef`/`password_ref` to a literal password-free remote target;
|
|
82
|
-
it is not a fourth source.
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
stql connect <sqlite-path|postgres-url|mysql-url|mongodb-url> [--name NAME] [--read-write]
|
|
86
|
-
stql connect --env ENV [--name NAME] [--read-write]
|
|
87
|
-
stql connect --credential-ref REF [--name NAME] [--read-write]
|
|
88
|
-
stql connect --profile NAME
|
|
89
|
-
stql disconnect
|
|
90
|
-
stql status
|
|
44
|
+
{"ok":true,"handle":"q_k7m2v5x9c3d6f8h4j2n7p5r9tw","rows":[{"id":7,"name":"Ada","email":"ada@example.com"}],"truncated":false,"cached":false,"total":1,"next_offset":null}
|
|
91
45
|
```
|
|
92
46
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
PostgreSQL, MySQL, and MongoDB credentials should come from environment
|
|
96
|
-
variables. The variable must contain the complete connection URL, not only its
|
|
97
|
-
password. Environment-backed SQLite paths require an explicit `sqlite:` prefix.
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
export APP_DATABASE_URL='postgres://user:password@host/app'
|
|
101
|
-
stql connect --env APP_DATABASE_URL --name app --read-only
|
|
102
|
-
|
|
103
|
-
export MYSQL_DATABASE_URL='mysql://user:password@host/app'
|
|
104
|
-
stql connect --env MYSQL_DATABASE_URL --name mysql-app --read-only
|
|
105
|
-
|
|
106
|
-
export MONGODB_URL='mongodb://user:password@host/app'
|
|
107
|
-
stql connect --env MONGODB_URL --name mongo-app --read-only
|
|
108
|
-
|
|
109
|
-
export SQLITE_DATABASE='sqlite:./app.sqlite'
|
|
110
|
-
stql connect --env SQLITE_DATABASE --name local --read-only
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
StateQL stores no PostgreSQL, MySQL, MongoDB, or Redis password.
|
|
114
|
-
Credential-bearing URLs must be supplied through `--env` or an opaque full-URL
|
|
115
|
-
credential reference. SQLite paths remain persisted as connection metadata.
|
|
116
|
-
|
|
117
|
-
### Local profiles
|
|
118
|
-
|
|
119
|
-
Profiles store exactly one connection target, environment-variable name, or
|
|
120
|
-
opaque credential reference together with read-only policy. A remote literal
|
|
121
|
-
target may additionally store a `password_ref`; SQLite, environment-backed, and
|
|
122
|
-
full-URL `credential_ref` profiles cannot. Credential values are never stored.
|
|
123
|
-
Profiles persist under `STQL_HOME` with other StateQL metadata, and list/show
|
|
124
|
-
responses include nullable `credential_ref` and `password_ref` fields.
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
stql profile add local ./app.sqlite --read-write
|
|
128
|
-
stql profile add production --env PROD_DATABASE_URL --read-only
|
|
129
|
-
stql profile add hosted --credential-ref 'vault://team/app' --read-only
|
|
130
|
-
stql profile list
|
|
131
|
-
stql profile show production
|
|
132
|
-
stql connect local
|
|
133
|
-
stql connect --profile production
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Credential references are bounded nonempty opaque strings; StateQL does not
|
|
137
|
-
apply environment-variable syntax or normalization to them. They can only be
|
|
138
|
-
resolved by a trusted host `CredentialResolver`, so the standalone CLI may
|
|
139
|
-
store them in profiles but cannot connect with them.
|
|
140
|
-
|
|
141
|
-
Library callers can keep nonsecret endpoint, username, database, TLS, and CA
|
|
142
|
-
options in the literal URL while resolving only its password:
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
await stateql.connect(
|
|
146
|
-
"postgres://app@db.example/app?sslmode=verify-full&sslrootcert=/etc/app-ca.pem",
|
|
147
|
-
{ passwordRef: "vault://database/app/password", readOnly: true },
|
|
148
|
-
);
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
The same field is accepted by `addProfile`, `updateProfile`, and batch
|
|
152
|
-
`connect`/`profile.add`/`profile.update` commands (snake case in batch input).
|
|
153
|
-
Targets with an embedded password or query parameters that override endpoint or
|
|
154
|
-
credential fields are rejected before credential resolution or driver access.
|
|
155
|
-
|
|
156
|
-
A bare connection target matching a profile name resolves to that profile;
|
|
157
|
-
otherwise it remains a path or database URL.
|
|
158
|
-
|
|
159
|
-
### Driver notes
|
|
160
|
-
|
|
161
|
-
- **SQLite:** use a filesystem path for direct connections or `sqlite:` for an
|
|
162
|
-
environment-backed path.
|
|
163
|
-
- **PostgreSQL:** StateQL preserves strict TLS verification by normalizing
|
|
164
|
-
`sslmode=prefer`, `require`, and `verify-ca` to `verify-full` before opening
|
|
165
|
-
the adapter. Use `sslmode=verify-full` explicitly for clarity. Setting
|
|
166
|
-
`uselibpqcompat=true` opts out and keeps libpq-compatible SSL semantics.
|
|
167
|
-
- **MySQL:** uses positional `?` parameters. MariaDB compatibility is not
|
|
168
|
-
currently claimed.
|
|
169
|
-
- **MongoDB:** supports `mongodb://` and `mongodb+srv://` URLs with an explicit
|
|
170
|
-
database path. SQL methods are rejected; use the native MongoDB methods below.
|
|
171
|
-
|
|
172
|
-
## CLI reference
|
|
173
|
-
|
|
174
|
-
```text
|
|
175
|
-
stql connect <sqlite-path|postgres-url|mysql-url|mongodb-url> [--name NAME] [--read-write]
|
|
176
|
-
stql connect --env ENV [--name NAME] [--read-write]
|
|
177
|
-
stql connect --profile NAME
|
|
178
|
-
stql disconnect
|
|
179
|
-
stql status
|
|
180
|
-
stql profile add|list|show|remove
|
|
181
|
-
stql session start|list|show|summary|close
|
|
182
|
-
stql query <sql> [--params JSON | --param VALUE...] [--cache auto|bypass|require]
|
|
183
|
-
stql filter <result-handle> <predicate> [--params JSON | --param VALUE...]
|
|
184
|
-
stql exec <sql> [--params JSON | --param VALUE...] [--idempotency-key KEY] [--replay]
|
|
185
|
-
[--allow-unbounded] [--allow-destructive]
|
|
186
|
-
stql mongo query|exec|plan '<EJSON command>' [--cache MODE] [--idempotency-key KEY]
|
|
187
|
-
[--replay] [--allow-unbounded] [--allow-destructive]
|
|
188
|
-
stql show|count|columns <result-handle>
|
|
189
|
-
stql rows <result-handle> [--offset N] [--limit N]
|
|
190
|
-
stql alias set <name> <result-handle>
|
|
191
|
-
stql export <result-handle> --output FILE [--format json|jsonl|csv]
|
|
192
|
-
stql inspect schema|table|collection|collections|columns|indexes|constraints [name]
|
|
193
|
-
stql transaction begin|status|commit|rollback [--isolation LEVEL]
|
|
194
|
-
stql plan <sql> [--allow-unbounded] [--allow-destructive]
|
|
195
|
-
stql apply <plan-handle>
|
|
196
|
-
stql history [--limit N]
|
|
197
|
-
stql receipt <operation-handle>
|
|
198
|
-
stql doctor
|
|
199
|
-
stql purge [expired|results|history|all]
|
|
200
|
-
stql capabilities
|
|
201
|
-
stql batch [commands.json|commands.jsonl|-] [--continue-on-error]
|
|
202
|
-
stql pipe [--continue-on-error]
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### SQL parameters
|
|
206
|
-
|
|
207
|
-
For shell-safe positional parameters, repeat `--param`. JSON scalars become
|
|
208
|
-
their native types; other values remain strings.
|
|
209
|
-
|
|
210
|
-
```powershell
|
|
211
|
-
stql exec "INSERT INTO users (name, status) VALUES (?, ?)" `
|
|
212
|
-
--param Ada --param trial
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Use `--params JSON` for a JSON array or named parameters. Use
|
|
216
|
-
`--params-file FILE` when JSON is awkward to quote; `--params-file -` reads
|
|
217
|
-
JSON from standard input.
|
|
218
|
-
|
|
219
|
-
### PostgreSQL diagnostics and maintenance
|
|
220
|
-
|
|
221
|
-
Run PostgreSQL plans through `query`:
|
|
222
|
-
|
|
223
|
-
```bash
|
|
224
|
-
stql query "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT * FROM jobs WHERE id = 42"
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Plain `EXPLAIN` may plan a structurally validated `SELECT`, `INSERT`, `UPDATE`,
|
|
228
|
-
or `DELETE`. Because `EXPLAIN ANALYZE` executes its inner statement, StateQL
|
|
229
|
-
accepts only a validated read-only `SELECT`; `SELECT INTO`, writing CTEs, and
|
|
230
|
-
mutations are rejected. Diagnostics execute inside PostgreSQL `BEGIN READ ONLY`
|
|
231
|
-
and are never reused from cache. `--cache require` therefore returns
|
|
232
|
-
`CACHE_MISS` without executing the diagnostic.
|
|
233
|
-
|
|
234
|
-
`VACUUM`, `ANALYZE`, `REINDEX`, and `CLUSTER` are PostgreSQL maintenance writes:
|
|
235
|
-
|
|
236
|
-
```bash
|
|
237
|
-
stql exec "VACUUM (ANALYZE) public.jobs" --allow-destructive
|
|
238
|
-
stql plan "REINDEX TABLE public.jobs" --allow-destructive
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
They require a read-write connection and `--allow-destructive`, reject StateQL
|
|
242
|
-
parameters, and run as individually tracked autocommit operations. They cannot
|
|
243
|
-
be staged in a StateQL transaction. A timeout or cancellation after dispatch is
|
|
244
|
-
reported as `OUTCOME_UNKNOWN`; inspect database state before replaying it. Raw
|
|
245
|
-
`BEGIN`, `COMMIT`, `ROLLBACK`, savepoint, and other transaction-control SQL
|
|
246
|
-
remain unsupported—use `stql transaction` commands instead. See
|
|
247
|
-
[`SQL_COMMAND_ROADMAP.md`](SQL_COMMAND_ROADMAP.md) for the exact implemented
|
|
248
|
-
boundary and deferred command categories.
|
|
249
|
-
|
|
250
|
-
### Native MongoDB
|
|
251
|
-
|
|
252
|
-
MongoDB commands use official Extended JSON (EJSON), so BSON values survive the
|
|
253
|
-
CLI boundary:
|
|
254
|
-
|
|
255
|
-
```bash
|
|
256
|
-
stql mongo query '{"operation":"find","collection":"users","filter":{"_id":{"$oid":"507f1f77bcf86cd799439011"}}}'
|
|
257
|
-
stql mongo exec '{"operation":"updateOne","collection":"users","filter":{"_id":{"$oid":"507f1f77bcf86cd799439011"}},"update":{"$set":{"seen_at":{"$date":"2026-01-01T00:00:00Z"}}}}'
|
|
258
|
-
stql mongo plan '{"operation":"deleteMany","collection":"users","filter":{"disabled":true}}' --allow-destructive
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
The TypeScript equivalents are `mongoQuery(command)`, `mongoExec(command)`, and
|
|
262
|
-
`mongoPlan(command)`. Supported reads are `find` and `aggregate`; writes are
|
|
263
|
-
`insertOne`, `insertMany`, `updateOne`, `updateMany`, `replaceOne`, `deleteOne`,
|
|
264
|
-
and `deleteMany`. Result documents are JSON-safe, order-preserving EJSON: for example,
|
|
265
|
-
ObjectIds and dates appear as `{ "$oid": "..." }` and
|
|
266
|
-
`{ "$date": { "$numberLong": "..." } }`.
|
|
267
|
-
|
|
268
|
-
Empty update, replacement, or delete filters require `--allow-unbounded`;
|
|
269
|
-
deletes and replacements also require `--allow-destructive`. Mongo inspection accepts `collections`,
|
|
270
|
-
`collection`, `columns`, `indexes`, and `constraints` (`schema` and `table`
|
|
271
|
-
remain aliases shared with SQL drivers). MongoDB cache confidence is TTL-based:
|
|
272
|
-
external writes are not detected, so use `--cache bypass` for a fresh read.
|
|
273
|
-
|
|
274
|
-
```ts
|
|
275
|
-
const result = await stateql.mongoQuery({
|
|
276
|
-
operation: "find",
|
|
277
|
-
collection: "users",
|
|
278
|
-
filter: { active: true },
|
|
279
|
-
options: { sort: { _id: 1 }, limit: 50 },
|
|
280
|
-
});
|
|
281
|
-
```
|
|
282
|
-
### Output modes
|
|
283
|
-
|
|
284
|
-
CLI output defaults to compact, one-line `agent` JSON. Successful responses
|
|
285
|
-
flatten useful data and expose the primary durable ID as `handle`. Errors retain
|
|
286
|
-
their complete error object. Empty warnings and tracing metadata are omitted.
|
|
287
|
-
|
|
288
|
-
```json
|
|
289
|
-
{"ok":false,"error":{"code":"UNBOUNDED_MUTATION","message":"Mutation has no WHERE clause.","retryable":false,"executed":false,"override_flag":"--allow-unbounded"}}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
Other modes are:
|
|
293
|
-
|
|
294
|
-
- `--output json`: original pretty, verbose envelope.
|
|
295
|
-
- `--output jsonl`: verbose envelope on one line.
|
|
296
|
-
- `--output text`: short human-readable status.
|
|
297
|
-
- `--output silent`: only a successful handle.
|
|
298
|
-
|
|
299
|
-
Set `STQL_OUTPUT` to choose a mode globally. For `export`, `--output` names the
|
|
300
|
-
file, so use `STQL_OUTPUT` to choose the command's response mode. Library
|
|
301
|
-
responses always keep the full envelope.
|
|
302
|
-
|
|
303
|
-
### Deadlines and cancellation
|
|
304
|
-
|
|
305
|
-
Database commands accept `--timeout-ms N`; the default is 30,000 ms. `Ctrl+C`
|
|
306
|
-
cancels active work.
|
|
307
|
-
|
|
308
|
-
- SQLite runs in a killable child process so long synchronous statements cannot
|
|
309
|
-
block StateQL's event loop.
|
|
310
|
-
- PostgreSQL combines server-side `statement_timeout` with client deadlines.
|
|
311
|
-
- MySQL deadlines destroy the active connection.
|
|
312
|
-
- MongoDB uses driver deadlines and closes stopped operations.
|
|
313
|
-
|
|
314
|
-
A timed-out or cancelled write may return `OUTCOME_UNKNOWN` when its commit
|
|
315
|
-
status cannot be proven. Cancellation stops that command's driver work; it does
|
|
316
|
-
not close the `StateQL` actor, and later commands remain usable.
|
|
317
|
-
|
|
318
|
-
## Durable state and result reuse
|
|
319
|
-
|
|
320
|
-
State metadata lives under `STQL_HOME`, or the platform data directory when
|
|
321
|
-
unset. StateQL keeps connections, sessions, handles, aliases, cache entries,
|
|
322
|
-
plans, transactions, history, and receipts available across CLI invocations.
|
|
323
|
-
|
|
324
|
-
### Sessions and actors
|
|
325
|
-
|
|
326
|
-
Set `STQL_SESSION` to select a named session and `STQL_ACTOR` to select an
|
|
327
|
-
attached actor. A session is a shared workspace: attached actors reuse its
|
|
328
|
-
connection, handles, aliases, cache, and state version. Plans and staged
|
|
329
|
-
transactions remain owned by the actor that created them.
|
|
330
|
-
|
|
331
|
-
Callers that omit `actor` keep the legacy behavior where the actor ID is the
|
|
332
|
-
session name.
|
|
333
|
-
|
|
334
|
-
### Result lifetime and limits
|
|
335
|
-
|
|
336
|
-
SQLite result rows are materialized locally for durable access. Read cache
|
|
337
|
-
entries expire after five minutes, and materialized handles expire after 24
|
|
338
|
-
hours. Expired results and plans are deleted the next time StateQL opens.
|
|
339
|
-
|
|
340
|
-
Queries exceeding 10,000 rows or 16 MiB of serialized row data fail before
|
|
341
|
-
persistence. Narrow the `WHERE` clause, add `LIMIT`, or select fewer columns.
|
|
342
|
-
These caps bound persisted materialization; the independent deadline bounds
|
|
343
|
-
execution time.
|
|
344
|
-
|
|
345
|
-
Command history keeps the latest 10,000 entries per session. SQLite cache reuse
|
|
346
|
-
also checks the database file signature. PostgreSQL, MySQL, and MongoDB cache
|
|
347
|
-
reuse is labeled `ttl_based` and is never authoritative.
|
|
348
|
-
|
|
349
|
-
StateQL limits persisted result payloads to 256 MiB by default. When that quota
|
|
350
|
-
is reached it removes the oldest unaliased results; aliases remain protected. A
|
|
351
|
-
single result that cannot fit fails with `STATE_QUOTA_EXCEEDED`. Configure the
|
|
352
|
-
limit with `maxStateBytes` in the library or `--max-state-bytes` in the CLI.
|
|
353
|
-
Cache and result retention can be configured with `cacheTtlSeconds` and
|
|
354
|
-
`resultTtlSeconds`, or their `--cache-ttl-seconds` and
|
|
355
|
-
`--result-ttl-seconds` CLI equivalents.
|
|
356
|
-
|
|
357
|
-
`stql doctor` checks SQLite integrity and stored payload shapes without printing
|
|
358
|
-
SQL, parameters, or result values. `stql purge` removes expired data by default;
|
|
359
|
-
use `results`, `history`, or `all` for explicit session cleanup. On POSIX
|
|
360
|
-
systems, StateQL removes group and world access from its state directory,
|
|
361
|
-
database, and SQLite sidecar files.
|
|
362
|
-
|
|
363
|
-
### Local filtering
|
|
364
|
-
|
|
365
|
-
`filter` evaluates one scalar SQLite predicate against a stored result. It
|
|
366
|
-
preserves source order, state metadata, and expiry, and never accesses the
|
|
367
|
-
original database.
|
|
368
|
-
|
|
369
|
-
Use parameters for values. Subqueries, query-shaping clauses, and
|
|
370
|
-
non-allowlisted functions are rejected. Common deterministic functions such as
|
|
371
|
-
`lower`, `upper`, `length`, and `coalesce` are supported.
|
|
372
|
-
|
|
373
|
-
## Write safety
|
|
374
|
-
|
|
375
|
-
Destructive and unbounded operations require `--allow-destructive` and
|
|
376
|
-
`--allow-unbounded`, respectively. The flags are independent.
|
|
377
|
-
|
|
378
|
-
`plan` validates and stores a write for later application. A plan persists only
|
|
379
|
-
the flags explicitly supplied when it is created; `apply` never adds
|
|
380
|
-
authorization.
|
|
381
|
-
|
|
382
|
-
Use an idempotency key to protect retryable writes from duplicate execution:
|
|
47
|
+
Use the handle returned by your query in place of the example handle below.
|
|
48
|
+
Give the snapshot a readable alias, then work with the stored data:
|
|
383
49
|
|
|
384
50
|
```bash
|
|
385
|
-
stql
|
|
386
|
-
|
|
387
|
-
|
|
51
|
+
stql alias set active-users q_k7m2v5x9c3d6f8h4j2n7p5r9tw
|
|
52
|
+
stql rows active-users --offset 0 --limit 10
|
|
53
|
+
stql count active-users
|
|
54
|
+
stql export active-users --output active-users.csv --format csv
|
|
55
|
+
stql filter active-users "email LIKE ?" --param "%@example.com"
|
|
388
56
|
```
|
|
389
57
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
records become `outcome_unknown` after five minutes.
|
|
58
|
+
These commands do not access the original database. `filter` creates a new
|
|
59
|
+
snapshot with its own handle. Repeating a query can reuse a valid cached result;
|
|
60
|
+
use `--cache bypass` when a fresh database read is required.
|
|
394
61
|
|
|
395
|
-
|
|
62
|
+
## Safety and state
|
|
396
63
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
64
|
+
- Connections are **read-only by default**. Writes require a read-write connection.
|
|
65
|
+
- Destructive and unbounded SQL/MongoDB operations require independent
|
|
66
|
+
`--allow-destructive` and `--allow-unbounded` approvals, respectively.
|
|
67
|
+
- `plan` stores a write for review; `apply` never adds authorization. Use
|
|
68
|
+
idempotency keys to protect retryable writes from duplicate execution.
|
|
69
|
+
- A write reported as `OUTCOME_UNKNOWN` must be inspected at the database before
|
|
70
|
+
replaying it. Transactions are staged locally, not live interactive SQL
|
|
71
|
+
transactions.
|
|
72
|
+
- Supply credential-bearing URLs through environment variables or a trusted
|
|
73
|
+
library credential resolver, not literal command arguments or profiles.
|
|
74
|
+
- Results and history persist under `STQL_HOME` (or the platform data directory).
|
|
75
|
+
Treat that state as database data; retention and size limits are configurable.
|
|
401
76
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
reject DDL because MySQL implicitly commits those statements.
|
|
406
|
-
MongoDB transactions use `snapshot` isolation and require a replica set or
|
|
407
|
-
sharded deployment; standalone servers do not support them.
|
|
77
|
+
See [write safety](docs/usage.md#write-safety),
|
|
78
|
+
[credentials](docs/usage.md#environment-backed-credentials), and
|
|
79
|
+
[database restrictions](docs/databases.md) before using production data.
|
|
408
80
|
|
|
409
|
-
##
|
|
81
|
+
## Documentation
|
|
410
82
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
83
|
+
| Guide | Contents |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| [Usage](docs/usage.md) | Connections, CLI reference, results, sessions, limits, writes, transactions, batch and pipes |
|
|
86
|
+
| [Database support](docs/databases.md) | SQL dialects, diagnostics, maintenance, native MongoDB and Redis commands |
|
|
87
|
+
| [TypeScript library](docs/library.md) | Connection setup, response handling, cleanup, actors, workspaces, and credential resolution |
|
|
88
|
+
| [SQL command roadmap](SQL_COMMAND_ROADMAP.md) | Development priorities and requirements for extending SQL support |
|
|
415
89
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
```bash
|
|
419
|
-
printf '%s\n' \
|
|
420
|
-
'{"command":"query","sql":"SELECT id, email FROM users ORDER BY id","as":"users"}' \
|
|
421
|
-
'{"command":"filter","handle":"users","where":"email LIKE ?","params":["%@example.com"],"as":"example_users"}' \
|
|
422
|
-
'{"command":"rows","handle":"example_users","limit":10}' |
|
|
423
|
-
stql pipe
|
|
424
|
-
```
|
|
90
|
+
Run `stql --help` for command syntax or `stql capabilities` for capability details.
|
|
425
91
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
```json
|
|
429
|
-
[
|
|
430
|
-
{
|
|
431
|
-
"command": "exec",
|
|
432
|
-
"sql": "UPDATE jobs SET claimed = 1 WHERE id = ?",
|
|
433
|
-
"params": [42],
|
|
434
|
-
"idempotency_key": "claim-job-42"
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
"command": "query",
|
|
438
|
-
"sql": "SELECT * FROM jobs WHERE id = ?",
|
|
439
|
-
"params": [42]
|
|
440
|
-
}
|
|
441
|
-
]
|
|
442
|
-
```
|
|
92
|
+
## TypeScript installation
|
|
443
93
|
|
|
444
94
|
```bash
|
|
445
|
-
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
Batch fields use snake case. Supported command names match CLI paths, such as
|
|
449
|
-
`filter`, `transaction.begin`, `session.summary`, `alias.set`, `plan`, and
|
|
450
|
-
`apply`. Native MongoDB batches use `mongo.query`, `mongo.exec`, or `mongo.plan`
|
|
451
|
-
with the command object in `mongo`; the same cache, replay, idempotency, safety,
|
|
452
|
-
and timeout fields apply. Database commands may set `timeout_ms`; otherwise they
|
|
453
|
-
use the 30-second default.
|
|
454
|
-
|
|
455
|
-
## TypeScript library
|
|
456
|
-
|
|
457
|
-
The package exports the same stateful operations for programmatic use. Library
|
|
458
|
-
responses retain the full response envelope regardless of the configured CLI
|
|
459
|
-
output mode.
|
|
460
|
-
|
|
461
|
-
```ts
|
|
462
|
-
import { StateQL } from "@fadhilp/stateql";
|
|
463
|
-
|
|
464
|
-
const stateql = StateQL.forActor({
|
|
465
|
-
home: "./.stql",
|
|
466
|
-
actor: "pi-session-id",
|
|
467
|
-
timeoutMs: 30_000,
|
|
468
|
-
credentialTimeoutMs: 120_000,
|
|
469
|
-
maxResultBytes: 16 * 1024 * 1024,
|
|
470
|
-
maxStateBytes: 256 * 1024 * 1024,
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
const controller = new AbortController();
|
|
474
|
-
const response = await stateql.query("SELECT * FROM users", {
|
|
475
|
-
signal: controller.signal,
|
|
476
|
-
timeoutMs: 5_000,
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
if (response.ok) {
|
|
480
|
-
const handle = (response.data as { result_id: string }).result_id;
|
|
481
|
-
await stateql.filter(handle, "email LIKE ?", {
|
|
482
|
-
params: ["%@example.com"],
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
Hosts that dispatch batch-shaped commands can attach trusted metadata out of
|
|
488
|
-
band. `origin` is audit/source metadata only; it never changes actor membership,
|
|
489
|
-
workspace access, or write authorization.
|
|
490
|
-
|
|
491
|
-
```ts
|
|
492
|
-
const controller = new AbortController();
|
|
493
|
-
await stateql.executeCommand(
|
|
494
|
-
{ command: "query", sql: "SELECT * FROM users", cache: "bypass" },
|
|
495
|
-
{ origin: "user", signal: controller.signal },
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
const userHistory = await stateql.history(50, { origin: "user" });
|
|
499
|
-
await stateql.executeCommand(
|
|
500
|
-
{ command: "history", limit: 50, history_origin: "user" },
|
|
501
|
-
{ origin: "model" },
|
|
502
|
-
);
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
Supported origins are `legacy`, `user`, `model`, `system`, and `api`. Existing
|
|
506
|
-
direct calls and `executeCommand(command)` calls are recorded as `legacy`.
|
|
507
|
-
`history_origin` is only a retrieval filter; putting an `origin` field in a
|
|
508
|
-
`BatchCommand` cannot attribute the command. `batch` accepts the same trusted
|
|
509
|
-
context as `options.executionContext` for all commands in that batch.
|
|
510
|
-
|
|
511
|
-
### Actor workspaces
|
|
512
|
-
|
|
513
|
-
`StateQL.forWorkspace(...)` is a trusted-host primitive that atomically creates
|
|
514
|
-
or reopens a durable workspace, attaches the requested actor, and returns a
|
|
515
|
-
client bound to that actor:
|
|
516
|
-
|
|
517
|
-
```ts
|
|
518
|
-
const stateql = StateQL.forWorkspace({
|
|
519
|
-
home: "./.stql",
|
|
520
|
-
workspace: "pylon-global",
|
|
521
|
-
actor: "pylon-session:abc123",
|
|
522
|
-
credentialResolver,
|
|
523
|
-
signal,
|
|
524
|
-
});
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
Repeated opens of the same actor and workspace are idempotent. An actor already
|
|
528
|
-
attached elsewhere fails with a `StateQLError` whose code is
|
|
529
|
-
`PERMISSION_DENIED`; StateQL never moves or merges it. All actor options,
|
|
530
|
-
including limits, credential resolution, cancellation, `home`, and `now`, are
|
|
531
|
-
preserved. The workspace name also reserves a same-named actor identity for
|
|
532
|
-
legacy compatibility, so workspace and actor identifiers must be globally
|
|
533
|
-
collision-free. The returned client is still bound only to `actor`, preserving
|
|
534
|
-
plan, transaction, operation, and history ownership.
|
|
535
|
-
|
|
536
|
-
`StateQL.forActor(...)` retains its existing behavior: it resolves the actor's
|
|
537
|
-
attached session directly from StateQL storage and creates a legacy-compatible
|
|
538
|
-
session named after the actor on first use. Use `new StateQL({ session, actor })`
|
|
539
|
-
when the session and membership are already known.
|
|
540
|
-
|
|
541
|
-
Membership management and `forWorkspace` are library-only host capabilities,
|
|
542
|
-
not batch or CLI commands. Existing member-authorized management remains
|
|
543
|
-
available through `linkActor(session, actorId)`, `unlinkActor(session, actorId)`,
|
|
544
|
-
`listActors(session)`, and `resolveActor(actorId)`. Integrations should ask for
|
|
545
|
-
user confirmation before changing membership or the shared connection; a host
|
|
546
|
-
calling `forWorkspace` is responsible for authorizing that workspace access.
|
|
547
|
-
|
|
548
|
-
### Harness credential resolution
|
|
549
|
-
|
|
550
|
-
Library integrations can resolve environment-variable names, opaque full-URL
|
|
551
|
-
credential references, or password-only references through a trusted approval
|
|
552
|
-
or secret-storage layer instead of mutating `process.env`:
|
|
553
|
-
|
|
554
|
-
Integrations pinned to an older published package should gate setup before
|
|
555
|
-
sending `password_ref`:
|
|
556
|
-
|
|
557
|
-
```ts
|
|
558
|
-
if ((StateQL.passwordReferenceVersion ?? 0) < 1) {
|
|
559
|
-
throw new Error("Installed StateQL does not support password references.");
|
|
560
|
-
}
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
`passwordReferenceVersion = 1` guarantees the password-only resolver request,
|
|
564
|
-
validation, persistence, reconnect, and redaction contract documented below.
|
|
565
|
-
|
|
566
|
-
```ts
|
|
567
|
-
import {
|
|
568
|
-
CredentialResolutionError,
|
|
569
|
-
StateQL,
|
|
570
|
-
type CredentialRequest,
|
|
571
|
-
} from "@fadhilp/stateql";
|
|
572
|
-
|
|
573
|
-
async function resolveCredential(
|
|
574
|
-
request: CredentialRequest,
|
|
575
|
-
): Promise<string | undefined> {
|
|
576
|
-
const approved = await credentialBroker.request({
|
|
577
|
-
reference: request.reference,
|
|
578
|
-
source: request.source ?? "secret_env",
|
|
579
|
-
actor: request.actorId,
|
|
580
|
-
session: request.session.id,
|
|
581
|
-
operation: request.operation,
|
|
582
|
-
access: request.access,
|
|
583
|
-
signal: request.signal,
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
if (approved.denied) throw new CredentialResolutionError("denied");
|
|
587
|
-
return approved.value;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const stateql = StateQL.forActor({
|
|
591
|
-
actor: "agent-session-id",
|
|
592
|
-
credentialResolver: resolveCredential,
|
|
593
|
-
});
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
Credential resolution has its own two-minute default deadline
|
|
597
|
-
(`credentialTimeoutMs`) and remains cancellable through `request.signal`.
|
|
598
|
-
The database-operation timeout begins after a credential is resolved.
|
|
599
|
-
|
|
600
|
-
When no custom resolver is configured, StateQL reads only `secret_env`
|
|
601
|
-
references from `process.env`; `credential_ref` and `password_ref` never fall
|
|
602
|
-
back to the environment. A configured resolver is authoritative for all
|
|
603
|
-
sources: returning `undefined` produces `CREDENTIAL_UNAVAILABLE` and never falls
|
|
604
|
-
back to the process environment. Resolver requests retain `reference` and
|
|
605
|
-
include `source` (`secret_env`, `credential_ref`, or `password_ref`); source may
|
|
606
|
-
be omitted only on legacy secret-environment request objects. A `password_ref`
|
|
607
|
-
request additionally includes the exact password-free effective `target`.
|
|
608
|
-
Resolvers may throw `CredentialResolutionError` with `denied`, `cancelled`,
|
|
609
|
-
`timeout`, or `unavailable` to produce controlled, secret-free failures. Unknown
|
|
610
|
-
resolver errors are replaced with a generic `CREDENTIAL_RESOLUTION_FAILED`
|
|
611
|
-
response.
|
|
612
|
-
|
|
613
|
-
StateQL calls the resolver only immediately before database access, after SQL
|
|
614
|
-
safety and duplicate checks. Requests contain actor and session identity, the
|
|
615
|
-
operation's effective read/write access, an abort signal, and sanitized
|
|
616
|
-
connection metadata.
|
|
617
|
-
|
|
618
|
-
For `secret_env` and `credential_ref`, returned values must be complete
|
|
619
|
-
PostgreSQL, MySQL, MongoDB, or Redis URLs, or explicit `sqlite:` sources. For
|
|
620
|
-
`password_ref`, the resolver returns only the password; an explicit empty string
|
|
621
|
-
is a resolved password, while `undefined` fails closed. StateQL percent-encodes
|
|
622
|
-
and injects only that password into the original target for adapter use, leaving
|
|
623
|
-
all nonsecret URL/TLS/CA bytes unchanged. It persists only the original target
|
|
624
|
-
and reference. Resolved credentials never enter connection metadata, history,
|
|
625
|
-
snapshots, cache keys, responses, or stored errors.
|
|
626
|
-
|
|
627
|
-
Harnesses remain responsible for approval policy, binding lifetime, revocation,
|
|
628
|
-
and keeping values out of their own logs and model-visible data.
|
|
629
|
-
|
|
630
|
-
For writes, credential resolution happens after StateQL atomically reserves the
|
|
631
|
-
operation for duplicate protection. A resolution failure keeps a non-executed
|
|
632
|
-
`failed` audit record, does not consume the idempotency key, and permits a safe
|
|
633
|
-
retry.
|
|
634
|
-
|
|
635
|
-
## Pylon database integration API (0.9.0)
|
|
636
|
-
|
|
637
|
-
### Result identities and aliases
|
|
638
|
-
|
|
639
|
-
Every materialized SQL, MongoDB, Redis, table, or derived result keeps its
|
|
640
|
-
immutable canonical `q_*` `result_id`. New canonical resource IDs use a
|
|
641
|
-
cryptographically random 26-character lowercase base32 suffix; existing
|
|
642
|
-
incremental IDs such as `q_121` remain valid and are not rewritten. Results also
|
|
643
|
-
receive a random 10-character lowercase base32 `display_alias`.
|
|
644
|
-
`ResultData.alias` normally equals that alias. When a batch command supplies
|
|
645
|
-
`as`, `alias` remains the caller alias for backward compatibility while
|
|
646
|
-
`display_alias` remains canonical. Generated aliases are session-scoped,
|
|
647
|
-
allocated atomically with the result, stable on cache reuse, and cannot be
|
|
648
|
-
reassigned by `setAlias`; explicit aliases and all old handles continue to
|
|
649
|
-
resolve.
|
|
650
|
-
|
|
651
|
-
Connections likewise retain canonical `conn_*` IDs with random 26-character
|
|
652
|
-
suffixes and receive persistent random 10-character lowercase base32 aliases,
|
|
653
|
-
exposed as `alias` and `display_alias` by `connect()` and
|
|
654
|
-
`snapshot().connection` (optional in snapshot types for older producers).
|
|
655
|
-
Connection aliases are unique within the state store, allocated atomically with
|
|
656
|
-
the connection, and backfilled for existing records on startup. They survive
|
|
657
|
-
reopening; reconnecting creates a new ID and alias. They are display identities
|
|
658
|
-
only, separate from result aliases; internal references and lookups continue to
|
|
659
|
-
use canonical connection IDs.
|
|
660
|
-
|
|
661
|
-
### Safe profile updates
|
|
662
|
-
|
|
663
|
-
```ts
|
|
664
|
-
updateProfile(name, {
|
|
665
|
-
target?: string | null,
|
|
666
|
-
secretEnv?: string | null,
|
|
667
|
-
credentialRef?: string | null,
|
|
668
|
-
passwordRef?: string | null,
|
|
669
|
-
readOnly?: boolean,
|
|
670
|
-
})
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
Omitting all source and password-reference fields keeps the existing source and
|
|
674
|
-
adjunct reference. Supplying any source field replaces the source atomically:
|
|
675
|
-
exactly one non-null source is required and the other source columns are
|
|
676
|
-
cleared. An omitted `passwordRef` is preserved when the existing literal target
|
|
677
|
-
is unchanged; changing the target clears it unless the update explicitly supplies
|
|
678
|
-
a replacement. Replacing the source with `secretEnv` or `credentialRef` clears
|
|
679
|
-
it. An explicit non-null password reference combined with either
|
|
680
|
-
reference-backed source is rejected. Direct URLs
|
|
681
|
-
with embedded passwords or secret-like query parameters are rejected.
|
|
682
|
-
`profile.list/show/update` return only
|
|
683
|
-
`{profile,target,secret_env,credential_ref,password_ref,read_only}`. Profile
|
|
684
|
-
changes affect subsequent `connect` calls and do not silently mutate an
|
|
685
|
-
already-open connection.
|
|
686
|
-
|
|
687
|
-
The `password_refs_v1` migration adds nullable `password_ref` columns to both
|
|
688
|
-
profiles and connections and enforces that they accompany only literal target
|
|
689
|
-
configuration. It rejects incompatible profile schemas/rows instead of dropping
|
|
690
|
-
references. Downgrading a state home containing password references is
|
|
691
|
-
unsupported: older binaries do not resolve this source and may attempt the
|
|
692
|
-
password-free target using ambient/trust authentication; constraint-protected
|
|
693
|
-
source replacements may also fail. Use the same or newer StateQL binary, or
|
|
694
|
-
explicitly clear all password references before downgrade.
|
|
695
|
-
|
|
696
|
-
### Bounded catalog
|
|
697
|
-
|
|
698
|
-
```ts
|
|
699
|
-
listObjects(
|
|
700
|
-
{ kind?, schema?, search?, offset?, limit? },
|
|
701
|
-
{ timeoutMs?, signal? },
|
|
702
|
-
) -> { objects, next_offset, supported_kinds }
|
|
703
|
-
|
|
704
|
-
describeObject(
|
|
705
|
-
{ kind, schema?, name, identity? },
|
|
706
|
-
{ timeoutMs?, signal? },
|
|
707
|
-
) -> { object, definition? }
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
SQL/MongoDB offsets are non-negative numbers; limits default to 50 and are at
|
|
711
|
-
most 200. Redis `offset` and `next_offset` are opaque numeric SCAN cursor strings;
|
|
712
|
-
its limit is a SCAN `COUNT` hint with a hard 200-item response bound. Redis pages
|
|
713
|
-
are not snapshots and can be empty or contain duplicates while keys change.
|
|
714
|
-
Search is a case-insensitive name substring for SQL/MongoDB and escaped glob
|
|
715
|
-
substring matching for Redis. No exact counts are forced.
|
|
716
|
-
|
|
717
|
-
Supported kinds are returned on every page: SQLite `table,view,trigger`;
|
|
718
|
-
PostgreSQL `table,view,function,trigger,enum`; MySQL
|
|
719
|
-
`table,view,function,trigger`; MongoDB `collection,view`; Redis `key`.
|
|
720
|
-
PostgreSQL function identities include identity arguments, so overloads remain
|
|
721
|
-
distinct. `describeObject` is read-only and requires the structured identity;
|
|
722
|
-
legacy `inspect` behavior is unchanged (and intentionally unavailable for Redis).
|
|
723
|
-
|
|
724
|
-
### Reviewed multi-row table edits
|
|
725
|
-
|
|
726
|
-
```ts
|
|
727
|
-
planTableUpdates(
|
|
728
|
-
Array<{ row_token: string; changes: { set?: object; unset?: string[] } }>,
|
|
729
|
-
options?,
|
|
730
|
-
) -> PlanData
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
Batches contain 1-100 distinct row identities and at most 256 KiB. All tokens,
|
|
734
|
-
connection/state versions, expiries, metadata, editable columns, and values are
|
|
735
|
-
validated before one plan is stored; expiry is the earliest token expiry.
|
|
736
|
-
`apply(plan_id)` executes all conditional row updates in one SQLite/PostgreSQL/
|
|
737
|
-
MySQL transaction and requires every row predicate to match, otherwise all are
|
|
738
|
-
rolled back. MongoDB uses one snapshot transaction and rejects deployments that
|
|
739
|
-
do not support transactions. Redis and active staged StateQL transactions are
|
|
740
|
-
rejected. The existing `planTableUpdate` and `apply` APIs remain supported.
|
|
741
|
-
Plans are actor-owned, claimed once, and retained as non-replayable when the
|
|
742
|
-
remote commit outcome is uncertain.
|
|
743
|
-
|
|
744
|
-
### Redis native commands
|
|
745
|
-
|
|
746
|
-
Redis/Rediss URLs support URL database selection, password or ACL username,
|
|
747
|
-
and TLS (`rediss`). Credential-bearing URLs must come from `secretEnv` or
|
|
748
|
-
`credentialRef`. Native methods accept `{command: string, args?: string[]}`:
|
|
749
|
-
|
|
750
|
-
- `redisQuery`: `GET`, `MGET`, `TYPE`, `EXISTS`, `TTL`, `PTTL`, `HGET`, `HMGET`,
|
|
751
|
-
bounded `LRANGE`, and bounded `SCAN`/`HSCAN`/`SSCAN`/`ZSCAN`.
|
|
752
|
-
- `redisExec` and `redisPlan`: one-key `SET`, `DEL`, `HSET`, `HDEL`, `LPUSH`,
|
|
753
|
-
`RPUSH`, `SADD`, `SREM`, `ZADD`, or `ZREM` mutation.
|
|
754
|
-
- `describeObject({kind:"key",name})`: bounded string/hash/list/set/zset value
|
|
755
|
-
inspection with TTL and continuation metadata where applicable.
|
|
756
|
-
|
|
757
|
-
Arguments are UTF-8 strings, at most 100 values/256 KiB; materialized replies are
|
|
758
|
-
at most 1 MiB. `KEYS`, scripts, modules, pub/sub, blocking commands, admin/flush,
|
|
759
|
-
and arbitrary commands are rejected. Key discovery always uses SCAN. A Redis
|
|
760
|
-
plan snapshots one bounded key and `apply` uses an isolated `WATCH` + one-command
|
|
761
|
-
`MULTI/EXEC`; a pre-apply content or expiry change returns `ROW_CONFLICT` and is
|
|
762
|
-
never retried automatically. Direct `redisExec` has Redis single-command
|
|
763
|
-
atomicity only. Redis has no SQL rollback or StateQL staged transaction support;
|
|
764
|
-
a lost write/EXEC reply is reported as `OUTCOME_UNKNOWN` and remains blocked.
|
|
765
|
-
|
|
766
|
-
### Lean history
|
|
767
|
-
|
|
768
|
-
```ts
|
|
769
|
-
history(limit?, {
|
|
770
|
-
origin?,
|
|
771
|
-
category?: "statement" | "introspection" | "management",
|
|
772
|
-
internal?: boolean,
|
|
773
|
-
offset?: number,
|
|
774
|
-
})
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
`category` and trusted-host `internal` filters are applied in SQLite before
|
|
778
|
-
`ORDER BY`, `LIMIT`, and `OFFSET`, so introspection cannot starve statement
|
|
779
|
-
history. `CommandExecutionContext.internal` is trusted host metadata and cannot
|
|
780
|
-
be supplied inside a batch command. Existing calls and origin filtering remain
|
|
781
|
-
compatible; old rows are classified from their command name and migrate as `internal: false`.
|
|
782
|
-
|
|
783
|
-
The synchronous, non-mutating snapshot bridge accepts the same classification
|
|
784
|
-
filters without entering the command queue or writing a history row:
|
|
785
|
-
|
|
786
|
-
```ts
|
|
787
|
-
stateql.snapshot({
|
|
788
|
-
historyLimit: 50,
|
|
789
|
-
historyCategory: "statement",
|
|
790
|
-
historyInternal: false,
|
|
791
|
-
});
|
|
95
|
+
npm install @fadhilp/stateql
|
|
792
96
|
```
|
|
793
97
|
|
|
794
|
-
|
|
795
|
-
|
|
98
|
+
The package exports `StateQL` and public TypeScript types. Start with the
|
|
99
|
+
[connected, cleanup-safe example](docs/library.md#getting-started).
|