@fadhilp/stateql 0.5.4 → 0.7.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 +91 -23
- package/dist/src/adapters.d.ts +1 -0
- package/dist/src/adapters.js +19 -6
- package/dist/src/cli.js +48 -2
- package/dist/src/connection.d.ts +1 -0
- package/dist/src/connection.js +29 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/migrations.js +22 -0
- package/dist/src/mongodb.d.ts +48 -0
- package/dist/src/mongodb.js +868 -0
- package/dist/src/response-data.js +12 -0
- package/dist/src/sql.d.ts +3 -1
- package/dist/src/sql.js +3 -0
- package/dist/src/stateql.d.ts +11 -3
- package/dist/src/stateql.js +730 -154
- package/dist/src/store.d.ts +8 -3
- package/dist/src/store.js +20 -14
- package/dist/src/types.d.ts +103 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
StateQL is a stateful database CLI and TypeScript library for AI agents and
|
|
4
4
|
automation. It provides a safe interface for querying, changing, and inspecting
|
|
5
|
-
SQLite, PostgreSQL, and
|
|
6
|
-
operations traceable across commands.
|
|
5
|
+
SQLite, PostgreSQL, MySQL, and MongoDB databases while keeping results reusable
|
|
6
|
+
and operations traceable across commands.
|
|
7
7
|
|
|
8
8
|
StateQL is built around durable handles:
|
|
9
9
|
|
|
@@ -80,7 +80,7 @@ A connection accepts exactly one source: a direct target, `--env`, or
|
|
|
80
80
|
`--profile`.
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
stql connect <sqlite-path|postgres-url|mysql-url> [--name NAME] [--read-write]
|
|
83
|
+
stql connect <sqlite-path|postgres-url|mysql-url|mongodb-url> [--name NAME] [--read-write]
|
|
84
84
|
stql connect --env ENV [--name NAME] [--read-write]
|
|
85
85
|
stql connect --profile NAME
|
|
86
86
|
stql disconnect
|
|
@@ -89,9 +89,9 @@ stql status
|
|
|
89
89
|
|
|
90
90
|
### Environment-backed credentials
|
|
91
91
|
|
|
92
|
-
PostgreSQL and
|
|
93
|
-
variable must contain the complete connection URL, not only its
|
|
94
|
-
Environment-backed SQLite paths require an explicit `sqlite:` prefix.
|
|
92
|
+
PostgreSQL, MySQL, and MongoDB credentials should come from environment
|
|
93
|
+
variables. The variable must contain the complete connection URL, not only its
|
|
94
|
+
password. Environment-backed SQLite paths require an explicit `sqlite:` prefix.
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
97
|
export APP_DATABASE_URL='postgres://user:password@host/app'
|
|
@@ -100,12 +100,16 @@ stql connect --env APP_DATABASE_URL --name app --read-only
|
|
|
100
100
|
export MYSQL_DATABASE_URL='mysql://user:password@host/app'
|
|
101
101
|
stql connect --env MYSQL_DATABASE_URL --name mysql-app --read-only
|
|
102
102
|
|
|
103
|
+
export MONGODB_URL='mongodb://user:password@host/app'
|
|
104
|
+
stql connect --env MONGODB_URL --name mongo-app --read-only
|
|
105
|
+
|
|
103
106
|
export SQLITE_DATABASE='sqlite:./app.sqlite'
|
|
104
107
|
stql connect --env SQLITE_DATABASE --name local --read-only
|
|
105
108
|
```
|
|
106
109
|
|
|
107
|
-
StateQL stores no PostgreSQL or
|
|
108
|
-
supplied through `--env`. SQLite paths remain persisted as
|
|
110
|
+
StateQL stores no PostgreSQL, MySQL, or MongoDB password. Credential-bearing
|
|
111
|
+
URLs must be supplied through `--env`. SQLite paths remain persisted as
|
|
112
|
+
connection metadata.
|
|
109
113
|
|
|
110
114
|
### Local profiles
|
|
111
115
|
|
|
@@ -135,11 +139,13 @@ otherwise it remains a path or database URL.
|
|
|
135
139
|
`uselibpqcompat=true` opts out and keeps libpq-compatible SSL semantics.
|
|
136
140
|
- **MySQL:** uses positional `?` parameters. MariaDB compatibility is not
|
|
137
141
|
currently claimed.
|
|
142
|
+
- **MongoDB:** supports `mongodb://` and `mongodb+srv://` URLs with an explicit
|
|
143
|
+
database path. SQL methods are rejected; use the native MongoDB methods below.
|
|
138
144
|
|
|
139
145
|
## CLI reference
|
|
140
146
|
|
|
141
147
|
```text
|
|
142
|
-
stql connect <sqlite-path|postgres-url|mysql-url> [--name NAME] [--read-write]
|
|
148
|
+
stql connect <sqlite-path|postgres-url|mysql-url|mongodb-url> [--name NAME] [--read-write]
|
|
143
149
|
stql connect --env ENV [--name NAME] [--read-write]
|
|
144
150
|
stql connect --profile NAME
|
|
145
151
|
stql disconnect
|
|
@@ -150,11 +156,13 @@ stql query <sql> [--params JSON | --param VALUE...] [--cache auto|bypass|require
|
|
|
150
156
|
stql filter <result-handle> <predicate> [--params JSON | --param VALUE...]
|
|
151
157
|
stql exec <sql> [--params JSON | --param VALUE...] [--idempotency-key KEY] [--replay]
|
|
152
158
|
[--allow-unbounded] [--allow-destructive]
|
|
159
|
+
stql mongo query|exec|plan '<EJSON command>' [--cache MODE] [--idempotency-key KEY]
|
|
160
|
+
[--replay] [--allow-unbounded] [--allow-destructive]
|
|
153
161
|
stql show|count|columns <result-handle>
|
|
154
162
|
stql rows <result-handle> [--offset N] [--limit N]
|
|
155
163
|
stql alias set <name> <result-handle>
|
|
156
164
|
stql export <result-handle> --output FILE [--format json|jsonl|csv]
|
|
157
|
-
stql inspect schema|table|columns|indexes|constraints [
|
|
165
|
+
stql inspect schema|table|collection|collections|columns|indexes|constraints [name]
|
|
158
166
|
stql transaction begin|status|commit|rollback [--isolation LEVEL]
|
|
159
167
|
stql plan <sql> [--allow-unbounded] [--allow-destructive]
|
|
160
168
|
stql apply <plan-handle>
|
|
@@ -181,6 +189,38 @@ Use `--params JSON` for a JSON array or named parameters. Use
|
|
|
181
189
|
`--params-file FILE` when JSON is awkward to quote; `--params-file -` reads
|
|
182
190
|
JSON from standard input.
|
|
183
191
|
|
|
192
|
+
### Native MongoDB
|
|
193
|
+
|
|
194
|
+
MongoDB commands use official Extended JSON (EJSON), so BSON values survive the
|
|
195
|
+
CLI boundary:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
stql mongo query '{"operation":"find","collection":"users","filter":{"_id":{"$oid":"507f1f77bcf86cd799439011"}}}'
|
|
199
|
+
stql mongo exec '{"operation":"updateOne","collection":"users","filter":{"_id":{"$oid":"507f1f77bcf86cd799439011"}},"update":{"$set":{"seen_at":{"$date":"2026-01-01T00:00:00Z"}}}}'
|
|
200
|
+
stql mongo plan '{"operation":"deleteMany","collection":"users","filter":{"disabled":true}}' --allow-destructive
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The TypeScript equivalents are `mongoQuery(command)`, `mongoExec(command)`, and
|
|
204
|
+
`mongoPlan(command)`. Supported reads are `find` and `aggregate`; writes are
|
|
205
|
+
`insertOne`, `insertMany`, `updateOne`, `updateMany`, `replaceOne`, `deleteOne`,
|
|
206
|
+
and `deleteMany`. Result documents are JSON-safe, order-preserving EJSON: for example,
|
|
207
|
+
ObjectIds and dates appear as `{ "$oid": "..." }` and
|
|
208
|
+
`{ "$date": { "$numberLong": "..." } }`.
|
|
209
|
+
|
|
210
|
+
Empty update, replacement, or delete filters require `--allow-unbounded`;
|
|
211
|
+
deletes and replacements also require `--allow-destructive`. Mongo inspection accepts `collections`,
|
|
212
|
+
`collection`, `columns`, `indexes`, and `constraints` (`schema` and `table`
|
|
213
|
+
remain aliases shared with SQL drivers). MongoDB cache confidence is TTL-based:
|
|
214
|
+
external writes are not detected, so use `--cache bypass` for a fresh read.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const result = await stateql.mongoQuery({
|
|
218
|
+
operation: "find",
|
|
219
|
+
collection: "users",
|
|
220
|
+
filter: { active: true },
|
|
221
|
+
options: { sort: { _id: 1 }, limit: 50 },
|
|
222
|
+
});
|
|
223
|
+
```
|
|
184
224
|
### Output modes
|
|
185
225
|
|
|
186
226
|
CLI output defaults to compact, one-line `agent` JSON. Successful responses
|
|
@@ -211,9 +251,11 @@ cancels active work.
|
|
|
211
251
|
block StateQL's event loop.
|
|
212
252
|
- PostgreSQL combines server-side `statement_timeout` with client deadlines.
|
|
213
253
|
- MySQL deadlines destroy the active connection.
|
|
254
|
+
- MongoDB uses driver deadlines and closes stopped operations.
|
|
214
255
|
|
|
215
|
-
A timed-out write may return `OUTCOME_UNKNOWN` when its commit
|
|
216
|
-
proven.
|
|
256
|
+
A timed-out or cancelled write may return `OUTCOME_UNKNOWN` when its commit
|
|
257
|
+
status cannot be proven. Cancellation stops that command's driver work; it does
|
|
258
|
+
not close the `StateQL` actor, and later commands remain usable.
|
|
217
259
|
|
|
218
260
|
## Durable state and result reuse
|
|
219
261
|
|
|
@@ -243,8 +285,8 @@ These caps bound persisted materialization; the independent deadline bounds
|
|
|
243
285
|
execution time.
|
|
244
286
|
|
|
245
287
|
Command history keeps the latest 10,000 entries per session. SQLite cache reuse
|
|
246
|
-
also checks the database file signature. PostgreSQL and
|
|
247
|
-
labeled `ttl_based` and is never authoritative.
|
|
288
|
+
also checks the database file signature. PostgreSQL, MySQL, and MongoDB cache
|
|
289
|
+
reuse is labeled `ttl_based` and is never authoritative.
|
|
248
290
|
|
|
249
291
|
StateQL limits persisted result payloads to 256 MiB by default. When that quota
|
|
250
292
|
is reached it removes the oldest unaliased results; aliases remain protected. A
|
|
@@ -303,6 +345,8 @@ SQLite supports `serializable`. PostgreSQL and MySQL also support
|
|
|
303
345
|
`repeatable read`, `read committed`, and `read uncommitted`. Server reads run
|
|
304
346
|
inside database-enforced read-only transactions. MySQL staged transactions
|
|
305
347
|
reject DDL because MySQL implicitly commits those statements.
|
|
348
|
+
MongoDB transactions use `snapshot` isolation and require a replica set or
|
|
349
|
+
sharded deployment; standalone servers do not support them.
|
|
306
350
|
|
|
307
351
|
## Batch and pipes
|
|
308
352
|
|
|
@@ -345,9 +389,10 @@ stql batch commands.json
|
|
|
345
389
|
|
|
346
390
|
Batch fields use snake case. Supported command names match CLI paths, such as
|
|
347
391
|
`filter`, `transaction.begin`, `session.summary`, `alias.set`, `plan`, and
|
|
348
|
-
`apply`.
|
|
349
|
-
|
|
350
|
-
|
|
392
|
+
`apply`. Native MongoDB batches use `mongo.query`, `mongo.exec`, or `mongo.plan`
|
|
393
|
+
with the command object in `mongo`; the same cache, replay, idempotency, safety,
|
|
394
|
+
and timeout fields apply. Database commands may set `timeout_ms`; otherwise they
|
|
395
|
+
use the 30-second default.
|
|
351
396
|
|
|
352
397
|
## TypeScript library
|
|
353
398
|
|
|
@@ -380,6 +425,30 @@ if (response.ok) {
|
|
|
380
425
|
}
|
|
381
426
|
```
|
|
382
427
|
|
|
428
|
+
Hosts that dispatch batch-shaped commands can attach trusted metadata out of
|
|
429
|
+
band. `origin` is audit/source metadata only; it never changes actor membership,
|
|
430
|
+
workspace access, or write authorization.
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
await stateql.executeCommand(
|
|
435
|
+
{ command: "query", sql: "SELECT * FROM users", cache: "bypass" },
|
|
436
|
+
{ origin: "user", signal: controller.signal },
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const userHistory = await stateql.history(50, { origin: "user" });
|
|
440
|
+
await stateql.executeCommand(
|
|
441
|
+
{ command: "history", limit: 50, history_origin: "user" },
|
|
442
|
+
{ origin: "model" },
|
|
443
|
+
);
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Supported origins are `legacy`, `user`, `model`, `system`, and `api`. Existing
|
|
447
|
+
direct calls and `executeCommand(command)` calls are recorded as `legacy`.
|
|
448
|
+
`history_origin` is only a retrieval filter; putting an `origin` field in a
|
|
449
|
+
`BatchCommand` cannot attribute the command. `batch` accepts the same trusted
|
|
450
|
+
context as `options.executionContext` for all commands in that batch.
|
|
451
|
+
|
|
383
452
|
### Actor workspaces
|
|
384
453
|
|
|
385
454
|
`StateQL.forActor(...)` resolves the actor's attached session directly from
|
|
@@ -440,13 +509,12 @@ safety and duplicate checks. Requests contain actor and session identity, the
|
|
|
440
509
|
operation's effective read/write access, an abort signal, and sanitized
|
|
441
510
|
connection metadata.
|
|
442
511
|
|
|
443
|
-
Returned values must be complete PostgreSQL or
|
|
512
|
+
Returned values must be complete PostgreSQL, MySQL, or MongoDB URLs, or explicit
|
|
444
513
|
`sqlite:` sources. StateQL validates the source and its stored driver before
|
|
445
|
-
adapter construction and normalizes SQLite paths. Credential-bearing
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
connections.
|
|
514
|
+
adapter construction and normalizes SQLite paths. Credential-bearing database
|
|
515
|
+
URLs are redacted before connection metadata is persisted and never enter
|
|
516
|
+
history, snapshots, cache keys, or responses. SQLite paths remain persisted
|
|
517
|
+
connection metadata, as they are for direct SQLite connections.
|
|
450
518
|
|
|
451
519
|
Harnesses remain responsible for approval policy, binding lifetime, revocation,
|
|
452
520
|
and keeping values out of their own logs and model-visible data.
|
package/dist/src/adapters.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare class AdapterWriteError extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
export interface Adapter {
|
|
28
28
|
readonly confidence: StateConfidence;
|
|
29
|
+
ping(): Promise<void>;
|
|
29
30
|
read(sql: string, params: SqlParameters): Promise<ReadResult>;
|
|
30
31
|
write(sql: string, params: SqlParameters): Promise<WriteResult>;
|
|
31
32
|
writeBatch(operations: OperationRecord[], isolation: string): Promise<WriteResult[]>;
|
package/dist/src/adapters.js
CHANGED
|
@@ -1,6 +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 { StateQLError } from "./errors.js";
|
|
4
5
|
import { isSqlParameters, parseJson, toJsonSafe } from "./util.js";
|
|
5
6
|
export class AdapterExecutionError extends Error {
|
|
6
7
|
reason;
|
|
@@ -36,13 +37,16 @@ export function createAdapterContext(timeoutMs, signal) {
|
|
|
36
37
|
}
|
|
37
38
|
export async function createAdapter(connection, context, input) {
|
|
38
39
|
const { source } = input;
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
switch (connection.driver) {
|
|
41
|
+
case "sqlite":
|
|
42
|
+
return new SQLiteAdapter(source, Boolean(connection.read_only), context);
|
|
43
|
+
case "postgres":
|
|
44
|
+
return new PostgresAdapter(source, Boolean(connection.read_only), context);
|
|
45
|
+
case "mysql":
|
|
46
|
+
return new MySqlAdapter(source, Boolean(connection.read_only), context);
|
|
47
|
+
case "mongodb":
|
|
48
|
+
throw new StateQLError("UNSUPPORTED_DRIVER", "MongoDB uses the native MongoDB adapter.");
|
|
41
49
|
}
|
|
42
|
-
if (connection.driver === "postgres") {
|
|
43
|
-
return new PostgresAdapter(source, Boolean(connection.read_only), context);
|
|
44
|
-
}
|
|
45
|
-
return new MySqlAdapter(source, Boolean(connection.read_only), context);
|
|
46
50
|
}
|
|
47
51
|
class SQLiteAdapter {
|
|
48
52
|
source;
|
|
@@ -85,6 +89,9 @@ class SQLiteAdapter {
|
|
|
85
89
|
}
|
|
86
90
|
});
|
|
87
91
|
}
|
|
92
|
+
async ping() {
|
|
93
|
+
await this.read("SELECT 1", []);
|
|
94
|
+
}
|
|
88
95
|
async read(sql, params) {
|
|
89
96
|
return this.call("read", [sql, params], false, false);
|
|
90
97
|
}
|
|
@@ -219,6 +226,9 @@ class PostgresAdapter {
|
|
|
219
226
|
statement_timeout: timeout,
|
|
220
227
|
});
|
|
221
228
|
}
|
|
229
|
+
async ping() {
|
|
230
|
+
await this.read("SELECT 1", []);
|
|
231
|
+
}
|
|
222
232
|
async read(sql, params) {
|
|
223
233
|
await this.connect();
|
|
224
234
|
await this.query("BEGIN READ ONLY", [], false);
|
|
@@ -440,6 +450,9 @@ class MySqlAdapter {
|
|
|
440
450
|
this.readOnly = readOnly;
|
|
441
451
|
this.context = context;
|
|
442
452
|
}
|
|
453
|
+
async ping() {
|
|
454
|
+
await this.read("SELECT 1", []);
|
|
455
|
+
}
|
|
443
456
|
async read(sql, params) {
|
|
444
457
|
await this.query("START TRANSACTION READ ONLY", [], false, false);
|
|
445
458
|
try {
|
package/dist/src/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createReadStream, readFileSync } from "node:fs";
|
|
|
3
3
|
import { extname, resolve } from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
|
+
import { BSON } from "mongodb";
|
|
6
7
|
import { exitCodeFor } from "./errors.js";
|
|
7
8
|
import { StateQL } from "./stateql.js";
|
|
8
9
|
const parsed = parseArgs({
|
|
@@ -85,7 +86,10 @@ async function runSingle() {
|
|
|
85
86
|
catch (error) {
|
|
86
87
|
response = cliFailure(error);
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
+
const responseCommand = command === "mongo" && subcommand
|
|
90
|
+
? `mongo.${subcommand}`
|
|
91
|
+
: command ?? "";
|
|
92
|
+
print(response, mode, responseCommand);
|
|
89
93
|
if (!response.ok)
|
|
90
94
|
process.exitCode = exitCodeFor(response.error.code);
|
|
91
95
|
}
|
|
@@ -155,6 +159,8 @@ async function dispatch() {
|
|
|
155
159
|
return dispatchProfile(subcommand, rest);
|
|
156
160
|
case "session":
|
|
157
161
|
return dispatchSession(subcommand, rest);
|
|
162
|
+
case "mongo":
|
|
163
|
+
return dispatchMongo(subcommand, rest);
|
|
158
164
|
case "query":
|
|
159
165
|
return stateql.query(sql, {
|
|
160
166
|
params,
|
|
@@ -217,6 +223,41 @@ async function dispatch() {
|
|
|
217
223
|
throw new Error(`Unknown command "${command}".`);
|
|
218
224
|
}
|
|
219
225
|
}
|
|
226
|
+
async function dispatchMongo(action, args) {
|
|
227
|
+
const payload = requireValue(args.join(" ").trim(), "MongoDB EJSON command");
|
|
228
|
+
switch (action) {
|
|
229
|
+
case "query":
|
|
230
|
+
return stateql.mongoQuery(parseMongoCommand(payload), {
|
|
231
|
+
cache: cacheMode(values.cache),
|
|
232
|
+
});
|
|
233
|
+
case "exec":
|
|
234
|
+
return stateql.mongoExec(parseMongoCommand(payload), {
|
|
235
|
+
replay: values.replay ?? false,
|
|
236
|
+
...(values["idempotency-key"]
|
|
237
|
+
? { idempotencyKey: values["idempotency-key"] }
|
|
238
|
+
: {}),
|
|
239
|
+
allowUnbounded: values["allow-unbounded"] ?? false,
|
|
240
|
+
allowDestructive: values["allow-destructive"] ?? false,
|
|
241
|
+
});
|
|
242
|
+
case "plan":
|
|
243
|
+
return stateql.mongoPlan(parseMongoCommand(payload), {
|
|
244
|
+
allowUnbounded: values["allow-unbounded"] ?? false,
|
|
245
|
+
...(values["allow-destructive"]
|
|
246
|
+
? { allowDestructive: true }
|
|
247
|
+
: {}),
|
|
248
|
+
});
|
|
249
|
+
default:
|
|
250
|
+
throw new Error(`Unknown MongoDB command "${action ?? ""}".`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function parseMongoCommand(value) {
|
|
254
|
+
try {
|
|
255
|
+
return BSON.EJSON.parse(value, { relaxed: false });
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
throw new Error("Invalid MongoDB EJSON command.");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
220
261
|
async function dispatchSession(action, args) {
|
|
221
262
|
switch (action) {
|
|
222
263
|
case "start":
|
|
@@ -427,6 +468,7 @@ function toAgentResponse(result, currentCommand) {
|
|
|
427
468
|
if (handle && handleKey)
|
|
428
469
|
delete data[handleKey];
|
|
429
470
|
if ((currentCommand === "query" ||
|
|
471
|
+
currentCommand === "mongo.query" ||
|
|
430
472
|
currentCommand === "filter" ||
|
|
431
473
|
currentCommand === "show") &&
|
|
432
474
|
typeof data.rows === "number" &&
|
|
@@ -469,6 +511,7 @@ function toAgentResponse(result, currentCommand) {
|
|
|
469
511
|
function primaryHandleKey(currentCommand) {
|
|
470
512
|
const keys = {
|
|
471
513
|
query: "result_id",
|
|
514
|
+
"mongo.query": "result_id",
|
|
472
515
|
filter: "result_id",
|
|
473
516
|
show: "result_id",
|
|
474
517
|
rows: "result_id",
|
|
@@ -478,9 +521,11 @@ function primaryHandleKey(currentCommand) {
|
|
|
478
521
|
alias: "result_id",
|
|
479
522
|
"alias.set": "result_id",
|
|
480
523
|
exec: "operation_id",
|
|
524
|
+
"mongo.exec": "operation_id",
|
|
481
525
|
receipt: "operation_id",
|
|
482
526
|
apply: "operation_id",
|
|
483
527
|
plan: "plan_id",
|
|
528
|
+
"mongo.plan": "plan_id",
|
|
484
529
|
connect: "connection_id",
|
|
485
530
|
transaction: "transaction_id",
|
|
486
531
|
"transaction.begin": "transaction_id",
|
|
@@ -535,8 +580,9 @@ Commands:
|
|
|
535
580
|
profile add|list|show|remove
|
|
536
581
|
session start|list|show|summary|close
|
|
537
582
|
query, filter, exec, show, rows, count, columns, export
|
|
583
|
+
mongo query|exec|plan '<EJSON command>'
|
|
538
584
|
alias set
|
|
539
|
-
inspect schema|table|columns|indexes|constraints
|
|
585
|
+
inspect schema|table|collection|collections|columns|indexes|constraints
|
|
540
586
|
transaction begin|status|commit|rollback
|
|
541
587
|
plan, apply, history, receipt, doctor, purge, capabilities
|
|
542
588
|
batch [file.json|file.jsonl|-]
|
package/dist/src/connection.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ConnectionRecord } from "./store.js";
|
|
|
2
2
|
import type { Driver, StateConfidence } from "./types.js";
|
|
3
3
|
export declare function databaseIdentity(connection: ConnectionRecord): unknown;
|
|
4
4
|
export declare function detectDriver(target: string): Driver;
|
|
5
|
+
export declare function mongoDatabaseName(target: string): string;
|
|
5
6
|
export declare function credentialSource(value: string, expectedDriver?: Driver): {
|
|
6
7
|
driver: Driver;
|
|
7
8
|
source: string;
|
package/dist/src/connection.js
CHANGED
|
@@ -13,16 +13,41 @@ export function detectDriver(target) {
|
|
|
13
13
|
return "postgres";
|
|
14
14
|
if (/^mysql:\/\//i.test(target))
|
|
15
15
|
return "mysql";
|
|
16
|
+
if (/^mongodb(?:\+srv)?:\/\//i.test(target))
|
|
17
|
+
return "mongodb";
|
|
16
18
|
if (/^[a-z][a-z\d+.-]*:\/\//i.test(target)) {
|
|
17
|
-
throw new StateQLError("UNSUPPORTED_DRIVER", "Only MySQL, PostgreSQL, and SQLite are supported.");
|
|
19
|
+
throw new StateQLError("UNSUPPORTED_DRIVER", "Only MongoDB, MySQL, PostgreSQL, and SQLite are supported.");
|
|
18
20
|
}
|
|
19
21
|
return "sqlite";
|
|
20
22
|
}
|
|
23
|
+
export function mongoDatabaseName(target) {
|
|
24
|
+
let url;
|
|
25
|
+
try {
|
|
26
|
+
url = new URL(target);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new StateQLError("INVALID_COMMAND", "Invalid MongoDB URL.");
|
|
30
|
+
}
|
|
31
|
+
if (!["mongodb:", "mongodb+srv:"].includes(url.protocol.toLowerCase()) ||
|
|
32
|
+
!url.hostname) {
|
|
33
|
+
throw new StateQLError("INVALID_COMMAND", "Invalid MongoDB URL.");
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const database = decodeURIComponent(url.pathname.replace(/^\//, ""));
|
|
37
|
+
if (database && !database.includes("/") && !database.includes("\0")) {
|
|
38
|
+
return database;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Report malformed escaping as an invalid explicit database name.
|
|
43
|
+
}
|
|
44
|
+
throw new StateQLError("INVALID_COMMAND", "MongoDB URL must include an explicit database name.");
|
|
45
|
+
}
|
|
21
46
|
export function credentialSource(value, expectedDriver) {
|
|
22
47
|
const explicitSqlite = /^sqlite:(?!\/\/)/i.test(value);
|
|
23
48
|
const driver = explicitSqlite ? "sqlite" : detectDriver(value);
|
|
24
49
|
if (driver === "sqlite" && (!explicitSqlite || value.length === 7)) {
|
|
25
|
-
throw new StateQLError("INVALID_COMMAND", "Secret environment variable must contain a complete PostgreSQL/MySQL URL or an explicit sqlite: source.", {
|
|
50
|
+
throw new StateQLError("INVALID_COMMAND", "Secret environment variable must contain a complete PostgreSQL/MySQL URL or an explicit sqlite: source; MongoDB URLs are also supported.", {
|
|
26
51
|
suggestedAction: "Store the full database URL, or prefix an SQLite path with sqlite:.",
|
|
27
52
|
});
|
|
28
53
|
}
|
|
@@ -54,7 +79,8 @@ export function normalizeSqliteSource(target) {
|
|
|
54
79
|
export function databaseUrlHasSecret(target) {
|
|
55
80
|
try {
|
|
56
81
|
const url = new URL(target);
|
|
57
|
-
return (Boolean(url.
|
|
82
|
+
return (Boolean(url.username) ||
|
|
83
|
+
Boolean(url.password) ||
|
|
58
84
|
[...url.searchParams.keys()].some((key) => /pass|token|secret|private[_-]?key|api[_-]?key/i.test(key)));
|
|
59
85
|
}
|
|
60
86
|
catch {
|
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 { 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";
|
|
4
|
+
export type { ActorData, ActorLinkData, ActorResolutionData, ActorsData, ActorUnlinkData, AliasData, ApplyData, BatchCommand, BatchCommandName, BatchOptions, CapabilitiesData, CommandExecutionContext, CommandOrigin, CloseSessionData, Column, ColumnsData, CommitTransactionData, ConnectOptions, ConnectionData, CountData, CredentialAccess, CredentialOperation, CredentialRequest, CredentialResolver, DisconnectData, DoctorData, Driver, ExecData, ExecOptions, ExecutionOptions, ExportData, Failure, FilterOptions, HistoryData, HistoryEntry, HistoryOptions, MongoAggregateOptions, MongoDocument, MongoFindOptions, MongoMutationOptions, MongoExecOptions, MongoPlanOptions, MongoQueryOptions, MongoReadCommand, MongoWriteCommand, MongoWriteOutcome, OperationData, PlanData, PlanOptions, ProfileData, ProfileOptions, ProfilesData, PurgeData, QueryOptions, RecentOperationData, RecentResultData, RemovedProfileData, Response, ResultData, RollbackTransactionData, Row, RowsData, RowsOptions, SqlParameters, SqlDriver, StateQLActorOptions, StateQLOptions, StateConfidence, StateQLSnapshot, StatusConnectionData, StatusData, Success, SessionData, SessionListItem, SessionsData, SessionSummaryData, TransactionData, TransactionReferenceData, Warning, } from "./types.js";
|
package/dist/src/migrations.js
CHANGED
|
@@ -55,6 +55,26 @@ const MIGRATIONS = [
|
|
|
55
55
|
requireColumns(db, "history", ["sql"]);
|
|
56
56
|
},
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
name: "operation_outcomes_v1",
|
|
60
|
+
apply(db) {
|
|
61
|
+
addColumn(db, "operations", "outcome_json", "TEXT");
|
|
62
|
+
},
|
|
63
|
+
validate(db) {
|
|
64
|
+
requireColumns(db, "operations", ["outcome_json"]);
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "history_origin_v1",
|
|
69
|
+
apply(db) {
|
|
70
|
+
addColumn(db, "history", "origin", "TEXT NOT NULL DEFAULT 'legacy'");
|
|
71
|
+
db.exec("CREATE INDEX IF NOT EXISTS history_session_origin ON history(session_id, origin)");
|
|
72
|
+
},
|
|
73
|
+
validate(db) {
|
|
74
|
+
requireColumns(db, "history", ["origin"]);
|
|
75
|
+
requireIndexes(db, ["history_session_origin"]);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
58
78
|
];
|
|
59
79
|
export function runMigrations(db, now) {
|
|
60
80
|
db.exec(`
|
|
@@ -169,6 +189,7 @@ function createInitialSchema(db) {
|
|
|
169
189
|
idempotency_key TEXT,
|
|
170
190
|
state_version_before TEXT NOT NULL,
|
|
171
191
|
state_version_after TEXT,
|
|
192
|
+
outcome_json TEXT,
|
|
172
193
|
created_at TEXT NOT NULL
|
|
173
194
|
);
|
|
174
195
|
CREATE INDEX IF NOT EXISTS operations_fingerprint
|
|
@@ -211,6 +232,7 @@ function createInitialSchema(db) {
|
|
|
211
232
|
session_id TEXT NOT NULL,
|
|
212
233
|
actor_id TEXT NOT NULL,
|
|
213
234
|
command TEXT NOT NULL,
|
|
235
|
+
origin TEXT NOT NULL DEFAULT 'legacy',
|
|
214
236
|
sql TEXT,
|
|
215
237
|
handle TEXT,
|
|
216
238
|
executed INTEGER NOT NULL,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type AdapterContext } from "./adapters.js";
|
|
2
|
+
import type { ConnectionRecord } from "./store.js";
|
|
3
|
+
import type { Column, MongoReadCommand, MongoWriteCommand, MongoWriteOutcome, Row } from "./types.js";
|
|
4
|
+
interface MongoReadResult {
|
|
5
|
+
rows: Row[];
|
|
6
|
+
columns: Column[];
|
|
7
|
+
}
|
|
8
|
+
interface MongoWriteResult {
|
|
9
|
+
affectedRows: number;
|
|
10
|
+
outcome: MongoWriteOutcome;
|
|
11
|
+
}
|
|
12
|
+
interface MongoWriteSafety {
|
|
13
|
+
unbounded: boolean;
|
|
14
|
+
destructive: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function validateMongoReadCommand(value: unknown): MongoReadCommand;
|
|
17
|
+
export declare function validateMongoWriteCommand(value: unknown): MongoWriteCommand;
|
|
18
|
+
export declare function analyzeMongoWriteSafety(command: MongoWriteCommand): MongoWriteSafety;
|
|
19
|
+
/** Deterministic EJSON preserves BSON types and property order. */
|
|
20
|
+
export declare function serializeMongoCommand(command: MongoReadCommand | MongoWriteCommand): string;
|
|
21
|
+
export declare function deserializeMongoWriteCommand(value: string): MongoWriteCommand;
|
|
22
|
+
export declare class MongoAdapter {
|
|
23
|
+
private readonly context;
|
|
24
|
+
readonly confidence: "ttl_based";
|
|
25
|
+
private readonly client;
|
|
26
|
+
private readonly databaseName;
|
|
27
|
+
private readonly readOnly;
|
|
28
|
+
private connected;
|
|
29
|
+
private closed;
|
|
30
|
+
private connecting?;
|
|
31
|
+
constructor(connection: ConnectionRecord, context: AdapterContext, input: {
|
|
32
|
+
source: string;
|
|
33
|
+
});
|
|
34
|
+
ping(): Promise<void>;
|
|
35
|
+
signature(): Promise<string>;
|
|
36
|
+
read(command: MongoReadCommand, maxRows: number): Promise<MongoReadResult>;
|
|
37
|
+
write(command: MongoWriteCommand): Promise<MongoWriteResult>;
|
|
38
|
+
writeBatch(commands: MongoWriteCommand[], isolation: string): Promise<MongoWriteResult[]>;
|
|
39
|
+
inspect(kind: string, name?: string): Promise<unknown>;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
private connect;
|
|
42
|
+
private executeWrite;
|
|
43
|
+
private requireCollection;
|
|
44
|
+
private sampleColumns;
|
|
45
|
+
private collectionIndexes;
|
|
46
|
+
private stop;
|
|
47
|
+
}
|
|
48
|
+
export {};
|