@deepagents/text2sql 0.16.0 → 0.17.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/dist/index.js CHANGED
@@ -522,6 +522,7 @@ import {
522
522
  OverlayFs,
523
523
  defineCommand
524
524
  } from "just-bash";
525
+ import { AsyncLocalStorage } from "node:async_hooks";
525
526
  import * as path from "node:path";
526
527
  import { v7 } from "uuid";
527
528
  import z3 from "zod";
@@ -550,7 +551,7 @@ function validateReadOnly(query) {
550
551
  }
551
552
  return { valid: true };
552
553
  }
553
- function createSqlCommand(adapter) {
554
+ function createSqlCommand(adapter, metaStore) {
554
555
  return createCommand("sql", {
555
556
  run: {
556
557
  usage: 'run "SELECT ..."',
@@ -573,6 +574,8 @@ function createSqlCommand(adapter) {
573
574
  };
574
575
  }
575
576
  const query = adapter.format(rawQuery);
577
+ const store = metaStore.getStore();
578
+ if (store) store.value = { formattedSql: query };
576
579
  const syntaxError = await adapter.validate(query);
577
580
  if (syntaxError) {
578
581
  return {
@@ -629,6 +632,8 @@ function createSqlCommand(adapter) {
629
632
  };
630
633
  }
631
634
  const query = adapter.format(rawQuery);
635
+ const store = metaStore.getStore();
636
+ if (store) store.value = { formattedSql: query };
632
637
  const syntaxError = await adapter.validate(query);
633
638
  if (syntaxError) {
634
639
  return {
@@ -648,7 +653,8 @@ function createSqlCommand(adapter) {
648
653
  }
649
654
  async function createResultTools(options) {
650
655
  const { adapter, skillMounts, filesystem: baseFs } = options;
651
- const sqlCommand = createSqlCommand(adapter);
656
+ const metaStore = new AsyncLocalStorage();
657
+ const sqlCommand = createSqlCommand(adapter, metaStore);
652
658
  const fsMounts = skillMounts.map(({ host, sandbox: sandbox2 }) => ({
653
659
  mountPoint: path.dirname(sandbox2),
654
660
  filesystem: new OverlayFs({
@@ -687,10 +693,19 @@ async function createResultTools(options) {
687
693
  reasoning: z3.string().trim().describe("Brief reason for executing this command")
688
694
  }),
689
695
  execute: async ({ command }, execOptions) => {
690
- if (!tools2.bash.execute) {
696
+ const execute = tools2.bash.execute;
697
+ if (!execute) {
691
698
  throw new Error("bash tool execution is not available");
692
699
  }
693
- return tools2.bash.execute({ command }, execOptions);
700
+ return metaStore.run({}, async () => {
701
+ const result = await execute({ command }, execOptions);
702
+ const meta = metaStore.getStore()?.value;
703
+ return meta ? { ...result, meta } : result;
704
+ });
705
+ },
706
+ toModelOutput: ({ output }) => {
707
+ const { meta, ...rest } = output;
708
+ return { type: "json", value: rest };
694
709
  }
695
710
  });
696
711
  return {
@@ -714,8 +729,6 @@ import {
714
729
  defaultSettingsMiddleware,
715
730
  wrapLanguageModel
716
731
  } from "ai";
717
- import { Console } from "node:console";
718
- import { createWriteStream } from "node:fs";
719
732
  import pRetry from "p-retry";
720
733
  import z4 from "zod";
721
734
  import "@deepagents/agent";
@@ -726,11 +739,6 @@ import {
726
739
  structuredOutput as structuredOutput2,
727
740
  user as user2
728
741
  } from "@deepagents/context";
729
- var logger = new Console({
730
- stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
731
- stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
732
- inspectOptions: { depth: null }
733
- });
734
742
  var RETRY_TEMPERATURES = [0, 0.2, 0.3];
735
743
  function extractSql(output) {
736
744
  const match = output.match(/```sql\n?([\s\S]*?)```/);
@@ -869,7 +877,6 @@ async function withRetry(computation, options = { retries: 3 }) {
869
877
  return APICallError.isInstance(context.error) || JSONParseError.isInstance(context.error) || TypeValidationError.isInstance(context.error) || NoObjectGeneratedError.isInstance(context.error) || NoOutputGeneratedError.isInstance(context.error) || NoContentGeneratedError.isInstance(context.error);
870
878
  },
871
879
  onFailedAttempt(context) {
872
- logger.error(`toSQL`, context.error);
873
880
  console.log(
874
881
  `Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
875
882
  );
@@ -939,9 +946,9 @@ var Checkpoint = class _Checkpoint {
939
946
  points;
940
947
  path;
941
948
  configHash;
942
- constructor(path5, configHash, points) {
949
+ constructor(path6, configHash, points) {
943
950
  this.points = points;
944
- this.path = path5;
951
+ this.path = path6;
945
952
  this.configHash = configHash;
946
953
  }
947
954
  /**
@@ -949,14 +956,14 @@ var Checkpoint = class _Checkpoint {
949
956
  * Handles corrupted files and config changes gracefully.
950
957
  */
951
958
  static async load(options) {
952
- const { path: path5, configHash } = options;
953
- if (existsSync(path5)) {
959
+ const { path: path6, configHash } = options;
960
+ if (existsSync(path6)) {
954
961
  try {
955
- const content = readFileSync(path5, "utf-8");
962
+ const content = readFileSync(path6, "utf-8");
956
963
  const file = JSON.parse(content);
957
964
  if (configHash && file.configHash && file.configHash !== configHash) {
958
965
  console.log("\u26A0 Config changed, starting fresh");
959
- return new _Checkpoint(path5, configHash, {});
966
+ return new _Checkpoint(path6, configHash, {});
960
967
  }
961
968
  const points = file.points ?? {};
962
969
  const totalEntries = Object.values(points).reduce(
@@ -964,14 +971,14 @@ var Checkpoint = class _Checkpoint {
964
971
  0
965
972
  );
966
973
  console.log(`\u2713 Resuming from checkpoint (${totalEntries} entries)`);
967
- return new _Checkpoint(path5, configHash, points);
974
+ return new _Checkpoint(path6, configHash, points);
968
975
  } catch {
969
976
  console.log("\u26A0 Checkpoint corrupted, starting fresh");
970
- return new _Checkpoint(path5, configHash, {});
977
+ return new _Checkpoint(path6, configHash, {});
971
978
  }
972
979
  }
973
980
  console.log("Starting new checkpoint");
974
- return new _Checkpoint(path5, configHash, {});
981
+ return new _Checkpoint(path6, configHash, {});
975
982
  }
976
983
  /**
977
984
  * Run a single computation with checkpointing.
@@ -1832,10 +1839,9 @@ import * as path4 from "node:path";
1832
1839
 
1833
1840
  // packages/text2sql/src/lib/fs/mssql/ddl.mssql-fs.ts
1834
1841
  function mssqlFsDDL(schema) {
1835
- const s = schema;
1836
1842
  return `
1837
- IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${s}].[fs_entries]') AND type = 'U')
1838
- CREATE TABLE [${s}].[fs_entries] (
1843
+ IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${schema}].[fs_entries]') AND type = 'U')
1844
+ CREATE TABLE [${schema}].[fs_entries] (
1839
1845
  path NVARCHAR(900) PRIMARY KEY,
1840
1846
  type NVARCHAR(20) NOT NULL,
1841
1847
  mode INT NOT NULL,
@@ -1845,17 +1851,17 @@ CREATE TABLE [${s}].[fs_entries] (
1845
1851
  );
1846
1852
  GO
1847
1853
 
1848
- IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${s}_fs_entries_type')
1849
- CREATE INDEX [idx_${s}_fs_entries_type] ON [${s}].[fs_entries](type);
1854
+ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_fs_entries_type')
1855
+ CREATE INDEX [idx_${schema}_fs_entries_type] ON [${schema}].[fs_entries](type);
1850
1856
  GO
1851
1857
 
1852
- IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${s}].[fs_chunks]') AND type = 'U')
1853
- CREATE TABLE [${s}].[fs_chunks] (
1858
+ IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${schema}].[fs_chunks]') AND type = 'U')
1859
+ CREATE TABLE [${schema}].[fs_chunks] (
1854
1860
  path NVARCHAR(900) NOT NULL,
1855
1861
  chunkIndex INT NOT NULL,
1856
1862
  data VARBINARY(MAX) NOT NULL,
1857
1863
  PRIMARY KEY (path, chunkIndex),
1858
- FOREIGN KEY (path) REFERENCES [${s}].[fs_entries](path) ON DELETE CASCADE ON UPDATE CASCADE
1864
+ FOREIGN KEY (path) REFERENCES [${schema}].[fs_entries](path) ON DELETE CASCADE ON UPDATE CASCADE
1859
1865
  );
1860
1866
  GO
1861
1867
  `;
@@ -2079,11 +2085,21 @@ var MssqlFs = class _MssqlFs {
2079
2085
  );
2080
2086
  }
2081
2087
  }
2082
- async #readChunks(filePath) {
2083
- const rows = await this.#query(
2084
- `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = @p0 ORDER BY chunkIndex`,
2085
- [filePath]
2086
- );
2088
+ async #readChunks(filePath, transaction) {
2089
+ let rows;
2090
+ if (transaction) {
2091
+ const req = transaction.request();
2092
+ req.input("p0", filePath);
2093
+ const result2 = await req.query(
2094
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = @p0 ORDER BY chunkIndex`
2095
+ );
2096
+ rows = result2.recordset;
2097
+ } else {
2098
+ rows = await this.#query(
2099
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = @p0 ORDER BY chunkIndex`,
2100
+ [filePath]
2101
+ );
2102
+ }
2087
2103
  if (rows.length === 0) {
2088
2104
  return new Uint8Array(0);
2089
2105
  }
@@ -2208,7 +2224,7 @@ var MssqlFs = class _MssqlFs {
2208
2224
  if (entry && entry.type !== "file") {
2209
2225
  throw new Error(`appendFile: not a file: ${filePath}`);
2210
2226
  }
2211
- const existing = entry ? await this.#readChunks(prefixed) : new Uint8Array(0);
2227
+ const existing = entry ? await this.#readChunks(prefixed, transaction) : new Uint8Array(0);
2212
2228
  const combined = new Uint8Array(existing.length + newData.length);
2213
2229
  combined.set(existing, 0);
2214
2230
  combined.set(newData, existing.length);
@@ -2563,6 +2579,11 @@ var MssqlFs = class _MssqlFs {
2563
2579
  const allEntriesResult = await allEntriesReq.query(
2564
2580
  `SELECT * FROM ${this.#t("fs_entries")} WHERE path = @p0 OR path LIKE @p0 + '/%' ORDER BY path DESC`
2565
2581
  );
2582
+ const destDeleteReq = transaction.request();
2583
+ destDeleteReq.input("dp0", destPrefixed);
2584
+ await destDeleteReq.query(
2585
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = @dp0 OR path LIKE @dp0 + '/%'`
2586
+ );
2566
2587
  for (const entry of [...allEntriesResult.recordset].reverse()) {
2567
2588
  const relativePath = path4.posix.relative(srcPrefixed, entry.path);
2568
2589
  const newPath = path4.posix.join(destPrefixed, relativePath);
@@ -2573,11 +2594,22 @@ var MssqlFs = class _MssqlFs {
2573
2594
  insertReq.input("p3", entry.size);
2574
2595
  insertReq.input("p4", Date.now());
2575
2596
  insertReq.input("p5", entry.symlinkTarget);
2576
- await insertReq.query(
2577
- `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlinkTarget)
2578
- VALUES (@p0, @p1, @p2, @p3, @p4, @p5)`
2579
- );
2597
+ await insertReq.query(`
2598
+ MERGE ${this.#t("fs_entries")} AS target
2599
+ USING (SELECT @p0 AS path) AS source
2600
+ ON target.path = source.path
2601
+ WHEN MATCHED THEN
2602
+ UPDATE SET type = @p1, mode = @p2, size = @p3, mtime = @p4, symlinkTarget = @p5
2603
+ WHEN NOT MATCHED THEN
2604
+ INSERT (path, type, mode, size, mtime, symlinkTarget)
2605
+ VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
2606
+ `);
2580
2607
  if (entry.type === "file") {
2608
+ const deleteChunksReq = transaction.request();
2609
+ deleteChunksReq.input("p0", newPath);
2610
+ await deleteChunksReq.query(
2611
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = @p0`
2612
+ );
2581
2613
  const chunksReq = transaction.request();
2582
2614
  chunksReq.input("p0", entry.path);
2583
2615
  const chunksResult = await chunksReq.query(
@@ -2607,11 +2639,22 @@ var MssqlFs = class _MssqlFs {
2607
2639
  insertReq.input("p3", srcEntry.size);
2608
2640
  insertReq.input("p4", Date.now());
2609
2641
  insertReq.input("p5", srcEntry.symlinkTarget);
2610
- await insertReq.query(
2611
- `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlinkTarget)
2612
- VALUES (@p0, @p1, @p2, @p3, @p4, @p5)`
2613
- );
2642
+ await insertReq.query(`
2643
+ MERGE ${this.#t("fs_entries")} AS target
2644
+ USING (SELECT @p0 AS path) AS source
2645
+ ON target.path = source.path
2646
+ WHEN MATCHED THEN
2647
+ UPDATE SET type = @p1, mode = @p2, size = @p3, mtime = @p4, symlinkTarget = @p5
2648
+ WHEN NOT MATCHED THEN
2649
+ INSERT (path, type, mode, size, mtime, symlinkTarget)
2650
+ VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
2651
+ `);
2614
2652
  if (srcEntry.type === "file") {
2653
+ const deleteChunksReq = transaction.request();
2654
+ deleteChunksReq.input("p0", destPrefixed);
2655
+ await deleteChunksReq.query(
2656
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = @p0`
2657
+ );
2615
2658
  const chunksReq = transaction.request();
2616
2659
  chunksReq.input("p0", srcPrefixed);
2617
2660
  const chunksResult = await chunksReq.query(
@@ -2775,6 +2818,876 @@ var MssqlFs = class _MssqlFs {
2775
2818
  }
2776
2819
  };
2777
2820
 
2821
+ // packages/text2sql/src/lib/fs/postgres/postgres-fs.ts
2822
+ import { createRequire as createRequire2 } from "node:module";
2823
+ import * as path5 from "node:path";
2824
+
2825
+ // packages/text2sql/src/lib/fs/postgres/ddl.postgres-fs.ts
2826
+ function postgresFsDDL(schema) {
2827
+ return `
2828
+ CREATE SCHEMA IF NOT EXISTS "${schema}";
2829
+
2830
+ CREATE TABLE IF NOT EXISTS "${schema}"."fs_entries" (
2831
+ path TEXT PRIMARY KEY,
2832
+ type TEXT NOT NULL,
2833
+ mode INTEGER NOT NULL,
2834
+ size BIGINT NOT NULL,
2835
+ mtime BIGINT NOT NULL,
2836
+ symlink_target TEXT
2837
+ );
2838
+
2839
+ CREATE INDEX IF NOT EXISTS "idx_${schema}_fs_entries_type" ON "${schema}"."fs_entries"(type);
2840
+
2841
+ CREATE TABLE IF NOT EXISTS "${schema}"."fs_chunks" (
2842
+ path TEXT NOT NULL,
2843
+ chunk_index INTEGER NOT NULL,
2844
+ data BYTEA NOT NULL,
2845
+ PRIMARY KEY (path, chunk_index),
2846
+ FOREIGN KEY (path) REFERENCES "${schema}"."fs_entries"(path) ON DELETE CASCADE ON UPDATE CASCADE
2847
+ );
2848
+ `;
2849
+ }
2850
+
2851
+ // packages/text2sql/src/lib/fs/postgres/postgres-fs.ts
2852
+ var PostgresFs = class _PostgresFs {
2853
+ #pool;
2854
+ #chunkSize;
2855
+ #root;
2856
+ #schema;
2857
+ #ownsPool;
2858
+ #isInitialized = false;
2859
+ constructor(options) {
2860
+ this.#chunkSize = options.chunkSize ?? 1024 * 1024;
2861
+ const schema = options.schema ?? "public";
2862
+ if (!/^[a-zA-Z_]\w*$/.test(schema)) {
2863
+ throw new Error(`Invalid schema name: "${schema}"`);
2864
+ }
2865
+ this.#schema = schema;
2866
+ const normalizedRoot = this.#normalizeRoot(options.root);
2867
+ this.#root = normalizedRoot === "/" ? "" : normalizedRoot;
2868
+ const pg = _PostgresFs.#requirePg();
2869
+ if (options.pool instanceof pg.Pool) {
2870
+ this.#pool = options.pool;
2871
+ this.#ownsPool = false;
2872
+ } else {
2873
+ this.#pool = typeof options.pool === "string" ? new pg.Pool({ connectionString: options.pool }) : new pg.Pool(options.pool);
2874
+ this.#ownsPool = true;
2875
+ }
2876
+ }
2877
+ static #requirePg() {
2878
+ try {
2879
+ const require2 = createRequire2(import.meta.url);
2880
+ return require2("pg");
2881
+ } catch {
2882
+ throw new Error(
2883
+ 'PostgresFs requires the "pg" package. Install it with: npm install pg'
2884
+ );
2885
+ }
2886
+ }
2887
+ #t(name) {
2888
+ return `"${this.#schema}"."${name}"`;
2889
+ }
2890
+ async initialize() {
2891
+ const ddl = postgresFsDDL(this.#schema);
2892
+ await this.#pool.query(ddl);
2893
+ const rootSlashExists = await this.#rawQuery(
2894
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = '/') AS exists`
2895
+ );
2896
+ if (!rootSlashExists[0].exists) {
2897
+ await this.#rawExec(
2898
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ('/', 'directory', 493, 0, $1)`,
2899
+ [Date.now()]
2900
+ );
2901
+ }
2902
+ if (this.#root) {
2903
+ await this.#createParentDirs(this.#root);
2904
+ const rootExists = await this.#rawQuery(
2905
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
2906
+ [this.#root]
2907
+ );
2908
+ if (!rootExists[0].exists) {
2909
+ await this.#rawExec(
2910
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
2911
+ [this.#root, Date.now()]
2912
+ );
2913
+ }
2914
+ }
2915
+ this.#isInitialized = true;
2916
+ }
2917
+ #ensureInitialized() {
2918
+ if (!this.#isInitialized) {
2919
+ throw new Error(
2920
+ "PostgresFs not initialized. Call await fs.initialize() after construction."
2921
+ );
2922
+ }
2923
+ }
2924
+ async #createParentDirs(p) {
2925
+ const segments = p.split("/").filter(Boolean);
2926
+ let currentPath = "/";
2927
+ for (let i = 0; i < segments.length - 1; i++) {
2928
+ currentPath = path5.posix.join(currentPath, segments[i]);
2929
+ const exists = await this.#rawQuery(
2930
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
2931
+ [currentPath]
2932
+ );
2933
+ if (!exists[0].exists) {
2934
+ await this.#rawExec(
2935
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
2936
+ [currentPath, Date.now()]
2937
+ );
2938
+ }
2939
+ }
2940
+ }
2941
+ async #rawQuery(sql, params) {
2942
+ const result = await this.#pool.query(sql, params);
2943
+ return result.rows;
2944
+ }
2945
+ async #rawExec(sql, params) {
2946
+ const result = await this.#pool.query(sql, params);
2947
+ return result.rowCount ?? 0;
2948
+ }
2949
+ async #query(sql, params) {
2950
+ this.#ensureInitialized();
2951
+ return this.#rawQuery(sql, params);
2952
+ }
2953
+ async #exec(sql, params) {
2954
+ this.#ensureInitialized();
2955
+ return this.#rawExec(sql, params);
2956
+ }
2957
+ async #useTransaction(fn) {
2958
+ this.#ensureInitialized();
2959
+ const client = await this.#pool.connect();
2960
+ try {
2961
+ await client.query("BEGIN");
2962
+ const result = await fn(client);
2963
+ await client.query("COMMIT");
2964
+ return result;
2965
+ } catch (error) {
2966
+ await client.query("ROLLBACK");
2967
+ throw error;
2968
+ } finally {
2969
+ client.release();
2970
+ }
2971
+ }
2972
+ #normalizeRoot(root) {
2973
+ return path5.posix.resolve("/", root.trim());
2974
+ }
2975
+ #prefixPath(p) {
2976
+ if (!this.#root) {
2977
+ return p;
2978
+ }
2979
+ if (p === "/") {
2980
+ return this.#root;
2981
+ }
2982
+ return path5.posix.join(this.#root, p);
2983
+ }
2984
+ #unprefixPath(p) {
2985
+ if (!this.#root) {
2986
+ return p;
2987
+ }
2988
+ if (p === this.#root) {
2989
+ return "/";
2990
+ }
2991
+ if (p.startsWith(this.#root + "/")) {
2992
+ return p.slice(this.#root.length) || "/";
2993
+ }
2994
+ return p;
2995
+ }
2996
+ #normalizePath(p) {
2997
+ return path5.posix.resolve("/", p);
2998
+ }
2999
+ #dirname(p) {
3000
+ const dir = path5.posix.dirname(p);
3001
+ return dir === "" ? "/" : dir;
3002
+ }
3003
+ async #ensureParentExists(filePath, client) {
3004
+ const parent = this.#dirname(filePath);
3005
+ const rootPath = this.#root || "/";
3006
+ if (parent === rootPath || parent === "/") return;
3007
+ const result = await client.query(
3008
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3009
+ [parent]
3010
+ );
3011
+ const entry = result.rows[0];
3012
+ if (!entry) {
3013
+ await this.#ensureParentExists(parent, client);
3014
+ await client.query(
3015
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
3016
+ [parent, Date.now()]
3017
+ );
3018
+ } else if (entry.type !== "directory") {
3019
+ throw new Error(`mkdir: parent is not a directory: ${parent}`);
3020
+ }
3021
+ }
3022
+ async #writeChunks(filePath, content, client) {
3023
+ await client.query(`DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`, [
3024
+ filePath
3025
+ ]);
3026
+ for (let i = 0; i < content.length; i += this.#chunkSize) {
3027
+ const chunk = content.slice(
3028
+ i,
3029
+ Math.min(i + this.#chunkSize, content.length)
3030
+ );
3031
+ await client.query(
3032
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3033
+ [filePath, Math.floor(i / this.#chunkSize), Buffer.from(chunk)]
3034
+ );
3035
+ }
3036
+ }
3037
+ async #readChunks(filePath, client) {
3038
+ let rows;
3039
+ if (client) {
3040
+ const result2 = await client.query(
3041
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = $1 ORDER BY chunk_index`,
3042
+ [filePath]
3043
+ );
3044
+ rows = result2.rows;
3045
+ } else {
3046
+ rows = await this.#query(
3047
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = $1 ORDER BY chunk_index`,
3048
+ [filePath]
3049
+ );
3050
+ }
3051
+ if (rows.length === 0) {
3052
+ return new Uint8Array(0);
3053
+ }
3054
+ const totalSize = rows.reduce((sum, row) => sum + row.data.length, 0);
3055
+ const result = new Uint8Array(totalSize);
3056
+ let offset = 0;
3057
+ for (const row of rows) {
3058
+ result.set(new Uint8Array(row.data), offset);
3059
+ offset += row.data.length;
3060
+ }
3061
+ return result;
3062
+ }
3063
+ async #resolveSymlink(p, seen = /* @__PURE__ */ new Set()) {
3064
+ if (seen.has(p)) {
3065
+ throw new Error(`readFile: circular symlink: ${p}`);
3066
+ }
3067
+ const rows = await this.#query(
3068
+ `SELECT type, symlink_target FROM ${this.#t("fs_entries")} WHERE path = $1`,
3069
+ [p]
3070
+ );
3071
+ const entry = rows[0];
3072
+ if (!entry) {
3073
+ throw new Error(`ENOENT: no such file or directory: ${p}`);
3074
+ }
3075
+ if (entry.type !== "symlink") {
3076
+ return p;
3077
+ }
3078
+ seen.add(p);
3079
+ const target = this.#normalizePath(
3080
+ path5.posix.resolve(this.#dirname(p), entry.symlink_target)
3081
+ );
3082
+ return this.#resolveSymlink(target, seen);
3083
+ }
3084
+ #toUint8Array(content, encoding) {
3085
+ if (content instanceof Uint8Array) {
3086
+ return content;
3087
+ }
3088
+ const enc = encoding ?? "utf8";
3089
+ return new Uint8Array(Buffer.from(content, enc));
3090
+ }
3091
+ async close() {
3092
+ if (this.#ownsPool) {
3093
+ await this.#pool.end();
3094
+ }
3095
+ }
3096
+ // ============================================================================
3097
+ // IFileSystem Implementation
3098
+ // ============================================================================
3099
+ async readFile(filePath, options) {
3100
+ const normalized = this.#normalizePath(filePath);
3101
+ const prefixed = this.#prefixPath(normalized);
3102
+ const resolved = await this.#resolveSymlink(prefixed);
3103
+ const rows = await this.#query(
3104
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3105
+ [resolved]
3106
+ );
3107
+ const entry = rows[0];
3108
+ if (!entry) {
3109
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3110
+ }
3111
+ if (entry.type === "directory") {
3112
+ throw new Error(`EISDIR: illegal operation on a directory: ${filePath}`);
3113
+ }
3114
+ const content = await this.#readChunks(resolved);
3115
+ const encoding = typeof options === "string" ? options : options?.encoding ?? "utf8";
3116
+ return Buffer.from(content).toString(encoding);
3117
+ }
3118
+ async readFileBuffer(filePath) {
3119
+ const normalized = this.#normalizePath(filePath);
3120
+ const prefixed = this.#prefixPath(normalized);
3121
+ const resolved = await this.#resolveSymlink(prefixed);
3122
+ const rows = await this.#query(
3123
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3124
+ [resolved]
3125
+ );
3126
+ const entry = rows[0];
3127
+ if (!entry) {
3128
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3129
+ }
3130
+ if (entry.type === "directory") {
3131
+ throw new Error(`EISDIR: illegal operation on a directory: ${filePath}`);
3132
+ }
3133
+ return this.#readChunks(resolved);
3134
+ }
3135
+ async writeFile(filePath, content, options) {
3136
+ const normalized = this.#normalizePath(filePath);
3137
+ const prefixed = this.#prefixPath(normalized);
3138
+ const encoding = typeof options === "string" ? options : options?.encoding;
3139
+ const data = this.#toUint8Array(content, encoding);
3140
+ await this.#useTransaction(async (client) => {
3141
+ await this.#ensureParentExists(prefixed, client);
3142
+ await client.query(
3143
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime)
3144
+ VALUES ($1, 'file', 420, $2, $3)
3145
+ ON CONFLICT (path) DO UPDATE SET type = 'file', size = EXCLUDED.size, mtime = EXCLUDED.mtime`,
3146
+ [prefixed, data.length, Date.now()]
3147
+ );
3148
+ await this.#writeChunks(prefixed, data, client);
3149
+ });
3150
+ }
3151
+ async appendFile(filePath, content, options) {
3152
+ const normalized = this.#normalizePath(filePath);
3153
+ const prefixed = this.#prefixPath(normalized);
3154
+ const encoding = typeof options === "string" ? options : options?.encoding;
3155
+ const newData = this.#toUint8Array(content, encoding);
3156
+ await this.#useTransaction(async (client) => {
3157
+ await this.#ensureParentExists(prefixed, client);
3158
+ const result = await client.query(
3159
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3160
+ [prefixed]
3161
+ );
3162
+ const entry = result.rows[0];
3163
+ if (entry && entry.type !== "file") {
3164
+ throw new Error(`appendFile: not a file: ${filePath}`);
3165
+ }
3166
+ const existing = entry ? await this.#readChunks(prefixed, client) : new Uint8Array(0);
3167
+ const combined = new Uint8Array(existing.length + newData.length);
3168
+ combined.set(existing, 0);
3169
+ combined.set(newData, existing.length);
3170
+ await client.query(
3171
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime)
3172
+ VALUES ($1, 'file', 420, $2, $3)
3173
+ ON CONFLICT (path) DO UPDATE SET size = EXCLUDED.size, mtime = EXCLUDED.mtime`,
3174
+ [prefixed, combined.length, Date.now()]
3175
+ );
3176
+ await this.#writeChunks(prefixed, combined, client);
3177
+ });
3178
+ }
3179
+ async exists(filePath) {
3180
+ const normalized = this.#normalizePath(filePath);
3181
+ const prefixed = this.#prefixPath(normalized);
3182
+ const rows = await this.#query(
3183
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3184
+ [prefixed]
3185
+ );
3186
+ return rows[0].exists;
3187
+ }
3188
+ async stat(filePath) {
3189
+ const normalized = this.#normalizePath(filePath);
3190
+ const prefixed = this.#prefixPath(normalized);
3191
+ const resolved = await this.#resolveSymlink(prefixed);
3192
+ const rows = await this.#query(
3193
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3194
+ [resolved]
3195
+ );
3196
+ const entry = rows[0];
3197
+ if (!entry) {
3198
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3199
+ }
3200
+ return {
3201
+ isFile: entry.type === "file",
3202
+ isDirectory: entry.type === "directory",
3203
+ isSymbolicLink: false,
3204
+ mode: Number(entry.mode),
3205
+ size: Number(entry.size),
3206
+ mtime: new Date(Number(entry.mtime))
3207
+ };
3208
+ }
3209
+ async lstat(filePath) {
3210
+ const normalized = this.#normalizePath(filePath);
3211
+ const prefixed = this.#prefixPath(normalized);
3212
+ const rows = await this.#query(
3213
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3214
+ [prefixed]
3215
+ );
3216
+ const entry = rows[0];
3217
+ if (!entry) {
3218
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3219
+ }
3220
+ return {
3221
+ isFile: entry.type === "file",
3222
+ isDirectory: entry.type === "directory",
3223
+ isSymbolicLink: entry.type === "symlink",
3224
+ mode: Number(entry.mode),
3225
+ size: Number(entry.size),
3226
+ mtime: new Date(Number(entry.mtime))
3227
+ };
3228
+ }
3229
+ async mkdir(dirPath, options) {
3230
+ const normalized = this.#normalizePath(dirPath);
3231
+ const prefixed = this.#prefixPath(normalized);
3232
+ const existingRows = await this.#query(
3233
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3234
+ [prefixed]
3235
+ );
3236
+ const existing = existingRows[0];
3237
+ if (existing) {
3238
+ if (options?.recursive) {
3239
+ return;
3240
+ }
3241
+ throw new Error(`EEXIST: file already exists: ${dirPath}`);
3242
+ }
3243
+ await this.#useTransaction(async (client) => {
3244
+ if (options?.recursive) {
3245
+ const rootPath = this.#root || "/";
3246
+ const relativePath = path5.posix.relative(rootPath, prefixed);
3247
+ const segments = relativePath.split("/").filter(Boolean);
3248
+ let currentPath = rootPath;
3249
+ for (const segment of segments) {
3250
+ currentPath = path5.posix.join(currentPath, segment);
3251
+ const result = await client.query(
3252
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3253
+ [currentPath]
3254
+ );
3255
+ const exists = result.rows[0];
3256
+ if (!exists) {
3257
+ await client.query(
3258
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
3259
+ [currentPath, Date.now()]
3260
+ );
3261
+ } else if (exists.type !== "directory") {
3262
+ throw new Error(`mkdir: not a directory: ${currentPath}`);
3263
+ }
3264
+ }
3265
+ } else {
3266
+ const parent = this.#dirname(prefixed);
3267
+ const parentResult = await client.query(
3268
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3269
+ [parent]
3270
+ );
3271
+ const parentEntry = parentResult.rows[0];
3272
+ if (!parentEntry) {
3273
+ throw new Error(`mkdir: parent does not exist: ${parent}`);
3274
+ }
3275
+ if (parentEntry.type !== "directory") {
3276
+ throw new Error(`mkdir: parent is not a directory: ${parent}`);
3277
+ }
3278
+ await client.query(
3279
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
3280
+ [prefixed, Date.now()]
3281
+ );
3282
+ }
3283
+ });
3284
+ }
3285
+ async readdir(dirPath) {
3286
+ const normalized = this.#normalizePath(dirPath);
3287
+ const prefixed = this.#prefixPath(normalized);
3288
+ const resolved = await this.#resolveSymlink(prefixed);
3289
+ const entryRows = await this.#query(
3290
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3291
+ [resolved]
3292
+ );
3293
+ const entry = entryRows[0];
3294
+ if (!entry) {
3295
+ throw new Error(`ENOENT: no such file or directory: ${dirPath}`);
3296
+ }
3297
+ if (entry.type !== "directory") {
3298
+ throw new Error(`ENOTDIR: not a directory: ${dirPath}`);
3299
+ }
3300
+ const prefix = resolved === "/" ? "/" : resolved + "/";
3301
+ const rows = await this.#query(
3302
+ `SELECT path FROM ${this.#t("fs_entries")}
3303
+ WHERE path LIKE $1 || '%'
3304
+ AND path != $2
3305
+ AND path NOT LIKE $1 || '%/%'`,
3306
+ [prefix, resolved]
3307
+ );
3308
+ return rows.map((row) => path5.posix.basename(row.path));
3309
+ }
3310
+ async readdirWithFileTypes(dirPath) {
3311
+ const normalized = this.#normalizePath(dirPath);
3312
+ const prefixed = this.#prefixPath(normalized);
3313
+ const resolved = await this.#resolveSymlink(prefixed);
3314
+ const entryRows = await this.#query(
3315
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3316
+ [resolved]
3317
+ );
3318
+ const entry = entryRows[0];
3319
+ if (!entry) {
3320
+ throw new Error(`ENOENT: no such file or directory: ${dirPath}`);
3321
+ }
3322
+ if (entry.type !== "directory") {
3323
+ throw new Error(`ENOTDIR: not a directory: ${dirPath}`);
3324
+ }
3325
+ const prefix = resolved === "/" ? "/" : resolved + "/";
3326
+ const rows = await this.#query(
3327
+ `SELECT path, type FROM ${this.#t("fs_entries")}
3328
+ WHERE path LIKE $1 || '%'
3329
+ AND path != $2
3330
+ AND path NOT LIKE $1 || '%/%'`,
3331
+ [prefix, resolved]
3332
+ );
3333
+ return rows.map((row) => ({
3334
+ name: path5.posix.basename(row.path),
3335
+ isFile: row.type === "file",
3336
+ isDirectory: row.type === "directory",
3337
+ isSymbolicLink: row.type === "symlink"
3338
+ }));
3339
+ }
3340
+ async rm(filePath, options) {
3341
+ const normalized = this.#normalizePath(filePath);
3342
+ const prefixed = this.#prefixPath(normalized);
3343
+ const rows = await this.#query(
3344
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3345
+ [prefixed]
3346
+ );
3347
+ const entry = rows[0];
3348
+ if (!entry) {
3349
+ if (options?.force) {
3350
+ return;
3351
+ }
3352
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3353
+ }
3354
+ await this.#useTransaction(async (client) => {
3355
+ if (entry.type === "directory") {
3356
+ const childrenResult = await client.query(
3357
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path LIKE $1 || '/%') AS exists`,
3358
+ [prefixed]
3359
+ );
3360
+ if (childrenResult.rows[0].exists && !options?.recursive) {
3361
+ throw new Error(`ENOTEMPTY: directory not empty: ${filePath}`);
3362
+ }
3363
+ await client.query(
3364
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3365
+ [prefixed]
3366
+ );
3367
+ } else {
3368
+ await client.query(
3369
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1`,
3370
+ [prefixed]
3371
+ );
3372
+ }
3373
+ });
3374
+ }
3375
+ async cp(src, dest, options) {
3376
+ const srcNormalized = this.#normalizePath(src);
3377
+ const destNormalized = this.#normalizePath(dest);
3378
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3379
+ const destPrefixed = this.#prefixPath(destNormalized);
3380
+ const srcRows = await this.#query(
3381
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3382
+ [srcPrefixed]
3383
+ );
3384
+ const srcEntry = srcRows[0];
3385
+ if (!srcEntry) {
3386
+ throw new Error(`ENOENT: no such file or directory: ${src}`);
3387
+ }
3388
+ if (srcEntry.type === "directory" && !options?.recursive) {
3389
+ throw new Error(`cp: -r not specified; omitting directory: ${src}`);
3390
+ }
3391
+ await this.#useTransaction(async (client) => {
3392
+ await this.#ensureParentExists(destPrefixed, client);
3393
+ if (srcEntry.type === "directory") {
3394
+ const allEntriesResult = await client.query(
3395
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3396
+ [srcPrefixed]
3397
+ );
3398
+ for (const entry of allEntriesResult.rows) {
3399
+ const relativePath = path5.posix.relative(srcPrefixed, entry.path);
3400
+ const newPath = path5.posix.join(destPrefixed, relativePath);
3401
+ await client.query(
3402
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3403
+ VALUES ($1, $2, $3, $4, $5, $6)
3404
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3405
+ [
3406
+ newPath,
3407
+ entry.type,
3408
+ entry.mode,
3409
+ entry.size,
3410
+ Date.now(),
3411
+ entry.symlink_target
3412
+ ]
3413
+ );
3414
+ if (entry.type === "file") {
3415
+ await client.query(
3416
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3417
+ [newPath]
3418
+ );
3419
+ const chunksResult = await client.query(
3420
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3421
+ [entry.path]
3422
+ );
3423
+ for (const chunk of chunksResult.rows) {
3424
+ await client.query(
3425
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3426
+ [newPath, chunk.chunk_index, chunk.data]
3427
+ );
3428
+ }
3429
+ }
3430
+ }
3431
+ } else {
3432
+ await client.query(
3433
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3434
+ VALUES ($1, $2, $3, $4, $5, $6)
3435
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3436
+ [
3437
+ destPrefixed,
3438
+ srcEntry.type,
3439
+ srcEntry.mode,
3440
+ srcEntry.size,
3441
+ Date.now(),
3442
+ srcEntry.symlink_target
3443
+ ]
3444
+ );
3445
+ if (srcEntry.type === "file") {
3446
+ const chunksResult = await client.query(
3447
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3448
+ [srcPrefixed]
3449
+ );
3450
+ await client.query(
3451
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3452
+ [destPrefixed]
3453
+ );
3454
+ for (const chunk of chunksResult.rows) {
3455
+ await client.query(
3456
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3457
+ [destPrefixed, chunk.chunk_index, chunk.data]
3458
+ );
3459
+ }
3460
+ }
3461
+ }
3462
+ });
3463
+ }
3464
+ async mv(src, dest) {
3465
+ const srcNormalized = this.#normalizePath(src);
3466
+ const destNormalized = this.#normalizePath(dest);
3467
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3468
+ const destPrefixed = this.#prefixPath(destNormalized);
3469
+ const srcRows = await this.#query(
3470
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3471
+ [srcPrefixed]
3472
+ );
3473
+ const srcEntry = srcRows[0];
3474
+ if (!srcEntry) {
3475
+ throw new Error(`ENOENT: no such file or directory: ${src}`);
3476
+ }
3477
+ await this.#useTransaction(async (client) => {
3478
+ await this.#ensureParentExists(destPrefixed, client);
3479
+ if (srcEntry.type === "directory") {
3480
+ const allEntriesResult = await client.query(
3481
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%' ORDER BY path DESC`,
3482
+ [srcPrefixed]
3483
+ );
3484
+ await client.query(
3485
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3486
+ [destPrefixed]
3487
+ );
3488
+ for (const entry of [...allEntriesResult.rows].reverse()) {
3489
+ const relativePath = path5.posix.relative(srcPrefixed, entry.path);
3490
+ const newPath = path5.posix.join(destPrefixed, relativePath);
3491
+ await client.query(
3492
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3493
+ VALUES ($1, $2, $3, $4, $5, $6)
3494
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3495
+ [
3496
+ newPath,
3497
+ entry.type,
3498
+ entry.mode,
3499
+ entry.size,
3500
+ Date.now(),
3501
+ entry.symlink_target
3502
+ ]
3503
+ );
3504
+ if (entry.type === "file") {
3505
+ await client.query(
3506
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3507
+ [newPath]
3508
+ );
3509
+ const chunksResult = await client.query(
3510
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3511
+ [entry.path]
3512
+ );
3513
+ for (const chunk of chunksResult.rows) {
3514
+ await client.query(
3515
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3516
+ [newPath, chunk.chunk_index, chunk.data]
3517
+ );
3518
+ }
3519
+ }
3520
+ }
3521
+ await client.query(
3522
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3523
+ [srcPrefixed]
3524
+ );
3525
+ } else {
3526
+ await client.query(
3527
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3528
+ VALUES ($1, $2, $3, $4, $5, $6)
3529
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3530
+ [
3531
+ destPrefixed,
3532
+ srcEntry.type,
3533
+ srcEntry.mode,
3534
+ srcEntry.size,
3535
+ Date.now(),
3536
+ srcEntry.symlink_target
3537
+ ]
3538
+ );
3539
+ if (srcEntry.type === "file") {
3540
+ await client.query(
3541
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3542
+ [destPrefixed]
3543
+ );
3544
+ const chunksResult = await client.query(
3545
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3546
+ [srcPrefixed]
3547
+ );
3548
+ for (const chunk of chunksResult.rows) {
3549
+ await client.query(
3550
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3551
+ [destPrefixed, chunk.chunk_index, chunk.data]
3552
+ );
3553
+ }
3554
+ }
3555
+ await client.query(
3556
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1`,
3557
+ [srcPrefixed]
3558
+ );
3559
+ }
3560
+ });
3561
+ }
3562
+ resolvePath(base, relativePath) {
3563
+ return path5.posix.resolve(base, relativePath);
3564
+ }
3565
+ getAllPaths() {
3566
+ throw new Error(
3567
+ "getAllPaths() is not supported in PostgresFs - use getAllPathsAsync() instead"
3568
+ );
3569
+ }
3570
+ async getAllPathsAsync() {
3571
+ const rows = await this.#query(
3572
+ `SELECT path FROM ${this.#t("fs_entries")} ORDER BY path`
3573
+ );
3574
+ return rows.map((row) => row.path);
3575
+ }
3576
+ async realpath(filePath) {
3577
+ const normalized = this.#normalizePath(filePath);
3578
+ const prefixed = this.#prefixPath(normalized);
3579
+ const resolved = await this.#resolveSymlink(prefixed);
3580
+ const rows = await this.#query(
3581
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3582
+ [resolved]
3583
+ );
3584
+ if (!rows[0].exists) {
3585
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3586
+ }
3587
+ return this.#unprefixPath(resolved);
3588
+ }
3589
+ async utimes(filePath, _atime, mtime) {
3590
+ const normalized = this.#normalizePath(filePath);
3591
+ const prefixed = this.#prefixPath(normalized);
3592
+ const resolved = await this.#resolveSymlink(prefixed);
3593
+ const result = await this.#exec(
3594
+ `UPDATE ${this.#t("fs_entries")} SET mtime = $1 WHERE path = $2`,
3595
+ [mtime.getTime(), resolved]
3596
+ );
3597
+ if (result === 0) {
3598
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3599
+ }
3600
+ }
3601
+ async chmod(filePath, mode) {
3602
+ const normalized = this.#normalizePath(filePath);
3603
+ const prefixed = this.#prefixPath(normalized);
3604
+ const result = await this.#exec(
3605
+ `UPDATE ${this.#t("fs_entries")} SET mode = $1 WHERE path = $2`,
3606
+ [mode, prefixed]
3607
+ );
3608
+ if (result === 0) {
3609
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3610
+ }
3611
+ }
3612
+ async symlink(target, linkPath) {
3613
+ const normalized = this.#normalizePath(linkPath);
3614
+ const prefixed = this.#prefixPath(normalized);
3615
+ const existingRows = await this.#query(
3616
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3617
+ [prefixed]
3618
+ );
3619
+ if (existingRows[0].exists) {
3620
+ throw new Error(`EEXIST: file already exists: ${linkPath}`);
3621
+ }
3622
+ await this.#useTransaction(async (client) => {
3623
+ await this.#ensureParentExists(prefixed, client);
3624
+ await client.query(
3625
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3626
+ VALUES ($1, 'symlink', 511, 0, $2, $3)`,
3627
+ [prefixed, Date.now(), target]
3628
+ );
3629
+ });
3630
+ }
3631
+ async link(existingPath, newPath) {
3632
+ const srcNormalized = this.#normalizePath(existingPath);
3633
+ const destNormalized = this.#normalizePath(newPath);
3634
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3635
+ const destPrefixed = this.#prefixPath(destNormalized);
3636
+ const srcRows = await this.#query(
3637
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3638
+ [srcPrefixed]
3639
+ );
3640
+ const srcEntry = srcRows[0];
3641
+ if (!srcEntry) {
3642
+ throw new Error(`ENOENT: no such file or directory: ${existingPath}`);
3643
+ }
3644
+ if (srcEntry.type !== "file") {
3645
+ throw new Error(`link: not supported for directories: ${existingPath}`);
3646
+ }
3647
+ const existingRows = await this.#query(
3648
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3649
+ [destPrefixed]
3650
+ );
3651
+ if (existingRows[0].exists) {
3652
+ throw new Error(`EEXIST: file already exists: ${newPath}`);
3653
+ }
3654
+ await this.#useTransaction(async (client) => {
3655
+ await this.#ensureParentExists(destPrefixed, client);
3656
+ await client.query(
3657
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime)
3658
+ VALUES ($1, 'file', $2, $3, $4)`,
3659
+ [destPrefixed, srcEntry.mode, srcEntry.size, Date.now()]
3660
+ );
3661
+ const chunksResult = await client.query(
3662
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3663
+ [srcPrefixed]
3664
+ );
3665
+ for (const chunk of chunksResult.rows) {
3666
+ await client.query(
3667
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3668
+ [destPrefixed, chunk.chunk_index, chunk.data]
3669
+ );
3670
+ }
3671
+ });
3672
+ }
3673
+ async readlink(linkPath) {
3674
+ const normalized = this.#normalizePath(linkPath);
3675
+ const prefixed = this.#prefixPath(normalized);
3676
+ const rows = await this.#query(
3677
+ `SELECT type, symlink_target FROM ${this.#t("fs_entries")} WHERE path = $1`,
3678
+ [prefixed]
3679
+ );
3680
+ const entry = rows[0];
3681
+ if (!entry) {
3682
+ throw new Error(`ENOENT: no such file or directory: ${linkPath}`);
3683
+ }
3684
+ if (entry.type !== "symlink") {
3685
+ throw new Error(`readlink: not a symbolic link: ${linkPath}`);
3686
+ }
3687
+ return entry.symlink_target;
3688
+ }
3689
+ };
3690
+
2778
3691
  // packages/text2sql/src/lib/fs/scoped-fs.ts
2779
3692
  var ScopedFs = class {
2780
3693
  #base;
@@ -2783,29 +3696,29 @@ var ScopedFs = class {
2783
3696
  this.#base = options.base;
2784
3697
  this.#prefix = options.prefix.replace(/\/$/, "");
2785
3698
  }
2786
- #scope(path5) {
2787
- return `${this.#prefix}${path5}`;
3699
+ #scope(path6) {
3700
+ return `${this.#prefix}${path6}`;
2788
3701
  }
2789
- #unscope(path5) {
2790
- if (path5 === this.#prefix) {
3702
+ #unscope(path6) {
3703
+ if (path6 === this.#prefix) {
2791
3704
  return "/";
2792
3705
  }
2793
- if (path5.startsWith(this.#prefix + "/")) {
2794
- return path5.slice(this.#prefix.length) || "/";
3706
+ if (path6.startsWith(this.#prefix + "/")) {
3707
+ return path6.slice(this.#prefix.length) || "/";
2795
3708
  }
2796
- return path5;
3709
+ return path6;
2797
3710
  }
2798
- async writeFile(path5, content, options) {
2799
- await this.#base.writeFile(this.#scope(path5), content, options);
3711
+ async writeFile(path6, content, options) {
3712
+ await this.#base.writeFile(this.#scope(path6), content, options);
2800
3713
  }
2801
- async appendFile(path5, content, options) {
2802
- await this.#base.appendFile(this.#scope(path5), content, options);
3714
+ async appendFile(path6, content, options) {
3715
+ await this.#base.appendFile(this.#scope(path6), content, options);
2803
3716
  }
2804
- async mkdir(path5, options) {
2805
- return this.#base.mkdir(this.#scope(path5), options);
3717
+ async mkdir(path6, options) {
3718
+ return this.#base.mkdir(this.#scope(path6), options);
2806
3719
  }
2807
- async rm(path5, options) {
2808
- await this.#base.rm(this.#scope(path5), options);
3720
+ async rm(path6, options) {
3721
+ await this.#base.rm(this.#scope(path6), options);
2809
3722
  }
2810
3723
  async cp(src, dest, options) {
2811
3724
  await this.#base.cp(this.#scope(src), this.#scope(dest), options);
@@ -2813,8 +3726,8 @@ var ScopedFs = class {
2813
3726
  async mv(src, dest) {
2814
3727
  await this.#base.mv(this.#scope(src), this.#scope(dest));
2815
3728
  }
2816
- async chmod(path5, mode) {
2817
- return this.#base.chmod(this.#scope(path5), mode);
3729
+ async chmod(path6, mode) {
3730
+ return this.#base.chmod(this.#scope(path6), mode);
2818
3731
  }
2819
3732
  async symlink(target, linkPath) {
2820
3733
  await this.#base.symlink(target, this.#scope(linkPath));
@@ -2822,35 +3735,35 @@ var ScopedFs = class {
2822
3735
  async link(existingPath, newPath) {
2823
3736
  await this.#base.link(this.#scope(existingPath), this.#scope(newPath));
2824
3737
  }
2825
- readFile(path5, options) {
2826
- return this.#base.readFile(this.#scope(path5), options);
3738
+ readFile(path6, options) {
3739
+ return this.#base.readFile(this.#scope(path6), options);
2827
3740
  }
2828
- readFileBuffer(path5) {
2829
- return this.#base.readFileBuffer(this.#scope(path5));
3741
+ readFileBuffer(path6) {
3742
+ return this.#base.readFileBuffer(this.#scope(path6));
2830
3743
  }
2831
- stat(path5) {
2832
- return this.#base.stat(this.#scope(path5));
3744
+ stat(path6) {
3745
+ return this.#base.stat(this.#scope(path6));
2833
3746
  }
2834
- lstat(path5) {
2835
- return this.#base.lstat(this.#scope(path5));
3747
+ lstat(path6) {
3748
+ return this.#base.lstat(this.#scope(path6));
2836
3749
  }
2837
- readdir(path5) {
2838
- return this.#base.readdir(this.#scope(path5));
3750
+ readdir(path6) {
3751
+ return this.#base.readdir(this.#scope(path6));
2839
3752
  }
2840
- readdirWithFileTypes(path5) {
2841
- return this.#base.readdirWithFileTypes(this.#scope(path5));
3753
+ readdirWithFileTypes(path6) {
3754
+ return this.#base.readdirWithFileTypes(this.#scope(path6));
2842
3755
  }
2843
- exists(path5) {
2844
- return this.#base.exists(this.#scope(path5));
3756
+ exists(path6) {
3757
+ return this.#base.exists(this.#scope(path6));
2845
3758
  }
2846
- readlink(path5) {
2847
- return this.#base.readlink(this.#scope(path5));
3759
+ readlink(path6) {
3760
+ return this.#base.readlink(this.#scope(path6));
2848
3761
  }
2849
- realpath(path5) {
2850
- return this.#base.realpath(this.#scope(path5)).then((p) => this.#unscope(p));
3762
+ realpath(path6) {
3763
+ return this.#base.realpath(this.#scope(path6)).then((p) => this.#unscope(p));
2851
3764
  }
2852
- utimes(path5, atime, mtime) {
2853
- return this.#base.utimes(this.#scope(path5), atime, mtime);
3765
+ utimes(path6, atime, mtime) {
3766
+ return this.#base.utimes(this.#scope(path6), atime, mtime);
2854
3767
  }
2855
3768
  resolvePath(base, relativePath) {
2856
3769
  return this.#base.resolvePath(base, relativePath);
@@ -2871,22 +3784,22 @@ var TrackedFs = class {
2871
3784
  getCreatedFiles() {
2872
3785
  return [...this.#createdFiles];
2873
3786
  }
2874
- async writeFile(path5, content, options) {
2875
- await this.#base.writeFile(path5, content, options);
2876
- this.#createdFiles.add(path5);
3787
+ async writeFile(path6, content, options) {
3788
+ await this.#base.writeFile(path6, content, options);
3789
+ this.#createdFiles.add(path6);
2877
3790
  }
2878
- async appendFile(path5, content, options) {
2879
- await this.#base.appendFile(path5, content, options);
2880
- this.#createdFiles.add(path5);
3791
+ async appendFile(path6, content, options) {
3792
+ await this.#base.appendFile(path6, content, options);
3793
+ this.#createdFiles.add(path6);
2881
3794
  }
2882
- async mkdir(path5, options) {
2883
- return this.#base.mkdir(path5, options);
3795
+ async mkdir(path6, options) {
3796
+ return this.#base.mkdir(path6, options);
2884
3797
  }
2885
- async rm(path5, options) {
2886
- await this.#base.rm(path5, options);
2887
- this.#createdFiles.delete(path5);
3798
+ async rm(path6, options) {
3799
+ await this.#base.rm(path6, options);
3800
+ this.#createdFiles.delete(path6);
2888
3801
  if (options?.recursive) {
2889
- const prefix = path5.endsWith("/") ? path5 : path5 + "/";
3802
+ const prefix = path6.endsWith("/") ? path6 : path6 + "/";
2890
3803
  for (const file of this.#createdFiles) {
2891
3804
  if (file.startsWith(prefix)) {
2892
3805
  this.#createdFiles.delete(file);
@@ -2903,8 +3816,8 @@ var TrackedFs = class {
2903
3816
  this.#createdFiles.delete(src);
2904
3817
  this.#createdFiles.add(dest);
2905
3818
  }
2906
- async chmod(path5, mode) {
2907
- return this.#base.chmod(path5, mode);
3819
+ async chmod(path6, mode) {
3820
+ return this.#base.chmod(path6, mode);
2908
3821
  }
2909
3822
  async symlink(target, linkPath) {
2910
3823
  await this.#base.symlink(target, linkPath);
@@ -2914,29 +3827,29 @@ var TrackedFs = class {
2914
3827
  await this.#base.link(existingPath, newPath);
2915
3828
  this.#createdFiles.add(newPath);
2916
3829
  }
2917
- readFile(path5, options) {
2918
- return this.#base.readFile(path5, options);
3830
+ readFile(path6, options) {
3831
+ return this.#base.readFile(path6, options);
2919
3832
  }
2920
- readFileBuffer(path5) {
2921
- return this.#base.readFileBuffer(path5);
3833
+ readFileBuffer(path6) {
3834
+ return this.#base.readFileBuffer(path6);
2922
3835
  }
2923
- stat(path5) {
2924
- return this.#base.stat(path5);
3836
+ stat(path6) {
3837
+ return this.#base.stat(path6);
2925
3838
  }
2926
- lstat(path5) {
2927
- return this.#base.lstat(path5);
3839
+ lstat(path6) {
3840
+ return this.#base.lstat(path6);
2928
3841
  }
2929
- readdir(path5) {
2930
- return this.#base.readdir(path5);
3842
+ readdir(path6) {
3843
+ return this.#base.readdir(path6);
2931
3844
  }
2932
- readdirWithFileTypes(path5) {
2933
- return this.#base.readdirWithFileTypes(path5);
3845
+ readdirWithFileTypes(path6) {
3846
+ return this.#base.readdirWithFileTypes(path6);
2934
3847
  }
2935
- exists(path5) {
2936
- return this.#base.exists(path5);
3848
+ exists(path6) {
3849
+ return this.#base.exists(path6);
2937
3850
  }
2938
- readlink(path5) {
2939
- return this.#base.readlink(path5);
3851
+ readlink(path6) {
3852
+ return this.#base.readlink(path6);
2940
3853
  }
2941
3854
  resolvePath(base, relativePath) {
2942
3855
  return this.#base.resolvePath(base, relativePath);
@@ -2944,11 +3857,11 @@ var TrackedFs = class {
2944
3857
  getAllPaths() {
2945
3858
  return this.#base.getAllPaths?.() ?? [];
2946
3859
  }
2947
- realpath(path5) {
2948
- return this.#base.realpath(path5);
3860
+ realpath(path6) {
3861
+ return this.#base.realpath(path6);
2949
3862
  }
2950
- utimes(path5, atime, mtime) {
2951
- return this.#base.utimes(path5, atime, mtime);
3863
+ utimes(path6, atime, mtime) {
3864
+ return this.#base.utimes(path6, atime, mtime);
2952
3865
  }
2953
3866
  };
2954
3867
 
@@ -3571,6 +4484,7 @@ export {
3571
4484
  JsonCache,
3572
4485
  MssqlFs,
3573
4486
  Point,
4487
+ PostgresFs,
3574
4488
  SQLValidationError,
3575
4489
  ScopedFs,
3576
4490
  SqliteFs,