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