@fadhilp/stateql 0.5.3 → 0.6.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 +64 -21
- 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 +10 -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 +161 -6
- package/dist/src/stateql.d.ts +8 -1
- package/dist/src/stateql.js +544 -22
- package/dist/src/store.d.ts +5 -2
- package/dist/src/store.js +13 -7
- package/dist/src/types.d.ts +90 -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,6 +251,7 @@ 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
256
|
A timed-out write may return `OUTCOME_UNKNOWN` when its commit status cannot be
|
|
216
257
|
proven.
|
|
@@ -243,8 +284,8 @@ These caps bound persisted materialization; the independent deadline bounds
|
|
|
243
284
|
execution time.
|
|
244
285
|
|
|
245
286
|
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.
|
|
287
|
+
also checks the database file signature. PostgreSQL, MySQL, and MongoDB cache
|
|
288
|
+
reuse is labeled `ttl_based` and is never authoritative.
|
|
248
289
|
|
|
249
290
|
StateQL limits persisted result payloads to 256 MiB by default. When that quota
|
|
250
291
|
is reached it removes the oldest unaliased results; aliases remain protected. A
|
|
@@ -303,6 +344,8 @@ SQLite supports `serializable`. PostgreSQL and MySQL also support
|
|
|
303
344
|
`repeatable read`, `read committed`, and `read uncommitted`. Server reads run
|
|
304
345
|
inside database-enforced read-only transactions. MySQL staged transactions
|
|
305
346
|
reject DDL because MySQL implicitly commits those statements.
|
|
347
|
+
MongoDB transactions use `snapshot` isolation and require a replica set or
|
|
348
|
+
sharded deployment; standalone servers do not support them.
|
|
306
349
|
|
|
307
350
|
## Batch and pipes
|
|
308
351
|
|
|
@@ -345,9 +388,10 @@ stql batch commands.json
|
|
|
345
388
|
|
|
346
389
|
Batch fields use snake case. Supported command names match CLI paths, such as
|
|
347
390
|
`filter`, `transaction.begin`, `session.summary`, `alias.set`, `plan`, and
|
|
348
|
-
`apply`.
|
|
349
|
-
|
|
350
|
-
|
|
391
|
+
`apply`. Native MongoDB batches use `mongo.query`, `mongo.exec`, or `mongo.plan`
|
|
392
|
+
with the command object in `mongo`; the same cache, replay, idempotency, safety,
|
|
393
|
+
and timeout fields apply. Database commands may set `timeout_ms`; otherwise they
|
|
394
|
+
use the 30-second default.
|
|
351
395
|
|
|
352
396
|
## TypeScript library
|
|
353
397
|
|
|
@@ -440,13 +484,12 @@ safety and duplicate checks. Requests contain actor and session identity, the
|
|
|
440
484
|
operation's effective read/write access, an abort signal, and sanitized
|
|
441
485
|
connection metadata.
|
|
442
486
|
|
|
443
|
-
Returned values must be complete PostgreSQL or
|
|
487
|
+
Returned values must be complete PostgreSQL, MySQL, or MongoDB URLs, or explicit
|
|
444
488
|
`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.
|
|
489
|
+
adapter construction and normalizes SQLite paths. Credential-bearing database
|
|
490
|
+
URLs are redacted before connection metadata is persisted and never enter
|
|
491
|
+
history, snapshots, cache keys, or responses. SQLite paths remain persisted
|
|
492
|
+
connection metadata, as they are for direct SQLite connections.
|
|
450
493
|
|
|
451
494
|
Harnesses remain responsible for approval policy, binding lifetime, revocation,
|
|
452
495
|
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, CloseSessionData, Column, ColumnsData, CommitTransactionData, ConnectOptions, ConnectionData, CountData, CredentialAccess, CredentialOperation, CredentialRequest, CredentialResolver, DisconnectData, DoctorData, Driver, ExecData, ExecOptions, ExecutionOptions, ExportData, Failure, FilterOptions, HistoryData, HistoryEntry, 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,15 @@ 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
|
+
},
|
|
58
67
|
];
|
|
59
68
|
export function runMigrations(db, now) {
|
|
60
69
|
db.exec(`
|
|
@@ -169,6 +178,7 @@ function createInitialSchema(db) {
|
|
|
169
178
|
idempotency_key TEXT,
|
|
170
179
|
state_version_before TEXT NOT NULL,
|
|
171
180
|
state_version_after TEXT,
|
|
181
|
+
outcome_json TEXT,
|
|
172
182
|
created_at TEXT NOT NULL
|
|
173
183
|
);
|
|
174
184
|
CREATE INDEX IF NOT EXISTS operations_fingerprint
|
|
@@ -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 {};
|