@fadhilp/stateql 0.11.1 → 0.13.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.
@@ -83,6 +83,22 @@ function execute(request) {
83
83
  }
84
84
  }
85
85
  }
86
+ case "writeAutocommit": {
87
+ if (readOnly)
88
+ throw new SQLiteBatchError("Connection is read-only.", false);
89
+ const [sql, params] = request.args;
90
+ if ((Array.isArray(params) && params.length > 0) ||
91
+ (!Array.isArray(params) && Object.keys(params).length > 0)) {
92
+ throw new SQLiteBatchError("SQLite maintenance statements do not accept parameters.", false);
93
+ }
94
+ try {
95
+ database.exec(sql);
96
+ return { affectedRows: 0 };
97
+ }
98
+ catch (error) {
99
+ throw new SQLiteBatchError(errorText(error), true);
100
+ }
101
+ }
86
102
  case "writeBatch": {
87
103
  if (readOnly)
88
104
  throw new Error("Connection is read-only.");
@@ -672,7 +672,17 @@ export class StateQL {
672
672
  if (!analysis.read) {
673
673
  throw new StateQLError("INVALID_SQL", "query accepts read statements only; use exec for writes.");
674
674
  }
675
+ const cacheMode = options.cache ?? "auto";
676
+ if (!analysis.cacheable && cacheMode === "require") {
677
+ throw new StateQLError("CACHE_MISS", "This statement is not cacheable.", {
678
+ retryable: true,
679
+ suggestedAction: "Run with --cache auto or --cache bypass.",
680
+ });
681
+ }
675
682
  const parameters = options.params ?? [];
683
+ if (analysis.requiresAutocommit && sqlParametersLength(parameters) > 0) {
684
+ throw new StateQLError("INVALID_SQL", "Autocommit diagnostic statements do not accept StateQL parameters.");
685
+ }
676
686
  const context = this.executionContext(options);
677
687
  const adapterSource = await this.resolveConnectionSource(connection, session, "query", "read", context);
678
688
  const adapter = await this.openAdapter(connection, context, adapterSource);
@@ -689,8 +699,8 @@ export class StateQL {
689
699
  stateVersion,
690
700
  });
691
701
  const cached = this.store.findResult(fingerprint);
692
- const cacheMode = options.cache ?? "auto";
693
- if (cacheMode !== "bypass" &&
702
+ if (analysis.cacheable &&
703
+ cacheMode !== "bypass" &&
694
704
  cached &&
695
705
  cached.row_count <= this.maxResultRows &&
696
706
  this.cacheValid(cached, stateVersion, stateSignature)) {
@@ -709,7 +719,15 @@ export class StateQL {
709
719
  suggestedAction: "Run with --cache auto or --cache bypass.",
710
720
  });
711
721
  }
712
- const result = await adapter.read(boundedReadSql(sql, this.maxResultRows + 1), parameters);
722
+ const executionSql = analysis.wrapForLimit
723
+ ? boundedReadSql(analysis.limitSql ?? sql, this.maxResultRows + 1)
724
+ : sql;
725
+ if (analysis.requiresAutocommit && !adapter.readAutocommit) {
726
+ throw new StateQLError("UNSUPPORTED_DRIVER", `${analysis.statementType.toUpperCase()} requires adapter autocommit reads.`);
727
+ }
728
+ const result = analysis.requiresAutocommit
729
+ ? await adapter.readAutocommit(executionSql, parameters)
730
+ : await adapter.read(executionSql, parameters);
713
731
  if (result.rows.length > this.maxResultRows) {
714
732
  throw new StateQLError("OUTPUT_LIMIT_EXCEEDED", `Query exceeds the ${this.maxResultRows}-row materialization limit.`, { suggestedAction: "Add a narrower WHERE clause or LIMIT." });
715
733
  }
@@ -2200,12 +2218,19 @@ export class StateQL {
2200
2218
  throw new StateQLError("INVALID_COMMAND", "Idempotency key cannot be empty.");
2201
2219
  }
2202
2220
  const parameters = options.params ?? [];
2221
+ if (analysis.requiresAutocommit &&
2222
+ (options.expectedRows !== undefined || sqlParametersLength(parameters) > 0)) {
2223
+ throw new StateQLError("INVALID_SQL", "Autocommit maintenance statements do not accept StateQL parameters or row-count preconditions.");
2224
+ }
2225
+ const transactionId = session.active_transaction_id ?? undefined;
2226
+ if (analysis.requiresAutocommit && transactionId) {
2227
+ throw new StateQLError("TRANSACTION_FAILED", `${analysis.statementType.toUpperCase()} cannot be staged in a transaction.`, { suggestedAction: "Rollback or commit the staged transaction, then run the maintenance statement separately." });
2228
+ }
2203
2229
  const fingerprint = hash({
2204
2230
  sql: analysis.normalized,
2205
2231
  parameters,
2206
2232
  database: databaseIdentity(connection),
2207
2233
  });
2208
- const transactionId = session.active_transaction_id ?? undefined;
2209
2234
  if (transactionId) {
2210
2235
  const transaction = this.store.getTransaction(transactionId);
2211
2236
  if (!transaction ||
@@ -2305,7 +2330,12 @@ export class StateQL {
2305
2330
  });
2306
2331
  }
2307
2332
  try {
2308
- const write = await adapter.write(sql, parameters, options.expectedRows);
2333
+ if (analysis.requiresAutocommit && !adapter.writeAutocommit) {
2334
+ throw new AdapterWriteError(`${analysis.statementType.toUpperCase()} requires adapter autocommit execution.`, false);
2335
+ }
2336
+ const write = analysis.requiresAutocommit
2337
+ ? await adapter.writeAutocommit(sql, parameters)
2338
+ : await adapter.write(sql, parameters, options.expectedRows);
2309
2339
  try {
2310
2340
  const finalized = planClaim
2311
2341
  ? this.store.finishPlannedOperation({
@@ -3083,6 +3113,11 @@ function markTransactionOutcomeUnknown(store, transactionId, sessionId, actorId)
3083
3113
  // A stale committing transaction is recovered as unknown after five minutes.
3084
3114
  }
3085
3115
  }
3116
+ function sqlParametersLength(parameters) {
3117
+ return Array.isArray(parameters)
3118
+ ? parameters.length
3119
+ : Object.keys(parameters).length;
3120
+ }
3086
3121
  function boundedReadSql(sql, limit) {
3087
3122
  const statement = sql.trim().replace(/;\s*$/, "");
3088
3123
  return `SELECT * FROM (${statement}) AS _stateql_bounded LIMIT ${limit}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fadhilp/stateql",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "Stateful, agent-oriented database CLI for safe result reuse",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,7 @@
21
21
  "files": [
22
22
  "dist/src",
23
23
  "README.md",
24
+ "SQL_COMMAND_ROADMAP.md",
24
25
  "LICENSE"
25
26
  ],
26
27
  "scripts": {