@deepagents/text2sql 3.0.1 → 4.0.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/README.md +46 -21
- package/dist/bin/sql.js +95 -32
- package/dist/index.js +172 -114
- package/dist/index.js.map +4 -4
- package/dist/lib/adapter-index.d.ts +1 -1
- package/dist/lib/adapter-index.d.ts.map +1 -1
- package/dist/lib/adapter-name.d.ts +8 -0
- package/dist/lib/adapter-name.d.ts.map +1 -1
- package/dist/lib/index-lock.d.ts +37 -8
- package/dist/lib/index-lock.d.ts.map +1 -1
- package/dist/lib/resolve-adapter.d.ts +14 -0
- package/dist/lib/resolve-adapter.d.ts.map +1 -0
- package/dist/lib/sql-command.d.ts +4 -1
- package/dist/lib/sql-command.d.ts.map +1 -1
- package/dist/lib/sql.d.ts +1 -1
- package/dist/lib/sql.d.ts.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Requires Node.js LTS
|
|
|
34
34
|
import { groq } from '@ai-sdk/groq';
|
|
35
35
|
import pg from 'pg';
|
|
36
36
|
|
|
37
|
-
import { Text2Sql } from '@deepagents/text2sql';
|
|
37
|
+
import { FileIndexLock, Text2Sql } from '@deepagents/text2sql';
|
|
38
38
|
import {
|
|
39
39
|
Postgres,
|
|
40
40
|
columnValues,
|
|
@@ -69,6 +69,7 @@ const adapter = new Postgres({
|
|
|
69
69
|
const text2sql = new Text2Sql({
|
|
70
70
|
model: groq('openai/gpt-oss-20b'),
|
|
71
71
|
adapters: { main: adapter },
|
|
72
|
+
lock: new FileIndexLock(),
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
// Generate SQL
|
|
@@ -82,6 +83,8 @@ console.log(sql);
|
|
|
82
83
|
The adapter-map key (`main` here) is the adapter name. Reuse that same key in
|
|
83
84
|
`text2sql.toSql(..., 'main')` and in any `sql validate <db> "..."` /
|
|
84
85
|
`sql run <db> "..."` calls from a sandbox where the package CLI is installed.
|
|
86
|
+
This is the configured connection name, not the SQL-level database or schema
|
|
87
|
+
name (for example, SQLite's default `main` schema).
|
|
85
88
|
|
|
86
89
|
Adapter names must match `/^[A-Za-z_][A-Za-z0-9_]*$/`. If you build adapter
|
|
87
90
|
maps dynamically, use `isValidAdapterName(name)` to check one key or
|
|
@@ -170,15 +173,21 @@ or upload/write that module before you call `sql index` or `chat()`.
|
|
|
170
173
|
|
|
171
174
|
`instructions()` returns the SQL-flavored system fragments (policies, workflows,
|
|
172
175
|
clarifications, style guides) — spread them into `context.set()` alongside the
|
|
173
|
-
schema fragments returned by `sql index`.
|
|
174
|
-
|
|
176
|
+
schema fragments returned by `sql index`. The index output starts with an
|
|
177
|
+
`available_databases` fragment that lists the exact configured adapter names the
|
|
178
|
+
model must pass to `sql validate <db>` and `sql run <db>`. Add or replace the
|
|
179
|
+
instructions with your own domain fragments as needed.
|
|
175
180
|
|
|
176
181
|
## Advanced: SQL CLI in Sandboxes
|
|
177
182
|
|
|
178
183
|
`sql validate <db> "..."` and `sql run <db> "..."` are real commands from the
|
|
179
|
-
`@deepagents/text2sql` package.
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
`@deepagents/text2sql` package. `<db>` is the configured adapter name. With a
|
|
185
|
+
single configured adapter, a mistaken database selector is routed to that sole
|
|
186
|
+
adapter and the command prints a note; with multiple adapters, unknown names
|
|
187
|
+
fail and print the available list. `sql index` writes an `available_databases`
|
|
188
|
+
fragment, schema fragments, and progress events for chat setup, indexing all
|
|
189
|
+
configured adapters by default (same as `--all`) unless adapter names are
|
|
190
|
+
provided.
|
|
182
191
|
|
|
183
192
|
Install the package inside the sandbox and set `TEXT2SQL_ADAPTERS` to a module
|
|
184
193
|
whose default export is `Record<string, Adapter>`. Missing `sql` means the
|
|
@@ -208,11 +217,16 @@ import { InMemoryFs, createVirtualSandbox } from '@deepagents/context';
|
|
|
208
217
|
import {
|
|
209
218
|
type CreateSqlCommandOptions,
|
|
210
219
|
type CreateSqlCommandResult,
|
|
220
|
+
FileIndexLock,
|
|
211
221
|
Text2Sql,
|
|
212
222
|
createSqlCommand,
|
|
213
223
|
} from '@deepagents/text2sql';
|
|
214
224
|
|
|
215
|
-
const text2sql = new Text2Sql({
|
|
225
|
+
const text2sql = new Text2Sql({
|
|
226
|
+
model,
|
|
227
|
+
adapters: { main: adapter },
|
|
228
|
+
lock: new FileIndexLock(),
|
|
229
|
+
});
|
|
216
230
|
|
|
217
231
|
const commandOptions: CreateSqlCommandOptions = {
|
|
218
232
|
outputDir: '/sql-artifacts',
|
|
@@ -334,8 +348,8 @@ for await (const chunk of followUp) {
|
|
|
334
348
|
## Schema index caching & coordination
|
|
335
349
|
|
|
336
350
|
Schema introspection is the expensive part of indexing. `Text2Sql` and
|
|
337
|
-
`AdapterIndexer` own no storage and no locking — you inject
|
|
338
|
-
|
|
351
|
+
`AdapterIndexer` own no storage and no locking — you inject them, keyed per
|
|
352
|
+
adapter. A `lock` is **required**; a `cache` is optional:
|
|
339
353
|
|
|
340
354
|
```typescript
|
|
341
355
|
export interface IndexCache {
|
|
@@ -350,27 +364,35 @@ export interface IndexLock {
|
|
|
350
364
|
}
|
|
351
365
|
```
|
|
352
366
|
|
|
353
|
-
- **
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
- **`
|
|
357
|
-
the same adapter wait on the lock, then read the warm cache.
|
|
367
|
+
- **`lock` only** (no `cache`) → introspection is serialized but not
|
|
368
|
+
deduplicated: each waiter re-introspects, because the cache recheck under the
|
|
369
|
+
lock is what turns serialization into single-flight.
|
|
370
|
+
- **`lock` + `cache`** → introspection is single-flight: concurrent callers for
|
|
371
|
+
the same adapter wait on the lock, then read the warm cache the holder wrote.
|
|
358
372
|
|
|
359
|
-
|
|
373
|
+
File-backed `IndexCache` and `IndexLock` implementations ship as composable
|
|
374
|
+
primitives:
|
|
360
375
|
|
|
361
376
|
```typescript
|
|
362
|
-
import { FileIndexCache, Text2Sql } from '@deepagents/text2sql';
|
|
377
|
+
import { FileIndexCache, FileIndexLock, Text2Sql } from '@deepagents/text2sql';
|
|
363
378
|
|
|
364
379
|
const text2sql = new Text2Sql({
|
|
365
380
|
model,
|
|
366
381
|
adapters: { main: adapter },
|
|
367
|
-
// Point `dir` at a shared volume to share one cache across
|
|
382
|
+
// Point both `dir`s at a shared volume to share one cache + lock across
|
|
383
|
+
// processes on the same filesystem.
|
|
368
384
|
cache: new FileIndexCache({ dir: '/var/cache/text2sql', namespace: 'v1' }),
|
|
385
|
+
lock: new FileIndexLock({ dir: '/var/cache/text2sql', namespace: 'v1' }),
|
|
369
386
|
});
|
|
370
387
|
```
|
|
371
388
|
|
|
372
389
|
`FileIndexCache` writes atomically (temp + rename) and treats an unparseable
|
|
373
|
-
file as a miss, so a torn read self-heals into a re-introspect.
|
|
390
|
+
file as a miss, so a torn read self-heals into a re-introspect. `FileIndexLock`
|
|
391
|
+
(built on `proper-lockfile`, options `{ dir?, namespace?, stale?, retries? }`)
|
|
392
|
+
serializes processes that share a POSIX filesystem; a held lock auto-refreshes
|
|
393
|
+
its mtime so a slow introspection is not mistaken for a crash, and acquisition
|
|
394
|
+
fails closed once retries are exhausted. Both `dir`s default to the OS temp
|
|
395
|
+
directory.
|
|
374
396
|
|
|
375
397
|
### Horizontally-scaled deployments
|
|
376
398
|
|
|
@@ -378,8 +400,10 @@ Running many processes/containers against one database (e.g. a fleet of
|
|
|
378
400
|
daemons)? Two coordination tiers:
|
|
379
401
|
|
|
380
402
|
- The **lock alone** serializes introspection — never two concurrent
|
|
381
|
-
introspections of the same database (caps peak DB load).
|
|
382
|
-
|
|
403
|
+
introspections of the same database (caps peak DB load). The shipped
|
|
404
|
+
`FileIndexLock` covers processes that share a POSIX filesystem (one host, or a
|
|
405
|
+
shared volume — NFSv4 / EFS); for hosts without a shared filesystem, back it
|
|
406
|
+
with a distributed lock you already operate (a Redis lock, a Postgres
|
|
383
407
|
`pg_advisory_lock`, etc.).
|
|
384
408
|
- The **lock plus a shared cache directory** gives fleet-wide single-flight:
|
|
385
409
|
the lock holder writes the cache on the shared volume, and every waiter reads
|
|
@@ -405,11 +429,12 @@ import { createUIMessageStream } from 'ai';
|
|
|
405
429
|
|
|
406
430
|
import {
|
|
407
431
|
AdapterIndexer,
|
|
432
|
+
FileIndexLock,
|
|
408
433
|
TEXT2SQL_INDEX_PROGRESS_CHUNK,
|
|
409
434
|
type Text2SqlIndexProgressEvent,
|
|
410
435
|
} from '@deepagents/text2sql';
|
|
411
436
|
|
|
412
|
-
const indexer = new AdapterIndexer({ adapters });
|
|
437
|
+
const indexer = new AdapterIndexer({ adapters, lock: new FileIndexLock() });
|
|
413
438
|
|
|
414
439
|
await context.continue(user('Show me top 10 customers'));
|
|
415
440
|
|
package/dist/bin/sql.js
CHANGED
|
@@ -68,6 +68,51 @@ var FileIndexCache = class {
|
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
// packages/text2sql/src/lib/index-lock.ts
|
|
72
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
73
|
+
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
74
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
75
|
+
import * as path2 from "node:path";
|
|
76
|
+
import { lock as acquireLock } from "proper-lockfile";
|
|
77
|
+
var DEFAULT_STALE_MS = 3e4;
|
|
78
|
+
var DEFAULT_RETRIES = {
|
|
79
|
+
retries: 10,
|
|
80
|
+
factor: 2,
|
|
81
|
+
minTimeout: 100,
|
|
82
|
+
maxTimeout: 2e3
|
|
83
|
+
};
|
|
84
|
+
var FileIndexLock = class {
|
|
85
|
+
#dir;
|
|
86
|
+
#namespace;
|
|
87
|
+
#stale;
|
|
88
|
+
#retries;
|
|
89
|
+
constructor(options = {}) {
|
|
90
|
+
this.#dir = options.dir ?? tmpdir2();
|
|
91
|
+
this.#namespace = options.namespace;
|
|
92
|
+
this.#stale = options.stale ?? DEFAULT_STALE_MS;
|
|
93
|
+
this.#retries = options.retries ?? DEFAULT_RETRIES;
|
|
94
|
+
}
|
|
95
|
+
async run(key, fn) {
|
|
96
|
+
await mkdir2(this.#dir, { recursive: true });
|
|
97
|
+
const release = await acquireLock(this.#lockPathFor(key), {
|
|
98
|
+
realpath: false,
|
|
99
|
+
stale: this.#stale,
|
|
100
|
+
retries: this.#retries
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
return await fn();
|
|
104
|
+
} finally {
|
|
105
|
+
await release().catch(() => {
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
#lockPathFor(key) {
|
|
110
|
+
const watermark = this.#namespace ? `${this.#namespace}-${key}` : key;
|
|
111
|
+
const hash = createHash2("md5").update(watermark).digest("hex");
|
|
112
|
+
return path2.join(this.#dir, `text2sql-lock-${hash}`);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
71
116
|
// packages/text2sql/src/lib/sql.ts
|
|
72
117
|
import "@deepagents/agent";
|
|
73
118
|
|
|
@@ -76,6 +121,8 @@ import { fragment } from "@deepagents/context";
|
|
|
76
121
|
|
|
77
122
|
// packages/text2sql/src/lib/adapter-name.ts
|
|
78
123
|
var ADAPTER_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
124
|
+
var DATABASE_NAME_FRAGMENT = "database";
|
|
125
|
+
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set([DATABASE_NAME_FRAGMENT]);
|
|
79
126
|
function isValidAdapterName(name) {
|
|
80
127
|
return name != null && ADAPTER_NAME_PATTERN.test(name);
|
|
81
128
|
}
|
|
@@ -86,6 +133,9 @@ function validateAdapterNames(names) {
|
|
|
86
133
|
`Invalid adapter name "${name}": must match ${ADAPTER_NAME_PATTERN}`
|
|
87
134
|
);
|
|
88
135
|
}
|
|
136
|
+
if (RESERVED_ADAPTER_NAMES.has(name)) {
|
|
137
|
+
throw new Error(`Adapter name "${name}" is reserved`);
|
|
138
|
+
}
|
|
89
139
|
}
|
|
90
140
|
}
|
|
91
141
|
|
|
@@ -197,6 +247,9 @@ function timestampProgressHandler(onProgress) {
|
|
|
197
247
|
});
|
|
198
248
|
};
|
|
199
249
|
}
|
|
250
|
+
function databaseNameFragment(name) {
|
|
251
|
+
return { name: DATABASE_NAME_FRAGMENT, data: name };
|
|
252
|
+
}
|
|
200
253
|
var AdapterIndexer = class {
|
|
201
254
|
#adapters;
|
|
202
255
|
#cache;
|
|
@@ -223,7 +276,7 @@ var AdapterIndexer = class {
|
|
|
223
276
|
const settled = await Promise.allSettled(
|
|
224
277
|
entries.map(async ([name, adapter]) => {
|
|
225
278
|
const schema = await this.#indexAdapter(name, adapter, progress);
|
|
226
|
-
return fragment(name, ...schema);
|
|
279
|
+
return fragment(name, databaseNameFragment(name), ...schema);
|
|
227
280
|
})
|
|
228
281
|
);
|
|
229
282
|
const failed = settled.find((result) => result.status === "rejected");
|
|
@@ -310,7 +363,7 @@ var AdapterIndexer = class {
|
|
|
310
363
|
});
|
|
311
364
|
return cached;
|
|
312
365
|
}
|
|
313
|
-
const fragments =
|
|
366
|
+
const fragments = await this.#lock.run(key, populate);
|
|
314
367
|
if (servedFromCache) emitCacheHit();
|
|
315
368
|
progress({
|
|
316
369
|
type: "adapter:end",
|
|
@@ -926,14 +979,25 @@ var Text2Sql = class {
|
|
|
926
979
|
};
|
|
927
980
|
|
|
928
981
|
// packages/text2sql/src/bin/command.ts
|
|
929
|
-
import * as
|
|
982
|
+
import * as path3 from "node:path";
|
|
983
|
+
|
|
984
|
+
// packages/text2sql/src/lib/resolve-adapter.ts
|
|
985
|
+
function resolveAdapter(text2Sql, db) {
|
|
986
|
+
if (text2Sql.hasAdapter(db)) return db;
|
|
987
|
+
const names = text2Sql.adapterNames();
|
|
988
|
+
if (names.length === 1) return names[0];
|
|
989
|
+
const available = names.join(", ") || "(none configured)";
|
|
990
|
+
throw new Error(`unknown database "${db}". Available: ${available}`);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// packages/text2sql/src/bin/command.ts
|
|
930
994
|
var OUT_DIR_OPTION = {
|
|
931
995
|
flag: "--out-dir <path>",
|
|
932
996
|
description: "Output directory (default: $TEXT2SQL_OUT_DIR or ./sql)"
|
|
933
997
|
};
|
|
934
998
|
function resolveOutputDir(ctx, options) {
|
|
935
999
|
const explicit = typeof options.outDir === "string" ? options.outDir : void 0;
|
|
936
|
-
return
|
|
1000
|
+
return path3.resolve(ctx.cwd, explicit ?? ctx.env.TEXT2SQL_OUT_DIR ?? "./sql");
|
|
937
1001
|
}
|
|
938
1002
|
var CommandError = class extends Error {
|
|
939
1003
|
command;
|
|
@@ -958,24 +1022,25 @@ var SqlCommand = class {
|
|
|
958
1022
|
fail(message, exitCode = 1) {
|
|
959
1023
|
throw new CommandError(this.name, message, exitCode);
|
|
960
1024
|
}
|
|
961
|
-
|
|
962
|
-
const available = text2Sql.adapterNames().join(", ") || "(none configured)";
|
|
1025
|
+
resolveAdapterName(text2Sql, db) {
|
|
963
1026
|
if (!db) {
|
|
1027
|
+
const available = text2Sql.adapterNames().join(", ") || "(none configured)";
|
|
964
1028
|
this.fail(
|
|
965
1029
|
`missing database name. Usage: sql ${this.name} ${this.usage}. Available: ${available}`
|
|
966
1030
|
);
|
|
967
1031
|
}
|
|
968
|
-
|
|
969
|
-
|
|
1032
|
+
try {
|
|
1033
|
+
return resolveAdapter(text2Sql, db);
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
this.fail(errorMessage2(error));
|
|
970
1036
|
}
|
|
971
|
-
return db;
|
|
972
1037
|
}
|
|
973
1038
|
};
|
|
974
1039
|
var SqlQueryCommand = class extends SqlCommand {
|
|
975
1040
|
args = "<db> <...sql>";
|
|
976
1041
|
async execute(ctx, args, options) {
|
|
977
1042
|
const [db, sqlParts] = args;
|
|
978
|
-
const name = this.
|
|
1043
|
+
const name = this.resolveAdapterName(ctx.text2Sql, db);
|
|
979
1044
|
const sql = (sqlParts ?? []).join(" ").trim();
|
|
980
1045
|
if (!sql) this.fail("no query provided");
|
|
981
1046
|
try {
|
|
@@ -991,12 +1056,12 @@ var SqlQueryCommand = class extends SqlCommand {
|
|
|
991
1056
|
// packages/text2sql/src/bin/commands/index.ts
|
|
992
1057
|
import { constants, createWriteStream } from "node:fs";
|
|
993
1058
|
import {
|
|
994
|
-
mkdir as
|
|
1059
|
+
mkdir as mkdir3,
|
|
995
1060
|
open,
|
|
996
1061
|
stat,
|
|
997
1062
|
writeFile as writeFile2
|
|
998
1063
|
} from "node:fs/promises";
|
|
999
|
-
import * as
|
|
1064
|
+
import * as path4 from "node:path";
|
|
1000
1065
|
import { finished } from "node:stream/promises";
|
|
1001
1066
|
import { v7 } from "uuid";
|
|
1002
1067
|
|
|
@@ -1040,13 +1105,13 @@ var IndexCommand = class extends SqlCommand {
|
|
|
1040
1105
|
const verbose = this.resolveVerbose(options.verbose);
|
|
1041
1106
|
const outputDir = resolveOutputDir(ctx, options);
|
|
1042
1107
|
const id = v7();
|
|
1043
|
-
const fragmentsPath =
|
|
1108
|
+
const fragmentsPath = path4.join(outputDir, `index-${id}.json`);
|
|
1044
1109
|
const explicitEventsPath = resolveExplicitEventsPath(ctx);
|
|
1045
|
-
const eventsPath = explicitEventsPath ??
|
|
1110
|
+
const eventsPath = explicitEventsPath ?? path4.join(outputDir, `index-${id}.events.ndjson`);
|
|
1046
1111
|
try {
|
|
1047
|
-
await
|
|
1112
|
+
await mkdir3(outputDir, { recursive: true });
|
|
1048
1113
|
if (explicitEventsPath) {
|
|
1049
|
-
await
|
|
1114
|
+
await mkdir3(path4.dirname(explicitEventsPath), { recursive: true });
|
|
1050
1115
|
}
|
|
1051
1116
|
const eventsStream = await openEventsWriter(
|
|
1052
1117
|
eventsPath,
|
|
@@ -1062,8 +1127,7 @@ var IndexCommand = class extends SqlCommand {
|
|
|
1062
1127
|
const manifest = {
|
|
1063
1128
|
fragmentsPath,
|
|
1064
1129
|
eventsPath,
|
|
1065
|
-
adapters: names
|
|
1066
|
-
fragments: countSchemaFragments(fragments)
|
|
1130
|
+
adapters: names
|
|
1067
1131
|
};
|
|
1068
1132
|
ctx.stdout.write(JSON.stringify(manifest, null, 2) + "\n");
|
|
1069
1133
|
return 0;
|
|
@@ -1107,7 +1171,7 @@ function createProgressHandler(eventsStream, verbose, stderr) {
|
|
|
1107
1171
|
}
|
|
1108
1172
|
function resolveExplicitEventsPath(ctx) {
|
|
1109
1173
|
const explicit = ctx.env.TEXT2SQL_INDEX_EVENTS_PATH?.trim();
|
|
1110
|
-
return explicit ?
|
|
1174
|
+
return explicit ? path4.resolve(ctx.cwd, explicit) : null;
|
|
1111
1175
|
}
|
|
1112
1176
|
async function openEventsWriter(eventsPath, explicitEventsPath, stderr) {
|
|
1113
1177
|
const probe = explicitEventsPath ? await probeFifoWriter(explicitEventsPath, stderr) : null;
|
|
@@ -1164,21 +1228,13 @@ function waitForOpen(stream) {
|
|
|
1164
1228
|
stream.once("error", onError);
|
|
1165
1229
|
});
|
|
1166
1230
|
}
|
|
1167
|
-
function countSchemaFragments(fragments) {
|
|
1168
|
-
return fragments.reduce((count, adapterFragment) => {
|
|
1169
|
-
if (Array.isArray(adapterFragment.data)) {
|
|
1170
|
-
return count + adapterFragment.data.length;
|
|
1171
|
-
}
|
|
1172
|
-
return count + 1;
|
|
1173
|
-
}, 0);
|
|
1174
|
-
}
|
|
1175
1231
|
function dedupe(values) {
|
|
1176
1232
|
return [...new Set(values)];
|
|
1177
1233
|
}
|
|
1178
1234
|
|
|
1179
1235
|
// packages/text2sql/src/bin/commands/run.ts
|
|
1180
|
-
import { mkdir as
|
|
1181
|
-
import * as
|
|
1236
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "node:fs/promises";
|
|
1237
|
+
import * as path5 from "node:path";
|
|
1182
1238
|
import { v7 as v72 } from "uuid";
|
|
1183
1239
|
var RunCommand = class extends SqlQueryCommand {
|
|
1184
1240
|
name = "run";
|
|
@@ -1189,9 +1245,9 @@ var RunCommand = class extends SqlQueryCommand {
|
|
|
1189
1245
|
async runQuery(ctx, name, sql, options) {
|
|
1190
1246
|
const outputDir = resolveOutputDir(ctx, options);
|
|
1191
1247
|
const { rows, columns } = await ctx.text2Sql.run(name, sql);
|
|
1192
|
-
const outPath =
|
|
1248
|
+
const outPath = path5.join(outputDir, `${v72()}.json`);
|
|
1193
1249
|
try {
|
|
1194
|
-
await
|
|
1250
|
+
await mkdir4(outputDir, { recursive: true });
|
|
1195
1251
|
await writeFile3(outPath, JSON.stringify(rows, null, 2));
|
|
1196
1252
|
} catch (error) {
|
|
1197
1253
|
this.fail(errorMessage2(error));
|
|
@@ -1336,12 +1392,19 @@ function resolveIndexCache() {
|
|
|
1336
1392
|
}
|
|
1337
1393
|
return new FileIndexCache({ dir, namespace });
|
|
1338
1394
|
}
|
|
1395
|
+
function resolveIndexLock() {
|
|
1396
|
+
return new FileIndexLock({
|
|
1397
|
+
dir: process.env.TEXT2SQL_INDEX_LOCK_DIR ?? process.env.TEXT2SQL_INDEX_CACHE_DIR,
|
|
1398
|
+
namespace: process.env.TEXT2SQL_INDEX_VERSION
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1339
1401
|
async function runCommand(command, positional, options) {
|
|
1340
1402
|
let ctx;
|
|
1341
1403
|
try {
|
|
1342
1404
|
const text2Sql = new Text2Sql({
|
|
1343
1405
|
adapters: await loadAdapters(),
|
|
1344
|
-
cache: resolveIndexCache()
|
|
1406
|
+
cache: resolveIndexCache(),
|
|
1407
|
+
lock: resolveIndexLock()
|
|
1345
1408
|
});
|
|
1346
1409
|
ctx = {
|
|
1347
1410
|
text2Sql,
|