@batadata/cli 0.2.5 → 0.2.6

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.
@@ -1,4 +1,4 @@
1
- import { writeFileSync } from "node:fs";
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { api, apiError } from "../api.js";
4
4
  import { requireToken, isJsonMode, loadConfig } from "../config.js";
@@ -683,12 +683,12 @@ async function powdbQuery(args) {
683
683
  const projectId = resolvePowdbProject(args);
684
684
  const res = await api.post(`/v1/powdb/${encodeURIComponent(projectId)}/query`, { query }, token);
685
685
  if (!res.ok) {
686
- const code = res.status === 503 ? "RETRYABLE"
686
+ const code = res.status === 503 ? "COMPUTE_STARTING"
687
687
  : res.status === 401 || res.status === 403 ? "INVALID_KEY"
688
688
  : res.status === 404 ? "NOT_FOUND"
689
689
  : res.status >= 500 || res.status === 0 ? "API_UNAVAILABLE"
690
690
  : "CLI_ERROR";
691
- emitError(code, apiError(res, "PowQL query failed"), code === "RETRYABLE" ? "The database is starting — retry in a few seconds." : "");
691
+ emitError(code, apiError(res, "PowQL query failed"), code === "COMPUTE_STARTING" ? "The database is starting — retry in a few seconds." : "");
692
692
  return;
693
693
  }
694
694
  const data = res.data;
@@ -729,6 +729,91 @@ async function powdbQuery(args) {
729
729
  }
730
730
  log();
731
731
  }
732
+ function truncateStatement(statement) {
733
+ const flat = statement.replace(/\s+/g, " ").trim();
734
+ return flat.length > 60 ? `${flat.slice(0, 57)}...` : flat;
735
+ }
736
+ async function powdbExec(args) {
737
+ const token = requireToken();
738
+ const jsonMode = isJsonMode();
739
+ const transactional = args.includes("--transactional");
740
+ const continueOnError = args.includes("--continue-on-error");
741
+ if (transactional && continueOnError) {
742
+ emitError("INVALID_ARG", "--transactional and --continue-on-error are mutually exclusive.", "Transactional scripts stop (and roll back) on the first failure by definition.");
743
+ return;
744
+ }
745
+ let filePath;
746
+ for (let i = 0; i < args.length; i++) {
747
+ if (args[i] === "--file" && args[i + 1])
748
+ filePath = args[i + 1];
749
+ if (args[i].startsWith("--file="))
750
+ filePath = args[i].slice("--file=".length);
751
+ }
752
+ let script;
753
+ if (filePath) {
754
+ try {
755
+ // `--file -` reads stdin (fd 0) — the pipe-friendly agent path.
756
+ script = readFileSync(filePath === "-" ? 0 : filePath, "utf8");
757
+ }
758
+ catch (err) {
759
+ emitError("FILE_ERROR", `Could not read ${filePath}: ${err instanceof Error ? err.message : String(err)}`, "");
760
+ return;
761
+ }
762
+ }
763
+ else {
764
+ script = args.find((a) => !a.startsWith("-") && a !== powdbProjectFlag(args) && a !== filePath);
765
+ }
766
+ if (!script || script.trim().length === 0) {
767
+ emitError("MISSING_ARG", 'Usage: bata powdb exec "<PowQL script>" | --file <path|-> [--transactional | --continue-on-error] [--project <id>]', "Statements are ;-separated (string/#-comment aware) and run pipelined on one connection.");
768
+ return;
769
+ }
770
+ const projectId = resolvePowdbProject(args);
771
+ const res = await api.post(`/v1/powdb/${encodeURIComponent(projectId)}/exec`, {
772
+ script,
773
+ ...(transactional ? { transactional: true } : {}),
774
+ ...(continueOnError ? { continue_on_error: true } : {}),
775
+ }, token);
776
+ if (!res.ok) {
777
+ const code = res.status === 503 ? "COMPUTE_STARTING"
778
+ : res.status === 401 || res.status === 403 ? "INVALID_KEY"
779
+ : res.status === 404 ? "NOT_FOUND"
780
+ : res.status >= 500 || res.status === 0 ? "API_UNAVAILABLE"
781
+ : "CLI_ERROR";
782
+ emitError(code, apiError(res, "PowQL script failed"), code === "COMPUTE_STARTING" ? "The database is starting — retry in a few seconds." : "");
783
+ return;
784
+ }
785
+ const data = res.data;
786
+ if (jsonMode) {
787
+ json(data);
788
+ if (("error" in data && data.error) || ("failed_count" in data && data.failed_count > 0)) {
789
+ process.exitCode = 1;
790
+ }
791
+ return;
792
+ }
793
+ if ("error" in data && data.error) {
794
+ const where = data.statement_index !== undefined
795
+ ? ` (statement ${data.statement_index + 1}${data.rolled_back ? ", rolled back — nothing persisted" : ""})`
796
+ : "";
797
+ emitError("QUERY_ERROR", `${data.error}${where}`, data.statement ?? "");
798
+ return;
799
+ }
800
+ log();
801
+ if ("outcomes" in data) {
802
+ for (const o of data.outcomes) {
803
+ if (o.ok)
804
+ log(` ${colors.green(">")} ok ${colors.dim(truncateStatement(o.statement))}`);
805
+ else
806
+ log(` ${colors.red("x")} ${o.error} ${colors.dim(truncateStatement(o.statement))}`);
807
+ }
808
+ log(` ${colors.dim(`${data.statement_count} statement${data.statement_count === 1 ? "" : "s"}, ${data.failed_count} failed, ${data.duration_ms}ms`)}`);
809
+ if (data.failed_count > 0)
810
+ process.exitCode = 1;
811
+ }
812
+ else if ("results" in data) {
813
+ log(` ${colors.green(">")} ${data.statement_count} statement${data.statement_count === 1 ? "" : "s"} executed${transactional ? " (transactional)" : ""} ${colors.dim(`${data.duration_ms}ms`)}`);
814
+ }
815
+ log();
816
+ }
732
817
  async function powdbLifecycle(action, args) {
733
818
  const token = requireToken();
734
819
  const jsonMode = isJsonMode();
@@ -766,6 +851,9 @@ function powdbHelp() {
766
851
  log();
767
852
  log(` ${colors.dim("Hosted PowDB projects (create one with: bata projects create --engine powdb):")}`);
768
853
  log(` ${colors.cyan("query")} Run a PowQL statement: bata powdb query "Note" [--project <id>]`);
854
+ log(` ${colors.cyan("exec")} Run a multi-statement script, pipelined (bulk load / seed / migrate):`);
855
+ log(` ${colors.dim(' bata powdb exec --file seed.powql [--transactional | --continue-on-error]')}`);
856
+ log(` ${colors.dim(' bata powdb pull --out - | bata powdb exec --file - (pipe-friendly)')}`);
769
857
  log(` ${colors.cyan("status")} Show the server state (running | parked) [--project <id>]`);
770
858
  log(` ${colors.cyan("park")} Scale the server to zero (WAL is durable) [--project <id>]`);
771
859
  log(` ${colors.cyan("wake")} Start a parked server (~tens of ms) [--project <id>]`);
@@ -796,6 +884,8 @@ export async function handlePowdb(args) {
796
884
  return powdbPull(args.slice(1));
797
885
  case "query":
798
886
  return powdbQuery(args.slice(1));
887
+ case "exec":
888
+ return powdbExec(args.slice(1));
799
889
  case "status":
800
890
  return powdbLifecycle("status", args.slice(1));
801
891
  case "park":
@@ -806,6 +896,6 @@ export async function handlePowdb(args) {
806
896
  powdbHelp();
807
897
  return;
808
898
  default:
809
- emitError("INVALID_FLAG", `Unknown subcommand: powdb ${sub}`, "Available: pull, query, status, park, wake. Run `bata powdb --help`.");
899
+ emitError("INVALID_FLAG", `Unknown subcommand: powdb ${sub}`, "Available: pull, query, exec, status, park, wake. Run `bata powdb --help`.");
810
900
  }
811
901
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@batadata/cli",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "CLI for BataDB — serverless Postgres platform",
5
5
  "bin": {
6
6
  "bata": "./dist/index.js"