@fadhilp/stateql 0.9.0 → 0.10.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 +140 -5
- package/dist/src/adapters.d.ts +7 -2
- package/dist/src/adapters.js +167 -0
- package/dist/src/cli.js +66 -3
- package/dist/src/connection.d.ts +1 -0
- package/dist/src/connection.js +18 -2
- package/dist/src/index.d.ts +2 -1
- package/dist/src/migrations.js +33 -0
- package/dist/src/mongodb.d.ts +4 -2
- package/dist/src/mongodb.js +53 -2
- package/dist/src/redis.d.ts +44 -0
- package/dist/src/redis.js +395 -0
- package/dist/src/sqlite-process.js +41 -0
- package/dist/src/stateql.d.ts +18 -4
- package/dist/src/stateql.js +646 -62
- package/dist/src/store.d.ts +22 -2
- package/dist/src/store.js +92 -12
- package/dist/src/table-editor.d.ts +5 -0
- package/dist/src/table-editor.js +22 -0
- package/dist/src/types.d.ts +74 -3
- package/package.json +4 -2
package/dist/src/store.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync } from "node:sqlite";
|
|
2
|
-
import type { Column, CommandOrigin, Driver, MongoWriteOutcome, Row, SqlParameters, StateConfidence } from "./types.js";
|
|
2
|
+
import type { Column, CommandOrigin, Driver, HistoryCategory, MongoWriteOutcome, Row, SqlParameters, StateConfidence } from "./types.js";
|
|
3
3
|
export interface SessionRecord {
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
|
@@ -46,6 +46,7 @@ export interface ResultRecord {
|
|
|
46
46
|
state_confidence: StateConfidence;
|
|
47
47
|
expires_at: string;
|
|
48
48
|
created_at: string;
|
|
49
|
+
alias?: string;
|
|
49
50
|
}
|
|
50
51
|
export interface OperationRecord {
|
|
51
52
|
id: string;
|
|
@@ -101,6 +102,8 @@ export interface HistoryRecord {
|
|
|
101
102
|
session_id: string;
|
|
102
103
|
actor_id: string;
|
|
103
104
|
origin: CommandOrigin;
|
|
105
|
+
category: HistoryCategory;
|
|
106
|
+
internal: number;
|
|
104
107
|
command: string;
|
|
105
108
|
sql: string | null;
|
|
106
109
|
target: string | null;
|
|
@@ -142,6 +145,13 @@ export declare class StateStore {
|
|
|
142
145
|
credentialRef?: string;
|
|
143
146
|
readOnly: boolean;
|
|
144
147
|
}): ProfileRecord;
|
|
148
|
+
updateProfile(input: {
|
|
149
|
+
name: string;
|
|
150
|
+
target: string | null;
|
|
151
|
+
secretEnv: string | null;
|
|
152
|
+
credentialRef: string | null;
|
|
153
|
+
readOnly: boolean;
|
|
154
|
+
}): ProfileRecord | undefined;
|
|
145
155
|
getProfile(name: string): ProfileRecord | undefined;
|
|
146
156
|
listProfiles(): ProfileRecord[];
|
|
147
157
|
removeProfile(name: string): boolean;
|
|
@@ -178,6 +188,9 @@ export declare class StateStore {
|
|
|
178
188
|
resultRows(result: ResultRecord): Row[];
|
|
179
189
|
resultColumns(result: ResultRecord): Column[];
|
|
180
190
|
setAlias(sessionId: string, name: string, resultId: string): void;
|
|
191
|
+
generatedAlias(resultId: string): string;
|
|
192
|
+
private allocateGeneratedAlias;
|
|
193
|
+
private backfillGeneratedAliases;
|
|
181
194
|
saveOperation(input: {
|
|
182
195
|
sessionId: string;
|
|
183
196
|
actorId: string;
|
|
@@ -277,6 +290,8 @@ export declare class StateStore {
|
|
|
277
290
|
sessionId: string;
|
|
278
291
|
actorId: string;
|
|
279
292
|
origin?: CommandOrigin;
|
|
293
|
+
category?: HistoryCategory;
|
|
294
|
+
internal?: boolean;
|
|
280
295
|
command: string;
|
|
281
296
|
sql?: string;
|
|
282
297
|
target?: string;
|
|
@@ -287,7 +302,12 @@ export declare class StateStore {
|
|
|
287
302
|
errorCode?: string;
|
|
288
303
|
id?: string;
|
|
289
304
|
}): HistoryRecord;
|
|
290
|
-
history(sessionId: string, limit: number,
|
|
305
|
+
history(sessionId: string, limit: number, input?: CommandOrigin | {
|
|
306
|
+
origin?: CommandOrigin;
|
|
307
|
+
category?: HistoryCategory;
|
|
308
|
+
internal?: boolean;
|
|
309
|
+
offset?: number;
|
|
310
|
+
}): HistoryRecord[];
|
|
291
311
|
recentOperations(sessionId: string, limit: number): OperationRecord[];
|
|
292
312
|
knownResults(sessionId: string, limit: number): Array<ResultRecord & {
|
|
293
313
|
alias: string | null;
|
package/dist/src/store.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import { chmodSync, existsSync, mkdirSync, statSync } from "node:fs";
|
|
2
3
|
import { DatabaseSync } from "node:sqlite";
|
|
3
4
|
import { join } from "node:path";
|
|
@@ -35,6 +36,7 @@ export class StateStore {
|
|
|
35
36
|
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
36
37
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
37
38
|
runMigrations(this.db, this.now);
|
|
39
|
+
this.backfillGeneratedAliases();
|
|
38
40
|
this.recoverStaleCommittingTransactions();
|
|
39
41
|
this.deleteExpiredData();
|
|
40
42
|
}
|
|
@@ -240,6 +242,12 @@ export class StateStore {
|
|
|
240
242
|
.run(input.name, input.target ?? null, input.secretEnv ?? null, input.credentialRef ?? null, input.readOnly ? 1 : 0, timestamp, timestamp);
|
|
241
243
|
return this.getProfile(input.name);
|
|
242
244
|
}
|
|
245
|
+
updateProfile(input) {
|
|
246
|
+
const result = this.db.prepare(`UPDATE profiles
|
|
247
|
+
SET target = ?, secret_env = ?, credential_ref = ?, read_only = ?, updated_at = ?
|
|
248
|
+
WHERE name = ?`).run(input.target, input.secretEnv, input.credentialRef, input.readOnly ? 1 : 0, this.now().toISOString(), input.name);
|
|
249
|
+
return Number(result.changes) === 1 ? this.getProfile(input.name) : undefined;
|
|
250
|
+
}
|
|
243
251
|
getProfile(name) {
|
|
244
252
|
return this.db
|
|
245
253
|
.prepare("SELECT * FROM profiles WHERE name = ?")
|
|
@@ -341,6 +349,7 @@ export class StateStore {
|
|
|
341
349
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
342
350
|
.run(id, input.sessionId, input.connectionId, input.fingerprint, input.sql, JSON.stringify(toJsonSafe(input.parameters)), JSON.stringify(toJsonSafe(input.rows)), JSON.stringify(input.columns), input.rows.length, input.stateVersion, input.stateSignature, input.stateConfidence, input.expiresAt, this.now().toISOString());
|
|
343
351
|
this.enforceResultQuota(id);
|
|
352
|
+
this.allocateGeneratedAlias(input.sessionId, id);
|
|
344
353
|
const result = this.getResult(id);
|
|
345
354
|
this.db.exec("COMMIT");
|
|
346
355
|
return result;
|
|
@@ -351,15 +360,18 @@ export class StateStore {
|
|
|
351
360
|
}
|
|
352
361
|
}
|
|
353
362
|
findResult(fingerprint) {
|
|
354
|
-
|
|
363
|
+
const result = this.db
|
|
355
364
|
.prepare(`SELECT * FROM results
|
|
356
365
|
WHERE fingerprint = ?
|
|
357
366
|
ORDER BY created_at DESC
|
|
358
367
|
LIMIT 1`)
|
|
359
368
|
.get(fingerprint);
|
|
369
|
+
if (result)
|
|
370
|
+
result.alias = this.generatedAlias(result.id);
|
|
371
|
+
return result;
|
|
360
372
|
}
|
|
361
373
|
getResult(idOrAlias, sessionId) {
|
|
362
|
-
|
|
374
|
+
const result = this.db
|
|
363
375
|
.prepare(`SELECT results.*
|
|
364
376
|
FROM results
|
|
365
377
|
LEFT JOIN aliases
|
|
@@ -369,6 +381,9 @@ export class StateStore {
|
|
|
369
381
|
AND (? IS NULL OR results.session_id = ?)
|
|
370
382
|
LIMIT 1`)
|
|
371
383
|
.get(idOrAlias, idOrAlias, sessionId ?? null, sessionId ?? null);
|
|
384
|
+
if (result)
|
|
385
|
+
result.alias = this.generatedAlias(result.id);
|
|
386
|
+
return result;
|
|
372
387
|
}
|
|
373
388
|
resultRows(result) {
|
|
374
389
|
return parseJson(result.rows_json, `result "${result.id}" rows`, isRows);
|
|
@@ -377,12 +392,55 @@ export class StateStore {
|
|
|
377
392
|
return parseJson(result.columns_json, `result "${result.id}" columns`, isColumns);
|
|
378
393
|
}
|
|
379
394
|
setAlias(sessionId, name, resultId) {
|
|
395
|
+
const existing = this.db.prepare("SELECT result_id, generated FROM aliases WHERE session_id = ? AND name = ?").get(sessionId, name);
|
|
396
|
+
if (existing?.generated) {
|
|
397
|
+
if (existing.result_id === resultId)
|
|
398
|
+
return;
|
|
399
|
+
throw new StateQLError("INVALID_COMMAND", "Generated result aliases cannot be reassigned.");
|
|
400
|
+
}
|
|
380
401
|
this.db
|
|
381
|
-
.prepare(`INSERT INTO aliases(session_id, name, result_id)
|
|
382
|
-
VALUES (?, ?,
|
|
402
|
+
.prepare(`INSERT INTO aliases(session_id, name, result_id, generated)
|
|
403
|
+
VALUES (?, ?, ?, 0)
|
|
383
404
|
ON CONFLICT(session_id, name) DO UPDATE SET result_id = excluded.result_id`)
|
|
384
405
|
.run(sessionId, name, resultId);
|
|
385
406
|
}
|
|
407
|
+
generatedAlias(resultId) {
|
|
408
|
+
const row = this.db.prepare("SELECT name FROM aliases WHERE result_id = ? AND generated = 1").get(resultId);
|
|
409
|
+
if (!row)
|
|
410
|
+
throw new Error(`Result "${resultId}" has no generated alias.`);
|
|
411
|
+
return row.name;
|
|
412
|
+
}
|
|
413
|
+
allocateGeneratedAlias(sessionId, resultId) {
|
|
414
|
+
const existing = this.db.prepare("SELECT name FROM aliases WHERE result_id = ? AND generated = 1").get(resultId);
|
|
415
|
+
if (existing)
|
|
416
|
+
return existing.name;
|
|
417
|
+
for (let attempt = 0; attempt < 64; attempt++) {
|
|
418
|
+
const name = randomBase32Alias();
|
|
419
|
+
this.db.prepare("INSERT OR IGNORE INTO aliases(session_id, name, result_id, generated) VALUES (?, ?, ?, 1)").run(sessionId, name, resultId);
|
|
420
|
+
const allocated = this.db.prepare("SELECT name FROM aliases WHERE result_id = ? AND generated = 1").get(resultId);
|
|
421
|
+
if (allocated)
|
|
422
|
+
return allocated.name;
|
|
423
|
+
}
|
|
424
|
+
throw new Error("Could not allocate a unique result alias.");
|
|
425
|
+
}
|
|
426
|
+
backfillGeneratedAliases() {
|
|
427
|
+
const results = this.db.prepare(`SELECT results.id, results.session_id
|
|
428
|
+
FROM results
|
|
429
|
+
LEFT JOIN aliases ON aliases.result_id = results.id AND aliases.generated = 1
|
|
430
|
+
WHERE aliases.result_id IS NULL`).all();
|
|
431
|
+
if (!results.length)
|
|
432
|
+
return;
|
|
433
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
434
|
+
try {
|
|
435
|
+
for (const result of results)
|
|
436
|
+
this.allocateGeneratedAlias(result.session_id, result.id);
|
|
437
|
+
this.db.exec("COMMIT");
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
this.db.exec("ROLLBACK");
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
386
444
|
saveOperation(input) {
|
|
387
445
|
const id = this.nextId("op");
|
|
388
446
|
this.db
|
|
@@ -797,10 +855,10 @@ export class StateStore {
|
|
|
797
855
|
const id = input.id ?? this.nextId("cmd");
|
|
798
856
|
this.db
|
|
799
857
|
.prepare(`INSERT INTO history
|
|
800
|
-
(id, timestamp, session_id, actor_id, origin, command, sql, target, handle,
|
|
858
|
+
(id, timestamp, session_id, actor_id, origin, category, internal, command, sql, target, handle,
|
|
801
859
|
executed, cached, success, error_code)
|
|
802
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
803
|
-
.run(id, this.now().toISOString(), input.sessionId, input.actorId, input.origin ?? "legacy", input.command, boundedHistorySql(input.sql), input.target?.slice(0, 1024) ?? null, input.handle ?? null, input.executed ? 1 : 0, input.cached ? 1 : 0, input.success ? 1 : 0, input.errorCode ?? null);
|
|
860
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
861
|
+
.run(id, this.now().toISOString(), input.sessionId, input.actorId, input.origin ?? "legacy", input.category ?? "management", input.internal ? 1 : 0, input.command, boundedHistorySql(input.sql), input.target?.slice(0, 1024) ?? null, input.handle ?? null, input.executed ? 1 : 0, input.cached ? 1 : 0, input.success ? 1 : 0, input.errorCode ?? null);
|
|
804
862
|
this.db
|
|
805
863
|
.prepare(`DELETE FROM history
|
|
806
864
|
WHERE rowid IN (
|
|
@@ -814,13 +872,29 @@ export class StateStore {
|
|
|
814
872
|
.prepare("SELECT * FROM history WHERE id = ?")
|
|
815
873
|
.get(id);
|
|
816
874
|
}
|
|
817
|
-
history(sessionId, limit,
|
|
875
|
+
history(sessionId, limit, input = {}) {
|
|
876
|
+
const options = typeof input === "string" ? { origin: input } : input;
|
|
877
|
+
const predicates = ["session_id = ?"];
|
|
878
|
+
const values = [sessionId];
|
|
879
|
+
if (options.origin !== undefined) {
|
|
880
|
+
predicates.push("origin = ?");
|
|
881
|
+
values.push(options.origin);
|
|
882
|
+
}
|
|
883
|
+
if (options.category !== undefined) {
|
|
884
|
+
predicates.push("category = ?");
|
|
885
|
+
values.push(options.category);
|
|
886
|
+
}
|
|
887
|
+
if (options.internal !== undefined) {
|
|
888
|
+
predicates.push("internal = ?");
|
|
889
|
+
values.push(options.internal ? 1 : 0);
|
|
890
|
+
}
|
|
891
|
+
values.push(limit, options.offset ?? 0);
|
|
818
892
|
return this.db
|
|
819
893
|
.prepare(`SELECT * FROM history
|
|
820
|
-
WHERE
|
|
894
|
+
WHERE ${predicates.join(" AND ")}
|
|
821
895
|
ORDER BY rowid DESC
|
|
822
|
-
LIMIT ?`)
|
|
823
|
-
.all(...
|
|
896
|
+
LIMIT ? OFFSET ?`)
|
|
897
|
+
.all(...values);
|
|
824
898
|
}
|
|
825
899
|
recentOperations(sessionId, limit) {
|
|
826
900
|
return this.db
|
|
@@ -837,6 +911,7 @@ export class StateStore {
|
|
|
837
911
|
LEFT JOIN aliases
|
|
838
912
|
ON aliases.result_id = results.id
|
|
839
913
|
AND aliases.session_id = results.session_id
|
|
914
|
+
AND aliases.generated = 1
|
|
840
915
|
WHERE results.session_id = ?
|
|
841
916
|
ORDER BY results.created_at DESC
|
|
842
917
|
LIMIT ?`)
|
|
@@ -946,12 +1021,13 @@ export class StateStore {
|
|
|
946
1021
|
while (this.resultBytes() > this.maxStateBytes) {
|
|
947
1022
|
const candidate = this.db.prepare(`SELECT id FROM results
|
|
948
1023
|
WHERE id <> ? AND NOT EXISTS (
|
|
949
|
-
SELECT 1 FROM aliases WHERE aliases.result_id = results.id
|
|
1024
|
+
SELECT 1 FROM aliases WHERE aliases.result_id = results.id AND aliases.generated = 0
|
|
950
1025
|
)
|
|
951
1026
|
ORDER BY created_at, rowid LIMIT 1`).get(protectedId);
|
|
952
1027
|
if (!candidate) {
|
|
953
1028
|
throw new StateQLError("STATE_QUOTA_EXCEEDED", `Stored results exceed the ${this.maxStateBytes}-byte state quota.`, { suggestedAction: "Purge results or increase maxStateBytes." });
|
|
954
1029
|
}
|
|
1030
|
+
this.db.prepare("DELETE FROM aliases WHERE result_id = ? AND generated = 1").run(candidate.id);
|
|
955
1031
|
this.db.prepare("DELETE FROM results WHERE id = ?").run(candidate.id);
|
|
956
1032
|
}
|
|
957
1033
|
}
|
|
@@ -1019,6 +1095,10 @@ export class StateStore {
|
|
|
1019
1095
|
function outcomeJson(outcome) {
|
|
1020
1096
|
return outcome === undefined ? null : JSON.stringify(toJsonSafe(outcome));
|
|
1021
1097
|
}
|
|
1098
|
+
function randomBase32Alias() {
|
|
1099
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
1100
|
+
return [...randomBytes(10)].map((value) => alphabet[value & 31]).join("");
|
|
1101
|
+
}
|
|
1022
1102
|
function boundedHistorySql(sql) {
|
|
1023
1103
|
if (sql === undefined)
|
|
1024
1104
|
return null;
|
|
@@ -26,6 +26,10 @@ export interface TableUpdate {
|
|
|
26
26
|
original: Row;
|
|
27
27
|
changes: TableChange;
|
|
28
28
|
}
|
|
29
|
+
export interface TableUpdateBatch {
|
|
30
|
+
version: 1;
|
|
31
|
+
updates: TableUpdate[];
|
|
32
|
+
}
|
|
29
33
|
export declare function quoteIdentifier(name: string, driver: Driver): string;
|
|
30
34
|
export declare function editableRow(metadata: EditableTable, row: Row): boolean;
|
|
31
35
|
export declare function compileTableUpdate(update: TableUpdate): {
|
|
@@ -34,3 +38,4 @@ export declare function compileTableUpdate(update: TableUpdate): {
|
|
|
34
38
|
mongo?: MongoWriteCommand;
|
|
35
39
|
};
|
|
36
40
|
export declare function parseTableUpdate(parameters: string): TableUpdate;
|
|
41
|
+
export declare function parseTableUpdates(parameters: string): TableUpdate[];
|
package/dist/src/table-editor.js
CHANGED
|
@@ -113,3 +113,25 @@ export function parseTableUpdate(parameters) {
|
|
|
113
113
|
throw new StateQLError("STALE_PLAN", "The stored table update is invalid. Reload the row and plan again.");
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
export function parseTableUpdates(parameters) {
|
|
117
|
+
try {
|
|
118
|
+
const outer = JSON.parse(parameters);
|
|
119
|
+
if (!Array.isArray(outer) || outer.length !== 1 || typeof outer[0] !== "string" || outer[0].length > 512 * 1024)
|
|
120
|
+
throw new Error();
|
|
121
|
+
const batch = JSON.parse(outer[0]);
|
|
122
|
+
if (!batch || batch.version !== 1 || !Array.isArray(batch.updates) || batch.updates.length < 1 || batch.updates.length > 100)
|
|
123
|
+
throw new Error();
|
|
124
|
+
for (const update of batch.updates) {
|
|
125
|
+
if (!update?.metadata || !["sqlite", "postgres", "mysql", "mongodb"].includes(update.metadata.driver) ||
|
|
126
|
+
!Array.isArray(update.metadata.columns) || update.metadata.columns.length > 100 ||
|
|
127
|
+
!update.metadata.columns.every(column => typeof column.name === "string" && typeof column.type === "string" && typeof column.nullable === "boolean" && typeof column.generated === "boolean" && Number.isSafeInteger(column.key)) ||
|
|
128
|
+
!update.original || !update.changes || typeof update.changes !== "object")
|
|
129
|
+
throw new Error();
|
|
130
|
+
compileTableUpdate(update);
|
|
131
|
+
}
|
|
132
|
+
return batch.updates;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw new StateQLError("STALE_PLAN", "The stored table update batch is invalid. Reload the rows and plan again.");
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export type SqlDriver = "sqlite" | "postgres" | "mysql";
|
|
2
|
-
export type Driver = SqlDriver | "mongodb";
|
|
2
|
+
export type Driver = SqlDriver | "mongodb" | "redis";
|
|
3
3
|
export type CommandOrigin = "legacy" | "user" | "model" | "system" | "api";
|
|
4
|
+
export type HistoryCategory = "statement" | "introspection" | "management";
|
|
4
5
|
/** Trusted host metadata for one executeCommand call; never part of BatchCommand input. */
|
|
5
6
|
export interface CommandExecutionContext {
|
|
6
7
|
signal?: AbortSignal;
|
|
7
8
|
origin?: CommandOrigin;
|
|
9
|
+
/** Marks host-generated setup or introspection separately from user statements. */
|
|
10
|
+
internal?: boolean;
|
|
8
11
|
}
|
|
9
12
|
export type CredentialAccess = "read" | "write";
|
|
10
13
|
export type CredentialOperation = "connect" | "query" | "inspect" | "plan" | "exec" | "apply" | "transaction.commit";
|
|
@@ -78,6 +81,8 @@ export interface HistoryEntry {
|
|
|
78
81
|
session_id: string;
|
|
79
82
|
actor_id: string;
|
|
80
83
|
origin: CommandOrigin;
|
|
84
|
+
category: HistoryCategory;
|
|
85
|
+
internal: boolean;
|
|
81
86
|
command: string;
|
|
82
87
|
sql: string | null;
|
|
83
88
|
target?: string | null;
|
|
@@ -87,6 +92,11 @@ export interface HistoryEntry {
|
|
|
87
92
|
success: boolean;
|
|
88
93
|
error_code: string | null;
|
|
89
94
|
}
|
|
95
|
+
export interface StateQLSnapshotOptions {
|
|
96
|
+
historyLimit?: number;
|
|
97
|
+
historyCategory?: HistoryCategory;
|
|
98
|
+
historyInternal?: boolean;
|
|
99
|
+
}
|
|
90
100
|
export interface StateQLSnapshot {
|
|
91
101
|
session: {
|
|
92
102
|
session_id: string;
|
|
@@ -195,12 +205,49 @@ export interface MongoWriteOutcome {
|
|
|
195
205
|
upserted_id?: unknown;
|
|
196
206
|
deleted_count?: number;
|
|
197
207
|
}
|
|
208
|
+
export interface RedisCommand {
|
|
209
|
+
command: string;
|
|
210
|
+
args?: string[];
|
|
211
|
+
}
|
|
212
|
+
export interface RedisWriteOutcome extends MongoWriteOutcome {
|
|
213
|
+
result: string | number | null;
|
|
214
|
+
}
|
|
215
|
+
export type CatalogObjectKind = "table" | "view" | "collection" | "function" | "trigger" | "enum" | "key";
|
|
216
|
+
export interface CatalogObject {
|
|
217
|
+
kind: CatalogObjectKind;
|
|
218
|
+
schema?: string;
|
|
219
|
+
name: string;
|
|
220
|
+
/** Stable database-native overload/object identity when name alone is ambiguous. */
|
|
221
|
+
identity?: string;
|
|
222
|
+
[key: string]: unknown;
|
|
223
|
+
}
|
|
224
|
+
export interface ListObjectsFilter {
|
|
225
|
+
kind?: CatalogObjectKind;
|
|
226
|
+
schema?: string;
|
|
227
|
+
search?: string;
|
|
228
|
+
/** Numeric for SQL/MongoDB; Redis uses its opaque SCAN cursor string. */
|
|
229
|
+
offset?: number | string;
|
|
230
|
+
limit?: number;
|
|
231
|
+
}
|
|
232
|
+
export interface ListObjectsData {
|
|
233
|
+
objects: CatalogObject[];
|
|
234
|
+
next_offset: number | string | null;
|
|
235
|
+
supported_kinds: CatalogObjectKind[];
|
|
236
|
+
}
|
|
237
|
+
export interface DescribeObjectData {
|
|
238
|
+
object: CatalogObject;
|
|
239
|
+
definition?: string | Record<string, unknown> | unknown[] | null;
|
|
240
|
+
[key: string]: unknown;
|
|
241
|
+
}
|
|
198
242
|
export interface ExecutionOptions {
|
|
199
243
|
timeoutMs?: number;
|
|
200
244
|
signal?: AbortSignal;
|
|
201
245
|
}
|
|
202
246
|
export interface HistoryOptions {
|
|
203
247
|
origin?: CommandOrigin;
|
|
248
|
+
category?: HistoryCategory;
|
|
249
|
+
internal?: boolean;
|
|
250
|
+
offset?: number;
|
|
204
251
|
}
|
|
205
252
|
export interface StateQLOptions extends ExecutionOptions {
|
|
206
253
|
home?: string;
|
|
@@ -256,6 +303,21 @@ export interface ProfileOptions {
|
|
|
256
303
|
secretEnv?: string;
|
|
257
304
|
credentialRef?: string;
|
|
258
305
|
}
|
|
306
|
+
export interface ProfileUpdateOptions {
|
|
307
|
+
target?: string | null;
|
|
308
|
+
secretEnv?: string | null;
|
|
309
|
+
credentialRef?: string | null;
|
|
310
|
+
readOnly?: boolean;
|
|
311
|
+
}
|
|
312
|
+
export interface RedisQueryOptions extends ExecutionOptions {
|
|
313
|
+
cache?: "auto" | "bypass" | "require";
|
|
314
|
+
}
|
|
315
|
+
export interface RedisExecOptions extends ExecutionOptions {
|
|
316
|
+
replay?: boolean;
|
|
317
|
+
idempotencyKey?: string;
|
|
318
|
+
}
|
|
319
|
+
export interface RedisPlanOptions extends ExecutionOptions {
|
|
320
|
+
}
|
|
259
321
|
export interface RowsOptions {
|
|
260
322
|
offset?: number;
|
|
261
323
|
limit?: number;
|
|
@@ -269,7 +331,7 @@ export interface MongoPlanOptions extends ExecutionOptions {
|
|
|
269
331
|
allowUnbounded?: boolean;
|
|
270
332
|
allowDestructive?: boolean;
|
|
271
333
|
}
|
|
272
|
-
export type BatchCommandName = "connect" | "disconnect" | "status" | "profile.add" | "profile.list" | "profile.show" | "profile.remove" | "session.start" | "session.list" | "session.show" | "session.summary" | "session.close" | "query" | "filter" | "exec" | "show" | "rows" | "count" | "columns" | "alias.set" | "inspect" | "transaction.begin" | "transaction.status" | "transaction.commit" | "transaction.rollback" | "plan" | "mongo.query" | "mongo.exec" | "mongo.plan" | "apply" | "history" | "receipt" | "doctor" | "purge" | "capabilities";
|
|
334
|
+
export type BatchCommandName = "connect" | "disconnect" | "status" | "profile.add" | "profile.list" | "profile.show" | "profile.remove" | "profile.update" | "session.start" | "session.list" | "session.show" | "session.summary" | "session.close" | "query" | "filter" | "exec" | "show" | "rows" | "count" | "columns" | "alias.set" | "inspect" | "transaction.begin" | "transaction.status" | "transaction.commit" | "transaction.rollback" | "plan" | "mongo.query" | "mongo.exec" | "mongo.plan" | "redis.query" | "redis.exec" | "redis.plan" | "objects.list" | "object.describe" | "apply" | "history" | "receipt" | "doctor" | "purge" | "capabilities";
|
|
273
335
|
export interface BatchCommand {
|
|
274
336
|
command: BatchCommandName;
|
|
275
337
|
target?: string;
|
|
@@ -282,6 +344,8 @@ export interface BatchCommand {
|
|
|
282
344
|
table?: string;
|
|
283
345
|
params?: SqlParameters;
|
|
284
346
|
mongo?: MongoReadCommand | MongoWriteCommand;
|
|
347
|
+
redis?: RedisCommand;
|
|
348
|
+
object?: CatalogObject;
|
|
285
349
|
cache?: "auto" | "bypass" | "require";
|
|
286
350
|
read_only?: boolean;
|
|
287
351
|
secret_env?: string;
|
|
@@ -292,11 +356,14 @@ export interface BatchCommand {
|
|
|
292
356
|
allow_unbounded?: boolean;
|
|
293
357
|
allow_destructive?: boolean;
|
|
294
358
|
offset?: number;
|
|
359
|
+
cursor?: string;
|
|
295
360
|
limit?: number;
|
|
296
361
|
isolation?: string;
|
|
297
362
|
timeout_ms?: number;
|
|
298
363
|
/** Retrieval filter for the history command; does not attribute this command. */
|
|
299
364
|
history_origin?: CommandOrigin;
|
|
365
|
+
history_category?: HistoryCategory;
|
|
366
|
+
history_internal?: boolean;
|
|
300
367
|
scope?: "expired" | "results" | "history" | "all";
|
|
301
368
|
}
|
|
302
369
|
export interface BatchOptions {
|
|
@@ -416,6 +483,9 @@ export interface SessionSummaryData {
|
|
|
416
483
|
}
|
|
417
484
|
export interface ResultData {
|
|
418
485
|
result_id: string;
|
|
486
|
+
alias: string;
|
|
487
|
+
/** Canonical generated alias; remains stable even when alias is an explicit caller alias. */
|
|
488
|
+
display_alias: string;
|
|
419
489
|
rows: number;
|
|
420
490
|
columns: Column[];
|
|
421
491
|
preview: Row[];
|
|
@@ -428,6 +498,7 @@ export interface ResultData {
|
|
|
428
498
|
mode: string;
|
|
429
499
|
expires_at: string;
|
|
430
500
|
};
|
|
501
|
+
next_cursor?: string | null;
|
|
431
502
|
}
|
|
432
503
|
export interface RowsData {
|
|
433
504
|
result_id: string;
|
|
@@ -468,7 +539,7 @@ export interface OperationData {
|
|
|
468
539
|
state_version_before: string;
|
|
469
540
|
state_version_after: string | null;
|
|
470
541
|
replay_of?: string;
|
|
471
|
-
outcome?: MongoWriteOutcome;
|
|
542
|
+
outcome?: MongoWriteOutcome | RedisWriteOutcome;
|
|
472
543
|
}
|
|
473
544
|
export interface ExecData extends OperationData {
|
|
474
545
|
duplicate?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fadhilp/stateql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Stateful, agent-oriented database CLI for safe result reuse",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsc -p tsconfig.json",
|
|
28
|
-
"test": "npm run build && node --test dist/test/cli.test.js dist/test/credential.test.js dist/test/mongodb.test.js dist/test/mysql.test.js dist/test/postgres.test.js dist/test/query.test.js dist/test/sqlite.test.js dist/test/store.test.js dist/test/terminal.test.js dist/test/transaction.test.js dist/test/write.test.js dist/test/panel.test.js",
|
|
28
|
+
"test": "npm run build && node --test dist/test/cli.test.js dist/test/credential.test.js dist/test/mongodb.test.js dist/test/mysql.test.js dist/test/postgres.test.js dist/test/query.test.js dist/test/redis.test.js dist/test/sqlite.test.js dist/test/store.test.js dist/test/terminal.test.js dist/test/transaction.test.js dist/test/write.test.js dist/test/panel.test.js",
|
|
29
29
|
"release": "npm version",
|
|
30
30
|
"version": "npm install",
|
|
31
31
|
"prepack": "npm test"
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"postgresql",
|
|
38
38
|
"cli",
|
|
39
39
|
"agents",
|
|
40
|
+
"redis",
|
|
40
41
|
"sql"
|
|
41
42
|
],
|
|
42
43
|
"license": "MIT",
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
"node": ">=22.16"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
48
|
+
"@redis/client": "^5.12.1",
|
|
47
49
|
"mongodb": "^6.21.0",
|
|
48
50
|
"mysql2": "^3.23.1",
|
|
49
51
|
"node-sql-parser": "^5.4.0",
|