@fadhilp/stateql 0.4.2 → 0.5.0
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 +244 -127
- package/dist/src/adapters.js +3 -3
- package/dist/src/cli.js +25 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/migrations.d.ts +2 -0
- package/dist/src/migrations.js +305 -0
- package/dist/src/response-data.d.ts +5 -5
- package/dist/src/sqlite-process.js +2 -2
- package/dist/src/stateql.d.ts +39 -37
- package/dist/src/stateql.js +54 -10
- package/dist/src/store.d.ts +21 -3
- package/dist/src/store.js +221 -209
- package/dist/src/types.d.ts +239 -1
- package/dist/src/util.d.ts +5 -2
- package/dist/src/util.js +25 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
# StateQL
|
|
2
2
|
|
|
3
|
-
StateQL is a stateful database CLI for AI agents and
|
|
4
|
-
safe interface for querying, changing, and inspecting
|
|
5
|
-
|
|
3
|
+
StateQL is a stateful database CLI and TypeScript library for AI agents and
|
|
4
|
+
automation. It provides a safe interface for querying, changing, and inspecting
|
|
5
|
+
SQLite, PostgreSQL, and MySQL databases while keeping results reusable and
|
|
6
|
+
operations traceable across commands.
|
|
7
|
+
|
|
8
|
+
StateQL is built around durable handles:
|
|
9
|
+
|
|
10
|
+
1. Run a query and receive a result handle such as `q_1`.
|
|
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.
|
|
6
14
|
|
|
7
15
|
Requires Node.js 22.5 or newer.
|
|
8
16
|
|
|
9
17
|
## Quick start
|
|
10
18
|
|
|
19
|
+
Install the CLI:
|
|
20
|
+
|
|
11
21
|
```bash
|
|
12
22
|
npm install -g @fadhilp/stateql
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
Connect to an existing SQLite database
|
|
16
|
-
query. Parameters keep values separate from SQL; `ORDER BY` makes paging
|
|
17
|
-
stable, while `LIMIT` bounds work at the database.
|
|
25
|
+
Connect to an existing SQLite database and run a bounded, parameterized query:
|
|
18
26
|
|
|
19
27
|
```bash
|
|
20
28
|
export STQL_SESSION=audit
|
|
@@ -27,14 +35,16 @@ stql query \
|
|
|
27
35
|
--param 2026-01-01
|
|
28
36
|
```
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
Parameters keep values separate from SQL. `ORDER BY` makes paging stable, and
|
|
39
|
+
`LIMIT` bounds work at the database. The default `agent` output is compact,
|
|
40
|
+
one-line JSON:
|
|
31
41
|
|
|
32
42
|
```json
|
|
33
43
|
{"ok":true,"handle":"q_1","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}
|
|
34
44
|
```
|
|
35
45
|
|
|
36
|
-
`q_1` is a durable
|
|
37
|
-
|
|
46
|
+
`q_1` is a durable snapshot. Filter it locally without accessing the original
|
|
47
|
+
database:
|
|
38
48
|
|
|
39
49
|
```bash
|
|
40
50
|
stql filter q_1 "email LIKE ?" --param "%@example.com"
|
|
@@ -44,8 +54,8 @@ stql filter q_1 "email LIKE ?" --param "%@example.com"
|
|
|
44
54
|
{"ok":true,"handle":"q_2","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}
|
|
45
55
|
```
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
or export it
|
|
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:
|
|
49
59
|
|
|
50
60
|
```bash
|
|
51
61
|
stql alias set example-users q_2
|
|
@@ -62,7 +72,22 @@ Example first page:
|
|
|
62
72
|
```
|
|
63
73
|
|
|
64
74
|
Running the same normalized query with the same parameters reuses `q_1` while
|
|
65
|
-
its cache is valid. Use `--cache bypass` when a fresh read is required.
|
|
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`, or
|
|
80
|
+
`--profile`.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
stql connect <sqlite-path|postgres-url|mysql-url> [--name NAME] [--read-write]
|
|
84
|
+
stql connect --env ENV [--name NAME] [--read-write]
|
|
85
|
+
stql connect --profile NAME
|
|
86
|
+
stql disconnect
|
|
87
|
+
stql status
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Environment-backed credentials
|
|
66
91
|
|
|
67
92
|
PostgreSQL and MySQL credentials should come from environment variables. The
|
|
68
93
|
variable must contain the complete connection URL, not only its password.
|
|
@@ -79,21 +104,45 @@ export SQLITE_DATABASE='sqlite:./app.sqlite'
|
|
|
79
104
|
stql connect --env SQLITE_DATABASE --name local --read-only
|
|
80
105
|
```
|
|
81
106
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
107
|
+
StateQL stores no PostgreSQL or MySQL password. Credential-bearing URLs must be
|
|
108
|
+
supplied through `--env`. SQLite paths remain persisted as connection metadata.
|
|
109
|
+
|
|
110
|
+
### Local profiles
|
|
111
|
+
|
|
112
|
+
Profiles store connection targets, read-only policy, and environment-variable
|
|
113
|
+
names. Credential values are never stored. Profiles persist under `STQL_HOME`
|
|
114
|
+
with other StateQL metadata.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
stql profile add local ./app.sqlite --read-write
|
|
118
|
+
stql profile add production --env PROD_DATABASE_URL --read-only
|
|
119
|
+
stql profile list
|
|
120
|
+
stql profile show production
|
|
121
|
+
stql connect local
|
|
122
|
+
stql connect --profile production
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A bare connection target matching a profile name resolves to that profile;
|
|
126
|
+
otherwise it remains a path or database URL.
|
|
127
|
+
|
|
128
|
+
### Driver notes
|
|
87
129
|
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
- **SQLite:** use a filesystem path for direct connections or `sqlite:` for an
|
|
131
|
+
environment-backed path.
|
|
132
|
+
- **PostgreSQL:** StateQL preserves strict TLS verification by normalizing
|
|
133
|
+
`sslmode=prefer`, `require`, and `verify-ca` to `verify-full` before opening
|
|
134
|
+
the adapter. Use `sslmode=verify-full` explicitly for clarity. Setting
|
|
135
|
+
`uselibpqcompat=true` opts out and keeps libpq-compatible SSL semantics.
|
|
136
|
+
- **MySQL:** uses positional `?` parameters. MariaDB compatibility is not
|
|
137
|
+
currently claimed.
|
|
90
138
|
|
|
91
|
-
##
|
|
139
|
+
## CLI reference
|
|
92
140
|
|
|
93
141
|
```text
|
|
94
142
|
stql connect <sqlite-path|postgres-url|mysql-url> [--name NAME] [--read-write]
|
|
95
143
|
stql connect --env ENV [--name NAME] [--read-write]
|
|
96
144
|
stql connect --profile NAME
|
|
145
|
+
stql disconnect
|
|
97
146
|
stql status
|
|
98
147
|
stql profile add|list|show|remove
|
|
99
148
|
stql session start|list|show|summary|close
|
|
@@ -103,6 +152,7 @@ stql exec <sql> [--params JSON | --param VALUE...] [--idempotency-key KEY] [--re
|
|
|
103
152
|
[--allow-unbounded] [--allow-destructive]
|
|
104
153
|
stql show|count|columns <result-handle>
|
|
105
154
|
stql rows <result-handle> [--offset N] [--limit N]
|
|
155
|
+
stql alias set <name> <result-handle>
|
|
106
156
|
stql export <result-handle> --output FILE [--format json|jsonl|csv]
|
|
107
157
|
stql inspect schema|table|columns|indexes|constraints [table]
|
|
108
158
|
stql transaction begin|status|commit|rollback [--isolation LEVEL]
|
|
@@ -110,61 +160,149 @@ stql plan <sql> [--allow-unbounded] [--allow-destructive]
|
|
|
110
160
|
stql apply <plan-handle>
|
|
111
161
|
stql history [--limit N]
|
|
112
162
|
stql receipt <operation-handle>
|
|
163
|
+
stql doctor
|
|
164
|
+
stql purge [expired|results|history|all]
|
|
113
165
|
stql capabilities
|
|
114
166
|
stql batch [commands.json|commands.jsonl|-] [--continue-on-error]
|
|
115
167
|
stql pipe [--continue-on-error]
|
|
116
168
|
```
|
|
117
169
|
|
|
118
|
-
|
|
119
|
-
cancels active work. SQLite runs in a killable child process so long synchronous
|
|
120
|
-
statements cannot block StateQL's event loop. PostgreSQL uses server-side
|
|
121
|
-
`statement_timeout` plus client deadlines. MySQL deadlines destroy the active
|
|
122
|
-
connection. A timed-out write may return `OUTCOME_UNKNOWN` when commit status
|
|
123
|
-
cannot be proven.
|
|
170
|
+
### SQL parameters
|
|
124
171
|
|
|
125
|
-
|
|
172
|
+
For shell-safe positional parameters, repeat `--param`. JSON scalars become
|
|
173
|
+
their native types; other values remain strings.
|
|
174
|
+
|
|
175
|
+
```powershell
|
|
176
|
+
stql exec "INSERT INTO users (name, status) VALUES (?, ?)" `
|
|
177
|
+
--param Ada --param trial
|
|
178
|
+
```
|
|
126
179
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
180
|
+
Use `--params JSON` for a JSON array or named parameters. Use
|
|
181
|
+
`--params-file FILE` when JSON is awkward to quote; `--params-file -` reads
|
|
182
|
+
JSON from standard input.
|
|
183
|
+
|
|
184
|
+
### Output modes
|
|
185
|
+
|
|
186
|
+
CLI output defaults to compact, one-line `agent` JSON. Successful responses
|
|
187
|
+
flatten useful data and expose the primary durable ID as `handle`. Errors retain
|
|
188
|
+
their complete error object. Empty warnings and tracing metadata are omitted.
|
|
130
189
|
|
|
131
190
|
```json
|
|
132
191
|
{"ok":false,"error":{"code":"UNBOUNDED_MUTATION","message":"Mutation has no WHERE clause.","retryable":false,"executed":false,"override_flag":"--allow-unbounded"}}
|
|
133
192
|
```
|
|
134
193
|
|
|
135
|
-
|
|
136
|
-
`--output jsonl` for that envelope on one line. `--output text` prints a short
|
|
137
|
-
human status; `--output silent` prints only a successful handle. Set
|
|
138
|
-
`STQL_OUTPUT` to choose a mode globally. For `export`, `--output` names the
|
|
139
|
-
file, so use `STQL_OUTPUT` to choose its response mode. Library responses keep
|
|
140
|
-
the full envelope regardless of CLI mode.
|
|
194
|
+
Other modes are:
|
|
141
195
|
|
|
142
|
-
|
|
143
|
-
|
|
196
|
+
- `--output json`: original pretty, verbose envelope.
|
|
197
|
+
- `--output jsonl`: verbose envelope on one line.
|
|
198
|
+
- `--output text`: short human-readable status.
|
|
199
|
+
- `--output silent`: only a successful handle.
|
|
144
200
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
201
|
+
Set `STQL_OUTPUT` to choose a mode globally. For `export`, `--output` names the
|
|
202
|
+
file, so use `STQL_OUTPUT` to choose the command's response mode. Library
|
|
203
|
+
responses always keep the full envelope.
|
|
204
|
+
|
|
205
|
+
### Deadlines and cancellation
|
|
206
|
+
|
|
207
|
+
Database commands accept `--timeout-ms N`; the default is 30,000 ms. `Ctrl+C`
|
|
208
|
+
cancels active work.
|
|
209
|
+
|
|
210
|
+
- SQLite runs in a killable child process so long synchronous statements cannot
|
|
211
|
+
block StateQL's event loop.
|
|
212
|
+
- PostgreSQL combines server-side `statement_timeout` with client deadlines.
|
|
213
|
+
- MySQL deadlines destroy the active connection.
|
|
214
|
+
|
|
215
|
+
A timed-out write may return `OUTCOME_UNKNOWN` when its commit status cannot be
|
|
216
|
+
proven.
|
|
217
|
+
|
|
218
|
+
## Durable state and result reuse
|
|
219
|
+
|
|
220
|
+
State metadata lives under `STQL_HOME`, or the platform data directory when
|
|
221
|
+
unset. StateQL keeps connections, sessions, handles, aliases, cache entries,
|
|
222
|
+
plans, transactions, history, and receipts available across CLI invocations.
|
|
223
|
+
|
|
224
|
+
### Sessions and actors
|
|
225
|
+
|
|
226
|
+
Set `STQL_SESSION` to select a named session and `STQL_ACTOR` to select an
|
|
227
|
+
attached actor. A session is a shared workspace: attached actors reuse its
|
|
228
|
+
connection, handles, aliases, cache, and state version. Plans and staged
|
|
229
|
+
transactions remain owned by the actor that created them.
|
|
230
|
+
|
|
231
|
+
Callers that omit `actor` keep the legacy behavior where the actor ID is the
|
|
232
|
+
session name.
|
|
233
|
+
|
|
234
|
+
### Result lifetime and limits
|
|
149
235
|
|
|
150
|
-
|
|
151
|
-
|
|
236
|
+
SQLite result rows are materialized locally for durable access. Read cache
|
|
237
|
+
entries expire after five minutes, and materialized handles expire after 24
|
|
238
|
+
hours. Expired results and plans are deleted the next time StateQL opens.
|
|
152
239
|
|
|
153
|
-
|
|
240
|
+
Queries exceeding 10,000 rows or 16 MiB of serialized row data fail before
|
|
241
|
+
persistence. Narrow the `WHERE` clause, add `LIMIT`, or select fewer columns.
|
|
242
|
+
These caps bound persisted materialization; the independent deadline bounds
|
|
243
|
+
execution time.
|
|
154
244
|
|
|
155
|
-
|
|
245
|
+
Command history keeps the latest 10,000 entries per session. SQLite cache reuse
|
|
246
|
+
also checks the database file signature. PostgreSQL and MySQL cache reuse is
|
|
247
|
+
labeled `ttl_based` and is never authoritative.
|
|
248
|
+
|
|
249
|
+
StateQL limits persisted result payloads to 256 MiB by default. When that quota
|
|
250
|
+
is reached it removes the oldest unaliased results; aliases remain protected. A
|
|
251
|
+
single result that cannot fit fails with `STATE_QUOTA_EXCEEDED`. Configure the
|
|
252
|
+
limit with `maxStateBytes` in the library or `--max-state-bytes` in the CLI.
|
|
253
|
+
Cache and result retention can be configured with `cacheTtlSeconds` and
|
|
254
|
+
`resultTtlSeconds`, or their `--cache-ttl-seconds` and
|
|
255
|
+
`--result-ttl-seconds` CLI equivalents.
|
|
256
|
+
|
|
257
|
+
`stql doctor` checks SQLite integrity and stored payload shapes without printing
|
|
258
|
+
SQL, parameters, or result values. `stql purge` removes expired data by default;
|
|
259
|
+
use `results`, `history`, or `all` for explicit session cleanup. On POSIX
|
|
260
|
+
systems, StateQL removes group and world access from its state directory,
|
|
261
|
+
database, and SQLite sidecar files.
|
|
262
|
+
|
|
263
|
+
### Local filtering
|
|
264
|
+
|
|
265
|
+
`filter` evaluates one scalar SQLite predicate against a stored result. It
|
|
266
|
+
preserves source order, state metadata, and expiry, and never accesses the
|
|
267
|
+
original database.
|
|
268
|
+
|
|
269
|
+
Use parameters for values. Subqueries, query-shaping clauses, and
|
|
270
|
+
non-allowlisted functions are rejected. Common deterministic functions such as
|
|
271
|
+
`lower`, `upper`, `length`, and `coalesce` are supported.
|
|
272
|
+
|
|
273
|
+
## Write safety
|
|
274
|
+
|
|
275
|
+
Destructive and unbounded operations require `--allow-destructive` and
|
|
276
|
+
`--allow-unbounded`, respectively. The flags are independent.
|
|
277
|
+
|
|
278
|
+
`plan` validates and stores a write for later application. A plan persists only
|
|
279
|
+
the flags explicitly supplied when it is created; `apply` never adds
|
|
280
|
+
authorization.
|
|
281
|
+
|
|
282
|
+
Use an idempotency key to protect retryable writes from duplicate execution:
|
|
156
283
|
|
|
157
284
|
```bash
|
|
158
|
-
stql
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
stql connect local
|
|
162
|
-
stql connect --profile production
|
|
285
|
+
stql exec "UPDATE jobs SET claimed = 1 WHERE id = ?" \
|
|
286
|
+
--param 42 \
|
|
287
|
+
--idempotency-key claim-job-42
|
|
163
288
|
```
|
|
164
289
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
290
|
+
If a write starts but StateQL cannot safely record its final outcome, it returns
|
|
291
|
+
`OUTCOME_UNKNOWN` and blocks automatic replay. Inspect database state before
|
|
292
|
+
using `--replay`. Interrupted commits remain fail-closed; stale `committing`
|
|
293
|
+
records become `outcome_unknown` after five minutes.
|
|
294
|
+
|
|
295
|
+
### Transactions
|
|
296
|
+
|
|
297
|
+
Transactions are staged in local state so they survive CLI invocations, then
|
|
298
|
+
executed atomically on commit. While a transaction is active, StateQL rejects
|
|
299
|
+
database reads, plans, connection changes, and disconnects. Commit or roll back
|
|
300
|
+
first.
|
|
301
|
+
|
|
302
|
+
SQLite supports `serializable`. PostgreSQL and MySQL also support
|
|
303
|
+
`repeatable read`, `read committed`, and `read uncommitted`. Server reads run
|
|
304
|
+
inside database-enforced read-only transactions. MySQL staged transactions
|
|
305
|
+
reject DDL because MySQL implicitly commits those statements.
|
|
168
306
|
|
|
169
307
|
## Batch and pipes
|
|
170
308
|
|
|
@@ -173,6 +311,8 @@ policy, and environment-variable names. Credential values are never stored.
|
|
|
173
311
|
the first error unless `--continue-on-error` is set. Output defaults to one
|
|
174
312
|
compact `agent` JSON object per line.
|
|
175
313
|
|
|
314
|
+
Pipe commands directly:
|
|
315
|
+
|
|
176
316
|
```bash
|
|
177
317
|
printf '%s\n' \
|
|
178
318
|
'{"command":"query","sql":"SELECT id, email FROM users ORDER BY id","as":"users"}' \
|
|
@@ -181,6 +321,8 @@ printf '%s\n' \
|
|
|
181
321
|
stql pipe
|
|
182
322
|
```
|
|
183
323
|
|
|
324
|
+
Or save a JSON array as `commands.json`:
|
|
325
|
+
|
|
184
326
|
```json
|
|
185
327
|
[
|
|
186
328
|
{
|
|
@@ -197,55 +339,21 @@ printf '%s\n' \
|
|
|
197
339
|
]
|
|
198
340
|
```
|
|
199
341
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
Batch filters use `where` for the predicate and may assign the derived result
|
|
204
|
-
with `as`. Database commands may set `timeout_ms`; otherwise they use the
|
|
205
|
-
30-second default.
|
|
206
|
-
|
|
207
|
-
State metadata lives under `STQL_HOME`, or the platform data directory when
|
|
208
|
-
unset. Set `STQL_SESSION` to select a named session and `STQL_ACTOR` to select
|
|
209
|
-
an attached actor for CLI invocations. A session is a shared workspace:
|
|
210
|
-
attached actors reuse its connection, handles, aliases, cache, and
|
|
211
|
-
state version, while plans and staged transactions remain owned by their
|
|
212
|
-
creating actor. Callers that omit `actor` keep the legacy behavior where the
|
|
213
|
-
actor ID is the session name.
|
|
214
|
-
|
|
215
|
-
Read cache entries expire after five minutes; materialized handles expire after
|
|
216
|
-
24 hours. Expired results and plans are deleted when StateQL next opens. Queries
|
|
217
|
-
exceeding 10,000 rows or 16 MiB of serialized row data fail before persistence;
|
|
218
|
-
add a narrower `WHERE` clause, `LIMIT`, or smaller column selection. These caps
|
|
219
|
-
bound persisted materialization, while the independent deadline bounds execution
|
|
220
|
-
time. Command history keeps the latest 10,000 entries per session. SQLite cache reuse also checks
|
|
221
|
-
the database file signature; PostgreSQL and MySQL reuse is labeled `ttl_based`,
|
|
222
|
-
never authoritative. Transactions are staged in local state so they survive CLI
|
|
223
|
-
invocations, then executed atomically on commit. Database reads, plans,
|
|
224
|
-
connection changes, and disconnects are rejected while a transaction is active;
|
|
225
|
-
commit or roll back first. SQLite supports `serializable`;
|
|
226
|
-
PostgreSQL and MySQL also support `repeatable read`, `read committed`, and
|
|
227
|
-
`read uncommitted`. Server reads run inside database-enforced read-only
|
|
228
|
-
transactions. MySQL staged transactions reject DDL because MySQL implicitly
|
|
229
|
-
commits those statements.
|
|
342
|
+
```bash
|
|
343
|
+
stql batch commands.json
|
|
344
|
+
```
|
|
230
345
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
`
|
|
235
|
-
|
|
236
|
-
database. Use parameters for values. Subqueries, query-shaping clauses, and
|
|
237
|
-
non-allowlisted functions are rejected; common deterministic functions such as
|
|
238
|
-
`lower`, `upper`, `length`, and `coalesce` are supported.
|
|
346
|
+
Batch fields use snake case. Supported command names match CLI paths, such as
|
|
347
|
+
`filter`, `transaction.begin`, `session.summary`, `alias.set`, `plan`, and
|
|
348
|
+
`apply`. Batch filters use `where` for the predicate and may assign the derived
|
|
349
|
+
result with `as`. Database commands may set `timeout_ms`; otherwise they use the
|
|
350
|
+
30-second default.
|
|
239
351
|
|
|
240
|
-
|
|
241
|
-
independently. Plans persist only flags explicitly supplied when the plan is
|
|
242
|
-
created; `apply` never adds authorization. If a database write starts but its
|
|
243
|
-
final outcome cannot be recorded safely, StateQL returns `OUTCOME_UNKNOWN` and
|
|
244
|
-
blocks automatic replay. Inspect database state before using `--replay`.
|
|
245
|
-
Interrupted commits remain fail-closed; stale `committing` records become
|
|
246
|
-
`outcome_unknown` after five minutes.
|
|
352
|
+
## TypeScript library
|
|
247
353
|
|
|
248
|
-
|
|
354
|
+
The package exports the same stateful operations for programmatic use. Library
|
|
355
|
+
responses retain the full response envelope regardless of the configured CLI
|
|
356
|
+
output mode.
|
|
249
357
|
|
|
250
358
|
```ts
|
|
251
359
|
import { StateQL } from "@fadhilp/stateql";
|
|
@@ -255,12 +363,15 @@ const stateql = StateQL.forActor({
|
|
|
255
363
|
actor: "pi-session-id",
|
|
256
364
|
timeoutMs: 30_000,
|
|
257
365
|
maxResultBytes: 16 * 1024 * 1024,
|
|
366
|
+
maxStateBytes: 256 * 1024 * 1024,
|
|
258
367
|
});
|
|
368
|
+
|
|
259
369
|
const controller = new AbortController();
|
|
260
370
|
const response = await stateql.query("SELECT * FROM users", {
|
|
261
371
|
signal: controller.signal,
|
|
262
372
|
timeoutMs: 5_000,
|
|
263
373
|
});
|
|
374
|
+
|
|
264
375
|
if (response.ok) {
|
|
265
376
|
const handle = (response.data as { result_id: string }).result_id;
|
|
266
377
|
await stateql.filter(handle, "email LIKE ?", {
|
|
@@ -269,6 +380,19 @@ if (response.ok) {
|
|
|
269
380
|
}
|
|
270
381
|
```
|
|
271
382
|
|
|
383
|
+
### Actor workspaces
|
|
384
|
+
|
|
385
|
+
`StateQL.forActor(...)` resolves the actor's attached session directly from
|
|
386
|
+
StateQL storage, avoiding a duplicate actor-to-session mapping in integrations.
|
|
387
|
+
On first use, it creates a legacy-compatible session named after the actor. Use
|
|
388
|
+
`new StateQL({ session, actor })` when the session is already known.
|
|
389
|
+
|
|
390
|
+
Membership is managed only through the library API, not batch commands:
|
|
391
|
+
`linkActor(session, actorId)`, `unlinkActor(session, actorId)`,
|
|
392
|
+
`listActors(session)`, and `resolveActor(actorId)`. An existing member must link
|
|
393
|
+
an actor before that actor opens an existing workspace. Integrations should ask
|
|
394
|
+
for user confirmation before changing membership or the shared connection.
|
|
395
|
+
|
|
272
396
|
### Harness credential resolution
|
|
273
397
|
|
|
274
398
|
Library integrations can resolve a profile's credential reference through a
|
|
@@ -292,6 +416,7 @@ async function resolveCredential(
|
|
|
292
416
|
access: request.access,
|
|
293
417
|
signal: request.signal,
|
|
294
418
|
});
|
|
419
|
+
|
|
295
420
|
if (approved.denied) throw new CredentialResolutionError("denied");
|
|
296
421
|
return approved.value;
|
|
297
422
|
}
|
|
@@ -302,39 +427,31 @@ const stateql = StateQL.forActor({
|
|
|
302
427
|
});
|
|
303
428
|
```
|
|
304
429
|
|
|
305
|
-
When no custom resolver is configured, StateQL
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
`
|
|
310
|
-
|
|
430
|
+
When no custom resolver is configured, StateQL reads references from
|
|
431
|
+
`process.env`. A configured resolver is authoritative: returning `undefined`
|
|
432
|
+
produces `CREDENTIAL_UNAVAILABLE` and never falls back to the process
|
|
433
|
+
environment. Resolvers may throw `CredentialResolutionError` with `denied`,
|
|
434
|
+
`cancelled`, `timeout`, or `unavailable` to produce controlled, secret-free
|
|
435
|
+
failures. Unknown resolver errors are replaced with a generic
|
|
311
436
|
`CREDENTIAL_RESOLUTION_FAILED` response.
|
|
312
437
|
|
|
313
438
|
StateQL calls the resolver only immediately before database access, after SQL
|
|
314
|
-
safety and duplicate checks. Requests contain actor
|
|
439
|
+
safety and duplicate checks. Requests contain actor and session identity, the
|
|
315
440
|
operation's effective read/write access, an abort signal, and sanitized
|
|
316
|
-
connection metadata.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
441
|
+
connection metadata.
|
|
442
|
+
|
|
443
|
+
Returned values must be complete PostgreSQL or MySQL URLs, or explicit
|
|
444
|
+
`sqlite:` sources. StateQL validates the source and its stored driver before
|
|
445
|
+
adapter construction and normalizes SQLite paths. Credential-bearing
|
|
446
|
+
PostgreSQL and MySQL URLs are redacted before connection metadata is persisted
|
|
447
|
+
and never enter history, snapshots, cache keys, or responses. SQLite paths
|
|
448
|
+
remain persisted connection metadata, as they are for direct SQLite
|
|
449
|
+
connections.
|
|
450
|
+
|
|
451
|
+
Harnesses remain responsible for approval policy, binding lifetime, revocation,
|
|
452
|
+
and keeping values out of their own logs and model-visible data.
|
|
325
453
|
|
|
326
454
|
For writes, credential resolution happens after StateQL atomically reserves the
|
|
327
455
|
operation for duplicate protection. A resolution failure keeps a non-executed
|
|
328
456
|
`failed` audit record, does not consume the idempotency key, and permits a safe
|
|
329
457
|
retry.
|
|
330
|
-
|
|
331
|
-
`StateQL.forActor(...)` resolves the actor's attached session directly from
|
|
332
|
-
StateQL storage, avoiding a duplicate actor-to-session mapping in integrations.
|
|
333
|
-
On first use, it creates a legacy-compatible session named after the actor.
|
|
334
|
-
Use `new StateQL({ session, actor })` when the session is already known.
|
|
335
|
-
|
|
336
|
-
Membership is managed only through the library API, not batch commands:
|
|
337
|
-
`linkActor(session, actorId)`, `unlinkActor(session, actorId)`,
|
|
338
|
-
`listActors(session)`, and `resolveActor(actorId)`. An existing member must link
|
|
339
|
-
an actor before that actor opens an existing workspace. Integrations should ask
|
|
340
|
-
for user confirmation before changing membership or the shared connection.
|
package/dist/src/adapters.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fork } from "node:child_process";
|
|
2
2
|
import { createConnection as createMySqlConnection, } from "mysql2";
|
|
3
3
|
import { Client, types as pgTypes } from "pg";
|
|
4
|
-
import { parseJson, toJsonSafe } from "./util.js";
|
|
4
|
+
import { isSqlParameters, parseJson, toJsonSafe } from "./util.js";
|
|
5
5
|
export class AdapterExecutionError extends Error {
|
|
6
6
|
reason;
|
|
7
7
|
outcomeUnknown;
|
|
@@ -288,7 +288,7 @@ class PostgresAdapter {
|
|
|
288
288
|
try {
|
|
289
289
|
for (const operation of operations) {
|
|
290
290
|
await this.setLocalDeadline();
|
|
291
|
-
const result = await this.query(operation.sql, postgresParams(parseJson(operation.parameters,
|
|
291
|
+
const result = await this.query(operation.sql, postgresParams(parseJson(operation.parameters, `operation "${operation.id}" parameters`, isSqlParameters)), true);
|
|
292
292
|
results.push({ affectedRows: result.rowCount ?? 0 });
|
|
293
293
|
}
|
|
294
294
|
}
|
|
@@ -506,7 +506,7 @@ class MySqlAdapter {
|
|
|
506
506
|
const results = [];
|
|
507
507
|
try {
|
|
508
508
|
for (const operation of operations) {
|
|
509
|
-
const [result] = await this.query(operation.sql, mysqlParams(parseJson(operation.parameters,
|
|
509
|
+
const [result] = await this.query(operation.sql, mysqlParams(parseJson(operation.parameters, `operation "${operation.id}" parameters`, isSqlParameters)), true, true);
|
|
510
510
|
results.push({ affectedRows: mysqlAffectedRows(result) });
|
|
511
511
|
}
|
|
512
512
|
}
|
package/dist/src/cli.js
CHANGED
|
@@ -25,6 +25,9 @@ const parsed = parseArgs({
|
|
|
25
25
|
offset: { type: "string" },
|
|
26
26
|
limit: { type: "string" },
|
|
27
27
|
"timeout-ms": { type: "string" },
|
|
28
|
+
"max-state-bytes": { type: "string" },
|
|
29
|
+
"cache-ttl-seconds": { type: "string" },
|
|
30
|
+
"result-ttl-seconds": { type: "string" },
|
|
28
31
|
format: { type: "string" },
|
|
29
32
|
output: { type: "string" },
|
|
30
33
|
isolation: { type: "string" },
|
|
@@ -50,6 +53,15 @@ const stateql = new StateQL({
|
|
|
50
53
|
? {}
|
|
51
54
|
: { timeoutMs: Number(values["timeout-ms"]) }),
|
|
52
55
|
...(process.env.STQL_ACTOR ? { actor: process.env.STQL_ACTOR } : {}),
|
|
56
|
+
...(values["max-state-bytes"] === undefined
|
|
57
|
+
? {}
|
|
58
|
+
: { maxStateBytes: Number(values["max-state-bytes"]) }),
|
|
59
|
+
...(values["cache-ttl-seconds"] === undefined
|
|
60
|
+
? {}
|
|
61
|
+
: { cacheTtlSeconds: Number(values["cache-ttl-seconds"]) }),
|
|
62
|
+
...(values["result-ttl-seconds"] === undefined
|
|
63
|
+
? {}
|
|
64
|
+
: { resultTtlSeconds: Number(values["result-ttl-seconds"]) }),
|
|
53
65
|
signal: abortController.signal,
|
|
54
66
|
});
|
|
55
67
|
try {
|
|
@@ -195,6 +207,10 @@ async function dispatch() {
|
|
|
195
207
|
return stateql.history(numberOption(values.limit, 20));
|
|
196
208
|
case "receipt":
|
|
197
209
|
return stateql.receipt(requireValue(subcommand, "operation handle"));
|
|
210
|
+
case "doctor":
|
|
211
|
+
return stateql.doctor();
|
|
212
|
+
case "purge":
|
|
213
|
+
return stateql.purge(purgeScope(subcommand));
|
|
198
214
|
case "capabilities":
|
|
199
215
|
return stateql.capabilities();
|
|
200
216
|
default:
|
|
@@ -325,6 +341,13 @@ function parseBatchJson(value, location) {
|
|
|
325
341
|
function numberOption(value, fallback) {
|
|
326
342
|
return value === undefined ? fallback : Number(value);
|
|
327
343
|
}
|
|
344
|
+
function purgeScope(value) {
|
|
345
|
+
if (!value || value === "expired")
|
|
346
|
+
return "expired";
|
|
347
|
+
if (value === "results" || value === "history" || value === "all")
|
|
348
|
+
return value;
|
|
349
|
+
throw new Error("purge scope must be expired, results, history, or all.");
|
|
350
|
+
}
|
|
328
351
|
function cacheMode(value) {
|
|
329
352
|
if (!value || value === "auto")
|
|
330
353
|
return "auto";
|
|
@@ -515,12 +538,13 @@ Commands:
|
|
|
515
538
|
alias set
|
|
516
539
|
inspect schema|table|columns|indexes|constraints
|
|
517
540
|
transaction begin|status|commit|rollback
|
|
518
|
-
plan, apply, history, receipt, capabilities
|
|
541
|
+
plan, apply, history, receipt, doctor, purge, capabilities
|
|
519
542
|
batch [file.json|file.jsonl|-]
|
|
520
543
|
pipe
|
|
521
544
|
|
|
522
545
|
SQL parameters: --params JSON, repeated --param VALUE, or --params-file FILE.
|
|
523
546
|
Deadline: --timeout-ms N (default: 30000). Ctrl+C cancels database work.
|
|
547
|
+
State: --max-state-bytes N, --cache-ttl-seconds N, --result-ttl-seconds N.
|
|
524
548
|
Output: --output agent|json|jsonl|text|silent (default: agent).
|
|
525
549
|
Batch/pipe accept JSON array files or JSONL streams. Stop on first error.`;
|
|
526
550
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { StateQL } from "./stateql.js";
|
|
2
2
|
export { CredentialResolutionError, StateQLError, exitCodeFor, } from "./errors.js";
|
|
3
3
|
export type { CredentialResolutionFailure } from "./errors.js";
|
|
4
|
-
export type { BatchCommand, BatchCommandName, BatchOptions, ConnectOptions, CredentialAccess, CredentialOperation, CredentialRequest, CredentialResolver, ExecOptions, ExecutionOptions, Failure, FilterOptions, HistoryEntry, PlanOptions, ProfileOptions, QueryOptions, Response, RowsOptions, SqlParameters, StateQLActorOptions, StateQLOptions, StateQLSnapshot, Success, } from "./types.js";
|
|
4
|
+
export type { ActorData, ActorLinkData, ActorResolutionData, ActorsData, ActorUnlinkData, AliasData, ApplyData, BatchCommand, BatchCommandName, BatchOptions, CapabilitiesData, CloseSessionData, Column, ColumnsData, CommitTransactionData, ConnectOptions, ConnectionData, CountData, CredentialAccess, CredentialOperation, CredentialRequest, CredentialResolver, DisconnectData, DoctorData, Driver, ExecData, ExecOptions, ExecutionOptions, ExportData, Failure, FilterOptions, HistoryData, HistoryEntry, OperationData, PlanData, PlanOptions, ProfileData, ProfileOptions, ProfilesData, PurgeData, QueryOptions, RecentOperationData, RecentResultData, RemovedProfileData, Response, ResultData, RollbackTransactionData, Row, RowsData, RowsOptions, SqlParameters, StateQLActorOptions, StateQLOptions, StateConfidence, StateQLSnapshot, StatusConnectionData, StatusData, Success, SessionData, SessionListItem, SessionsData, SessionSummaryData, TransactionData, TransactionReferenceData, Warning, } from "./types.js";
|