@fadhilp/stateql 0.5.2 → 0.5.3

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.
@@ -46,6 +46,15 @@ const MIGRATIONS = [
46
46
  apply: migrateSharedSessionActors,
47
47
  validate: validateSharedSessionActors,
48
48
  },
49
+ {
50
+ name: "history_sql_v1",
51
+ apply(db) {
52
+ addColumn(db, "history", "sql", "TEXT");
53
+ },
54
+ validate(db) {
55
+ requireColumns(db, "history", ["sql"]);
56
+ },
57
+ },
49
58
  ];
50
59
  export function runMigrations(db, now) {
51
60
  db.exec(`
@@ -202,6 +211,7 @@ function createInitialSchema(db) {
202
211
  session_id TEXT NOT NULL,
203
212
  actor_id TEXT NOT NULL,
204
213
  command TEXT NOT NULL,
214
+ sql TEXT,
205
215
  handle TEXT,
206
216
  executed INTEGER NOT NULL,
207
217
  cached INTEGER NOT NULL,
@@ -613,7 +613,7 @@ export class StateQL {
613
613
  finally {
614
614
  await closeAdapterQuietly(adapter);
615
615
  }
616
- });
616
+ }, sql);
617
617
  }
618
618
  async show(idOrAlias) {
619
619
  return this.withResult("show", idOrAlias, async (result) => ({
@@ -756,7 +756,7 @@ export class StateQL {
756
756
  return this.run("exec", async (session) => {
757
757
  const connection = this.requireConnection(session);
758
758
  return this.performExec(session, connection, sql, options, this.executionContext(options));
759
- });
759
+ }, sql);
760
760
  }
761
761
  async receipt(id) {
762
762
  return this.run("receipt", async (session) => {
@@ -1023,9 +1023,10 @@ export class StateQL {
1023
1023
  finally {
1024
1024
  await closeAdapterQuietly(adapter);
1025
1025
  }
1026
- });
1026
+ }, sql);
1027
1027
  }
1028
1028
  async apply(planId, options = {}) {
1029
+ let historySql;
1029
1030
  return this.run("apply", async (session) => {
1030
1031
  this.rejectDuringStagedTransaction(session, "Plans");
1031
1032
  const plan = this.store.getPlan(planId);
@@ -1043,6 +1044,7 @@ export class StateQL {
1043
1044
  if (Date.parse(plan.expires_at) <= this.now().getTime()) {
1044
1045
  throw new StateQLError("STALE_PLAN", "Plan has expired.");
1045
1046
  }
1047
+ historySql = plan.sql;
1046
1048
  const planParameters = parseJson(plan.parameters, `plan "${plan.id}" parameters`, isSqlParameters);
1047
1049
  const claimToken = this.store.nextId("claim");
1048
1050
  const claimed = this.store.claimPlan(plan.id, session.id, this.actorId, claimToken);
@@ -1096,7 +1098,7 @@ export class StateQL {
1096
1098
  if (!retainClaim)
1097
1099
  this.store.releasePlanClaim(plan.id, claimToken);
1098
1100
  }
1099
- });
1101
+ }, () => historySql);
1100
1102
  }
1101
1103
  async history(limit = 20) {
1102
1104
  return this.run("history", async (session) => ({
@@ -1642,7 +1644,7 @@ export class StateQL {
1642
1644
  result.state_version === stateVersion &&
1643
1645
  result.state_signature === stateSignature);
1644
1646
  }
1645
- async run(command, action) {
1647
+ async run(command, action, historySql) {
1646
1648
  const started = performance.now();
1647
1649
  let session = this.store.ensureSession(this.sessionName);
1648
1650
  const commandId = this.store.nextId("cmd");
@@ -1661,12 +1663,14 @@ export class StateQL {
1661
1663
  try {
1662
1664
  const result = await action(session);
1663
1665
  const responseSession = result.session ?? session;
1666
+ const sqlText = resolveHistorySql(historySql);
1664
1667
  this.store.addHistory({
1665
1668
  id: commandId,
1666
1669
  sessionId: session.id,
1667
1670
  actorId: this.actorId,
1668
1671
  command,
1669
1672
  ...(result.handle ? { handle: result.handle } : {}),
1673
+ ...(sqlText !== undefined ? { sql: sqlText } : {}),
1670
1674
  executed: result.executed ?? false,
1671
1675
  cached: result.cached ?? false,
1672
1676
  success: true,
@@ -1690,11 +1694,13 @@ export class StateQL {
1690
1694
  }
1691
1695
  catch (error) {
1692
1696
  const stateqlError = asStateQLError(error);
1697
+ const sqlText = resolveHistorySql(historySql);
1693
1698
  this.store.addHistory({
1694
1699
  id: commandId,
1695
1700
  sessionId: session.id,
1696
1701
  actorId: this.actorId,
1697
1702
  command,
1703
+ ...(sqlText !== undefined ? { sql: sqlText } : {}),
1698
1704
  executed: stateqlError.details.executed,
1699
1705
  cached: false,
1700
1706
  success: false,
@@ -1712,6 +1718,9 @@ export class StateQL {
1712
1718
  }
1713
1719
  }
1714
1720
  }
1721
+ function resolveHistorySql(sql) {
1722
+ return typeof sql === "function" ? sql() : sql;
1723
+ }
1715
1724
  function historyEntry(item) {
1716
1725
  return {
1717
1726
  command_id: item.id,
@@ -1719,6 +1728,7 @@ function historyEntry(item) {
1719
1728
  session_id: item.session_id,
1720
1729
  actor_id: item.actor_id,
1721
1730
  command: item.command,
1731
+ sql: item.sql,
1722
1732
  handle: item.handle,
1723
1733
  executed: Boolean(item.executed),
1724
1734
  cached: Boolean(item.cached),
@@ -98,6 +98,7 @@ export interface HistoryRecord {
98
98
  session_id: string;
99
99
  actor_id: string;
100
100
  command: string;
101
+ sql: string | null;
101
102
  handle: string | null;
102
103
  executed: number;
103
104
  cached: number;
@@ -267,6 +268,7 @@ export declare class StateStore {
267
268
  sessionId: string;
268
269
  actorId: string;
269
270
  command: string;
271
+ sql?: string;
270
272
  handle?: string;
271
273
  executed: boolean;
272
274
  cached: boolean;
package/dist/src/store.js CHANGED
@@ -6,6 +6,7 @@ import { runMigrations } from "./migrations.js";
6
6
  import { StateQLError } from "./errors.js";
7
7
  import { isColumns, isRows, isSqlParameters, parseJson, toJsonSafe, } from "./util.js";
8
8
  const HISTORY_LIMIT_PER_SESSION = 10_000;
9
+ const MAX_HISTORY_SQL_BYTES = 4_096;
9
10
  const DEFAULT_MAX_STATE_BYTES = 256 * 1024 * 1024;
10
11
  export class StateStore {
11
12
  now;
@@ -791,10 +792,10 @@ export class StateStore {
791
792
  const id = input.id ?? this.nextId("cmd");
792
793
  this.db
793
794
  .prepare(`INSERT INTO history
794
- (id, timestamp, session_id, actor_id, command, handle, executed,
795
+ (id, timestamp, session_id, actor_id, command, sql, handle, executed,
795
796
  cached, success, error_code)
796
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
797
- .run(id, this.now().toISOString(), input.sessionId, input.actorId, input.command, input.handle ?? null, input.executed ? 1 : 0, input.cached ? 1 : 0, input.success ? 1 : 0, input.errorCode ?? null);
797
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
798
+ .run(id, this.now().toISOString(), input.sessionId, input.actorId, input.command, boundedHistorySql(input.sql), input.handle ?? null, input.executed ? 1 : 0, input.cached ? 1 : 0, input.success ? 1 : 0, input.errorCode ?? null);
798
799
  this.db
799
800
  .prepare(`DELETE FROM history
800
801
  WHERE rowid IN (
@@ -1010,6 +1011,22 @@ export class StateStore {
1010
1011
  }
1011
1012
  }
1012
1013
  }
1014
+ function boundedHistorySql(sql) {
1015
+ if (sql === undefined)
1016
+ return null;
1017
+ if (Buffer.byteLength(sql, "utf8") <= MAX_HISTORY_SQL_BYTES)
1018
+ return sql;
1019
+ const suffix = "…";
1020
+ let prefix = "";
1021
+ for (const character of sql) {
1022
+ if (Buffer.byteLength(`${prefix}${character}${suffix}`, "utf8") >
1023
+ MAX_HISTORY_SQL_BYTES) {
1024
+ break;
1025
+ }
1026
+ prefix += character;
1027
+ }
1028
+ return `${prefix}${suffix}`;
1029
+ }
1013
1030
  function restrictMode(path, allowed) {
1014
1031
  chmodSync(path, statSync(path).mode & allowed);
1015
1032
  }
@@ -64,6 +64,7 @@ export interface HistoryEntry {
64
64
  session_id: string;
65
65
  actor_id: string;
66
66
  command: string;
67
+ sql: string | null;
67
68
  handle: string | null;
68
69
  executed: boolean;
69
70
  cached: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fadhilp/stateql",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Stateful, agent-oriented database CLI for safe result reuse",
5
5
  "repository": {
6
6
  "type": "git",