@deepagents/text2sql 0.39.0 → 2.1.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.
Files changed (39) hide show
  1. package/README.md +157 -74
  2. package/dist/bin/sql.js +795 -0
  3. package/dist/index.d.ts +2 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +3376 -3309
  6. package/dist/index.js.map +4 -4
  7. package/dist/lib/adapter-index.d.ts +35 -0
  8. package/dist/lib/adapter-index.d.ts.map +1 -0
  9. package/dist/lib/agents/question.agent.d.ts.map +1 -1
  10. package/dist/lib/agents/sql-transform-plugins.d.ts +3 -3
  11. package/dist/lib/agents/sql-transform-plugins.d.ts.map +1 -1
  12. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  13. package/dist/lib/instructions.d.ts +1 -1
  14. package/dist/lib/instructions.d.ts.map +1 -1
  15. package/dist/lib/sql-hooks.d.ts +15 -0
  16. package/dist/lib/sql-hooks.d.ts.map +1 -0
  17. package/dist/lib/sql-meta-hook.d.ts +20 -0
  18. package/dist/lib/sql-meta-hook.d.ts.map +1 -0
  19. package/dist/lib/sql-repair.d.ts +2 -0
  20. package/dist/lib/sql-repair.d.ts.map +1 -0
  21. package/dist/lib/sql-transform.d.ts +2 -0
  22. package/dist/lib/sql-transform.d.ts.map +1 -0
  23. package/dist/lib/sql.d.ts +5 -38
  24. package/dist/lib/sql.d.ts.map +1 -1
  25. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
  26. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -1
  27. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
  28. package/dist/lib/synthesis/index.js +59 -2
  29. package/dist/lib/synthesis/index.js.map +4 -4
  30. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
  31. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
  32. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
  33. package/package.json +9 -5
  34. package/dist/lib/agents/sql-command.d.ts +0 -11
  35. package/dist/lib/agents/sql-command.d.ts.map +0 -1
  36. package/dist/lib/demo.d.ts +0 -2
  37. package/dist/lib/demo.d.ts.map +0 -1
  38. package/dist/lib/sandbox.d.ts +0 -21
  39. package/dist/lib/sandbox.d.ts.map +0 -1
@@ -0,0 +1,795 @@
1
+ #!/usr/bin/env node
2
+
3
+ // packages/text2sql/src/bin/sql.ts
4
+ import { cac } from "cac";
5
+
6
+ // packages/text2sql/src/bin/command.ts
7
+ import * as path from "node:path";
8
+ var OUT_DIR_OPTION = {
9
+ flag: "--out-dir <path>",
10
+ description: "Output directory (default: $TEXT2SQL_OUT_DIR or ./sql)"
11
+ };
12
+ function resolveOutputDir(ctx, options) {
13
+ const explicit = typeof options.outDir === "string" ? options.outDir : void 0;
14
+ return path.resolve(ctx.cwd, explicit ?? ctx.env.TEXT2SQL_OUT_DIR ?? "./sql");
15
+ }
16
+ var CommandError = class extends Error {
17
+ command;
18
+ exitCode;
19
+ constructor(command, message, exitCode = 1) {
20
+ super(message);
21
+ this.name = "CommandError";
22
+ this.command = command;
23
+ this.exitCode = exitCode;
24
+ }
25
+ };
26
+ function renderCommandError(error, stderr) {
27
+ stderr.write(`sql ${error.command}: ${error.message}
28
+ `);
29
+ }
30
+ function errorMessage(error) {
31
+ return error instanceof Error ? error.message : String(error);
32
+ }
33
+ var SqlCommand = class {
34
+ options = [];
35
+ helpDisplay;
36
+ fail(message, exitCode = 1) {
37
+ throw new CommandError(this.name, message, exitCode);
38
+ }
39
+ requireAdapter(adapters, db) {
40
+ const available = Object.keys(adapters).join(", ") || "(none configured)";
41
+ if (!db) {
42
+ this.fail(
43
+ `missing database name. Usage: sql ${this.name} ${this.usage}. Available: ${available}`
44
+ );
45
+ }
46
+ const adapter = adapters[db];
47
+ if (!adapter) {
48
+ this.fail(`unknown database "${db}". Available: ${available}`);
49
+ }
50
+ return adapter;
51
+ }
52
+ };
53
+ var SqlQueryCommand = class extends SqlCommand {
54
+ args = "<db> <...sql>";
55
+ async execute(ctx, args, options) {
56
+ const [db, sqlParts] = args;
57
+ const adapter = this.requireAdapter(ctx.adapters, db);
58
+ const sql = (sqlParts ?? []).join(" ").trim();
59
+ if (!sql) this.fail("no query provided");
60
+ const formatted = await this.formatAndValidate(adapter, sql);
61
+ return this.afterValidation(ctx, adapter, formatted, options);
62
+ }
63
+ async formatAndValidate(adapter, sql) {
64
+ let formatted;
65
+ try {
66
+ formatted = adapter.format(sql);
67
+ } catch (error) {
68
+ this.fail(errorMessage(error));
69
+ }
70
+ try {
71
+ const syntaxError = await adapter.validate(formatted);
72
+ if (syntaxError) this.fail(syntaxError);
73
+ } catch (error) {
74
+ this.fail(errorMessage(error));
75
+ }
76
+ return formatted;
77
+ }
78
+ };
79
+
80
+ // packages/text2sql/src/bin/commands/index.ts
81
+ import { constants, createWriteStream } from "node:fs";
82
+ import {
83
+ mkdir,
84
+ open,
85
+ stat,
86
+ writeFile as writeFile2
87
+ } from "node:fs/promises";
88
+ import * as path3 from "node:path";
89
+ import { finished } from "node:stream/promises";
90
+ import { v7 } from "uuid";
91
+
92
+ // packages/text2sql/src/lib/adapter-index.ts
93
+ import { fragment } from "@deepagents/context";
94
+
95
+ // packages/text2sql/src/lib/adapter-name.ts
96
+ var ADAPTER_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
97
+ function isValidAdapterName(name) {
98
+ return name != null && ADAPTER_NAME_PATTERN.test(name);
99
+ }
100
+ function validateAdapterNames(names) {
101
+ for (const name of names) {
102
+ if (!isValidAdapterName(name)) {
103
+ throw new Error(
104
+ `Invalid adapter name "${name}": must match ${ADAPTER_NAME_PATTERN}`
105
+ );
106
+ }
107
+ }
108
+ }
109
+
110
+ // packages/text2sql/src/lib/adapters/adapter.ts
111
+ import { format as formatSql } from "sql-formatter";
112
+
113
+ // packages/text2sql/src/lib/agents/exceptions.ts
114
+ var sqlValidationMarker = Symbol("SQLValidationError");
115
+ var unanswerableSqlMarker = Symbol("UnanswerableSQLError");
116
+ var sqlScopeMarker = Symbol("SQLScopeError");
117
+ var sqlReadOnlyMarker = Symbol("SQLReadOnlyError");
118
+ var SQLValidationError = class _SQLValidationError extends Error {
119
+ [sqlValidationMarker];
120
+ constructor(message) {
121
+ super(message);
122
+ this.name = "SQLValidationError";
123
+ this[sqlValidationMarker] = true;
124
+ }
125
+ static isInstance(error) {
126
+ return error instanceof _SQLValidationError && error[sqlValidationMarker] === true;
127
+ }
128
+ };
129
+ var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
130
+ [unanswerableSqlMarker];
131
+ constructor(message) {
132
+ super(message);
133
+ this.name = "UnanswerableSQLError";
134
+ this[unanswerableSqlMarker] = true;
135
+ }
136
+ static isInstance(error) {
137
+ return error instanceof _UnanswerableSQLError && error[unanswerableSqlMarker] === true;
138
+ }
139
+ };
140
+ var SQLScopeError = class _SQLScopeError extends Error {
141
+ [sqlScopeMarker];
142
+ payload;
143
+ errorType;
144
+ constructor(payload) {
145
+ super(JSON.stringify(payload));
146
+ this.name = "SQLScopeError";
147
+ this.payload = payload;
148
+ this.errorType = payload.error_type;
149
+ this[sqlScopeMarker] = true;
150
+ }
151
+ static isInstance(error) {
152
+ return error instanceof _SQLScopeError && error[sqlScopeMarker] === true;
153
+ }
154
+ };
155
+ var SQLReadOnlyError = class _SQLReadOnlyError extends Error {
156
+ [sqlReadOnlyMarker];
157
+ constructor(message) {
158
+ super(message);
159
+ this.name = "SQLReadOnlyError";
160
+ this[sqlReadOnlyMarker] = true;
161
+ }
162
+ static isInstance(error) {
163
+ return error instanceof _SQLReadOnlyError && error[sqlReadOnlyMarker] === true;
164
+ }
165
+ };
166
+
167
+ // packages/text2sql/src/lib/adapters/groundings/context.ts
168
+ var noopProgress = () => {
169
+ };
170
+ function timestampProgress(onProgress) {
171
+ if (!onProgress) return noopProgress;
172
+ return (progress) => {
173
+ onProgress({
174
+ ...progress,
175
+ timestampMs: progress.timestampMs ?? Date.now()
176
+ });
177
+ };
178
+ }
179
+ function createGroundingContext(options = {}) {
180
+ return {
181
+ tables: [],
182
+ views: [],
183
+ relationships: [],
184
+ info: void 0,
185
+ cache: /* @__PURE__ */ new Map(),
186
+ onProgress: timestampProgress(options.onProgress)
187
+ };
188
+ }
189
+
190
+ // packages/text2sql/src/lib/adapters/runtime-scope.ts
191
+ import nodeSqlParser from "node-sql-parser";
192
+ var { Parser } = nodeSqlParser;
193
+ var parser = new Parser();
194
+
195
+ // packages/text2sql/src/lib/file-cache.ts
196
+ import { createHash } from "node:crypto";
197
+ import { existsSync } from "node:fs";
198
+ import { readFile, writeFile } from "node:fs/promises";
199
+ import { tmpdir } from "node:os";
200
+ import path2 from "node:path";
201
+ var FileCache = class {
202
+ path;
203
+ constructor(watermark, extension = ".txt") {
204
+ const hash = createHash("md5").update(watermark).digest("hex");
205
+ this.path = path2.join(tmpdir(), `text2sql-${hash}${extension}`);
206
+ }
207
+ async get() {
208
+ if (existsSync(this.path)) {
209
+ return readFile(this.path, "utf-8");
210
+ }
211
+ return null;
212
+ }
213
+ set(content) {
214
+ return writeFile(this.path, content, "utf-8");
215
+ }
216
+ };
217
+ var JsonCache = class extends FileCache {
218
+ constructor(watermark) {
219
+ super(watermark, ".json");
220
+ }
221
+ async read() {
222
+ const content = await this.get();
223
+ if (content) {
224
+ return JSON.parse(content);
225
+ }
226
+ return null;
227
+ }
228
+ write(data) {
229
+ return this.set(JSON.stringify(data));
230
+ }
231
+ };
232
+
233
+ // packages/text2sql/src/lib/adapter-index.ts
234
+ var noopProgressHandler = () => {
235
+ };
236
+ function timestampProgressHandler(onProgress) {
237
+ if (!onProgress) return noopProgressHandler;
238
+ return (event) => {
239
+ onProgress({
240
+ ...event,
241
+ timestampMs: event.timestampMs ?? Date.now()
242
+ });
243
+ };
244
+ }
245
+ var AdapterIndexer = class {
246
+ #adapters;
247
+ #version;
248
+ #cacheKey;
249
+ constructor(options) {
250
+ const adapterNames = Object.keys(options.adapters);
251
+ if (adapterNames.length === 0) {
252
+ throw new Error("AdapterIndexer requires at least one adapter");
253
+ }
254
+ validateAdapterNames(adapterNames);
255
+ this.#adapters = options.adapters;
256
+ this.#version = options.version;
257
+ this.#cacheKey = options.cacheKey;
258
+ }
259
+ async index(options = {}) {
260
+ const progress = timestampProgressHandler(options.onProgress);
261
+ const entries = this.#resolveIndexEntries(options.adapterNames);
262
+ progress({
263
+ type: "index:start",
264
+ message: `Indexing ${entries.length} adapter${entries.length === 1 ? "" : "s"}...`,
265
+ current: 0,
266
+ total: entries.length
267
+ });
268
+ const settled = await Promise.allSettled(
269
+ entries.map(async ([name, adapter]) => {
270
+ const schema = await this.#indexAdapter(name, adapter, progress);
271
+ return fragment(name, ...schema);
272
+ })
273
+ );
274
+ const failed = settled.find((result) => result.status === "rejected");
275
+ if (failed) {
276
+ progress({
277
+ type: "index:error",
278
+ message: failed.reason instanceof Error ? failed.reason.message : String(failed.reason)
279
+ });
280
+ throw failed.reason;
281
+ }
282
+ const wrapped = settled.map((result) => {
283
+ if (result.status === "rejected") {
284
+ throw result.reason;
285
+ }
286
+ return result.value;
287
+ });
288
+ progress({
289
+ type: "index:end",
290
+ message: "Finished indexing adapters.",
291
+ current: entries.length,
292
+ total: entries.length
293
+ });
294
+ return wrapped;
295
+ }
296
+ async indexAdapter(name, options = {}) {
297
+ const adapter = this.#adapters[name];
298
+ if (!adapter) {
299
+ const available = Object.keys(this.#adapters).join(", ");
300
+ throw new Error(`unknown adapter "${name}". Available: ${available}`);
301
+ }
302
+ return this.#indexAdapter(
303
+ name,
304
+ adapter,
305
+ timestampProgressHandler(options.onProgress)
306
+ );
307
+ }
308
+ async #indexAdapter(name, adapter, progress) {
309
+ progress({
310
+ type: "adapter:start",
311
+ adapter: name,
312
+ message: `Indexing adapter "${name}"...`
313
+ });
314
+ const cacheKey = this.#adapterCacheKey(name);
315
+ const cache = cacheKey ? new JsonCache(cacheKey) : void 0;
316
+ try {
317
+ const cached = await cache?.read();
318
+ if (cached) {
319
+ progress({
320
+ type: "adapter:cache-hit",
321
+ adapter: name,
322
+ message: `Using cached index for adapter "${name}".`,
323
+ cached: true
324
+ });
325
+ progress({
326
+ type: "adapter:end",
327
+ adapter: name,
328
+ message: `Finished indexing adapter "${name}".`,
329
+ cached: true
330
+ });
331
+ return cached;
332
+ }
333
+ if (cache) {
334
+ progress({
335
+ type: "adapter:cache-miss",
336
+ adapter: name,
337
+ message: `No cached index for adapter "${name}".`,
338
+ cached: false
339
+ });
340
+ }
341
+ const ctx = createGroundingContext({
342
+ onProgress: (event) => progress(adapterProgressEvent(name, event))
343
+ });
344
+ const fragments = await adapter.introspect(ctx);
345
+ await cache?.write(fragments);
346
+ progress({
347
+ type: "adapter:end",
348
+ adapter: name,
349
+ message: `Finished indexing adapter "${name}".`,
350
+ cached: false
351
+ });
352
+ return fragments;
353
+ } catch (error) {
354
+ const reason = errorMessage2(error);
355
+ progress({
356
+ type: "adapter:error",
357
+ adapter: name,
358
+ message: `Failed indexing adapter "${name}": ${reason}`
359
+ });
360
+ throw new Error(`introspecting adapter "${name}": ${reason}`, {
361
+ cause: error
362
+ });
363
+ }
364
+ }
365
+ #resolveIndexEntries(adapterNames) {
366
+ const availableNames = Object.keys(this.#adapters);
367
+ const requestedNames = adapterNames && adapterNames.length > 0 ? [...new Set(adapterNames)] : availableNames;
368
+ const available = availableNames.join(", ");
369
+ return requestedNames.map((name) => {
370
+ const adapter = this.#adapters[name];
371
+ if (!adapter) {
372
+ throw new Error(`unknown adapter "${name}". Available: ${available}`);
373
+ }
374
+ return [name, adapter];
375
+ });
376
+ }
377
+ #adapterCacheKey(name) {
378
+ const configured = this.#cacheKey?.(name);
379
+ if (configured !== void 0) {
380
+ return configured;
381
+ }
382
+ if (this.#version) {
383
+ return `index-${this.#version}-${name}`;
384
+ }
385
+ return void 0;
386
+ }
387
+ };
388
+ function adapterProgressEvent(adapter, progress) {
389
+ return {
390
+ type: progress.type,
391
+ adapter,
392
+ phase: progress.phase,
393
+ table: progress.table,
394
+ message: progress.message,
395
+ current: progress.current,
396
+ total: progress.total,
397
+ cached: false,
398
+ timestampMs: progress.timestampMs
399
+ };
400
+ }
401
+ function errorMessage2(error) {
402
+ return error instanceof Error ? error.message : String(error);
403
+ }
404
+
405
+ // packages/text2sql/src/bin/commands/indexing-formatter.ts
406
+ var REDUNDANT_MESSAGE_TYPES = /* @__PURE__ */ new Set([
407
+ "adapter:start",
408
+ "adapter:end"
409
+ ]);
410
+ function formatPretty(event) {
411
+ const head = event.adapter ? `${event.type} ${event.adapter}` : event.type;
412
+ const parts = [`[${head}]`];
413
+ if (event.phase) parts.push(`phase=${event.phase}`);
414
+ if (event.table) parts.push(`table=${event.table}`);
415
+ if (typeof event.current === "number" && typeof event.total === "number" && !event.type.endsWith(":start")) {
416
+ parts.push(`${event.current}/${event.total}`);
417
+ }
418
+ if (event.cached) parts.push("cached");
419
+ if (!REDUNDANT_MESSAGE_TYPES.has(event.type)) parts.push(event.message);
420
+ return parts.join(" ");
421
+ }
422
+
423
+ // packages/text2sql/src/bin/commands/index.ts
424
+ var IndexCommand = class extends SqlCommand {
425
+ name = "index";
426
+ description = "Index adapter schemas and write context artifacts";
427
+ args = "[...adapters]";
428
+ usage = "[--all] [--verbose [pretty|json]] [adapter ...]";
429
+ helpDisplay = "[--all] [--verbose] [adapter ...]";
430
+ options = [
431
+ { flag: "--all", description: "Index all adapters (default)" },
432
+ {
433
+ flag: "-v, --verbose [format]",
434
+ description: "Mirror progress events to stderr (format: pretty (default) | json)"
435
+ },
436
+ OUT_DIR_OPTION
437
+ ];
438
+ async execute(ctx, args, options) {
439
+ const adapterNames = args[0] ?? [];
440
+ const requested = options.all ? [] : adapterNames;
441
+ const names = this.resolveNames(ctx.adapters, requested);
442
+ const verbose = this.resolveVerbose(options.verbose);
443
+ const outputDir = resolveOutputDir(ctx, options);
444
+ const id = v7();
445
+ const fragmentsPath = path3.join(outputDir, `index-${id}.json`);
446
+ const explicitEventsPath = resolveExplicitEventsPath(ctx);
447
+ const eventsPath = explicitEventsPath ?? path3.join(outputDir, `index-${id}.events.ndjson`);
448
+ try {
449
+ await mkdir(outputDir, { recursive: true });
450
+ if (explicitEventsPath) {
451
+ await mkdir(path3.dirname(explicitEventsPath), { recursive: true });
452
+ }
453
+ const eventsStream = await openEventsWriter(
454
+ eventsPath,
455
+ explicitEventsPath,
456
+ ctx.stderr
457
+ );
458
+ try {
459
+ const indexer = new AdapterIndexer({
460
+ adapters: ctx.adapters,
461
+ version: ctx.env.TEXT2SQL_INDEX_VERSION
462
+ });
463
+ const fragments = await indexer.index({
464
+ adapterNames: names,
465
+ onProgress: createProgressHandler(eventsStream, verbose, ctx.stderr)
466
+ });
467
+ await writeFile2(fragmentsPath, JSON.stringify(fragments, null, 2));
468
+ const manifest = {
469
+ fragmentsPath,
470
+ eventsPath,
471
+ adapters: names,
472
+ fragments: countSchemaFragments(fragments)
473
+ };
474
+ ctx.stdout.write(JSON.stringify(manifest, null, 2) + "\n");
475
+ return 0;
476
+ } finally {
477
+ eventsStream.end();
478
+ await finished(eventsStream).catch(() => {
479
+ });
480
+ }
481
+ } catch (error) {
482
+ this.fail(errorMessage(error));
483
+ }
484
+ }
485
+ resolveNames(adapters, requested) {
486
+ const availableNames = Object.keys(adapters);
487
+ const available = availableNames.join(", ") || "(none configured)";
488
+ const names = requested.length === 0 ? availableNames : dedupe(requested);
489
+ for (const name of names) {
490
+ if (!adapters[name]) {
491
+ this.fail(`unknown adapter "${name}". Available: ${available}`);
492
+ }
493
+ }
494
+ return names;
495
+ }
496
+ resolveVerbose(value) {
497
+ if (value === void 0 || value === false) return null;
498
+ if (value === true || value === "" || value === "pretty") return "pretty";
499
+ if (value === "json") return "json";
500
+ this.fail(
501
+ `invalid --verbose value "${String(value)}". Expected "pretty" or "json".`
502
+ );
503
+ }
504
+ };
505
+ function createProgressHandler(eventsStream, verbose, stderr) {
506
+ return (event) => {
507
+ const stamped = { ...event, timestampMs: event.timestampMs ?? Date.now() };
508
+ const json = JSON.stringify(stamped) + "\n";
509
+ eventsStream.write(json);
510
+ if (verbose === "pretty") stderr.write(formatPretty(stamped) + "\n");
511
+ else if (verbose === "json") stderr.write(json);
512
+ };
513
+ }
514
+ function resolveExplicitEventsPath(ctx) {
515
+ const explicit = ctx.env.TEXT2SQL_INDEX_EVENTS_PATH?.trim();
516
+ return explicit ? path3.resolve(ctx.cwd, explicit) : null;
517
+ }
518
+ async function openEventsWriter(eventsPath, explicitEventsPath, stderr) {
519
+ const probe = explicitEventsPath ? await probeFifoWriter(explicitEventsPath, stderr) : null;
520
+ const stream = createWriteStream(eventsPath, {
521
+ flags: "a",
522
+ encoding: "utf-8"
523
+ });
524
+ try {
525
+ await waitForOpen(stream);
526
+ return stream;
527
+ } catch (err) {
528
+ stream.destroy();
529
+ throw err;
530
+ } finally {
531
+ await probe?.close().catch(() => {
532
+ });
533
+ }
534
+ }
535
+ async function probeFifoWriter(eventsPath, stderr) {
536
+ let stats;
537
+ try {
538
+ stats = await stat(eventsPath);
539
+ } catch (err) {
540
+ if (err.code === "ENOENT") return null;
541
+ throw err;
542
+ }
543
+ if (!stats.isFIFO()) return null;
544
+ try {
545
+ return await open(eventsPath, constants.O_WRONLY | constants.O_NONBLOCK);
546
+ } catch (err) {
547
+ if (err.code === "ENXIO") {
548
+ stderr.write(`sql index: waiting for reader on FIFO ${eventsPath}...
549
+ `);
550
+ return null;
551
+ }
552
+ throw err;
553
+ }
554
+ }
555
+ function waitForOpen(stream) {
556
+ return new Promise((resolve4, reject) => {
557
+ if (!stream.pending) {
558
+ resolve4();
559
+ return;
560
+ }
561
+ const onOpen = () => {
562
+ stream.off("error", onError);
563
+ resolve4();
564
+ };
565
+ const onError = (err) => {
566
+ stream.off("open", onOpen);
567
+ reject(err);
568
+ };
569
+ stream.once("open", onOpen);
570
+ stream.once("error", onError);
571
+ });
572
+ }
573
+ function countSchemaFragments(fragments) {
574
+ return fragments.reduce((count, adapterFragment) => {
575
+ if (Array.isArray(adapterFragment.data)) {
576
+ return count + adapterFragment.data.length;
577
+ }
578
+ return count + 1;
579
+ }, 0);
580
+ }
581
+ function dedupe(values) {
582
+ return [...new Set(values)];
583
+ }
584
+
585
+ // packages/text2sql/src/bin/commands/run.ts
586
+ import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
587
+ import * as path4 from "node:path";
588
+ import { v7 as v72 } from "uuid";
589
+ var RunCommand = class extends SqlQueryCommand {
590
+ name = "run";
591
+ description = "Execute query against <db> and store results";
592
+ usage = '<db> "SELECT ..." [--out-dir <path>]';
593
+ helpDisplay = '<db> "SELECT ..."';
594
+ options = [OUT_DIR_OPTION];
595
+ async afterValidation(ctx, adapter, sql, options) {
596
+ const outputDir = resolveOutputDir(ctx, options);
597
+ let rows;
598
+ try {
599
+ rows = await adapter.execute(sql);
600
+ } catch (error) {
601
+ this.fail(errorMessage(error));
602
+ }
603
+ if (!Array.isArray(rows)) {
604
+ this.fail("adapter.execute must return an array of rows");
605
+ }
606
+ const outPath = path4.join(outputDir, `${v72()}.json`);
607
+ try {
608
+ await mkdir2(outputDir, { recursive: true });
609
+ await writeFile3(outPath, JSON.stringify(rows, null, 2));
610
+ } catch (error) {
611
+ this.fail(errorMessage(error));
612
+ }
613
+ const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
614
+ ctx.stdout.write(`results stored in ${outPath}
615
+ `);
616
+ ctx.stdout.write(`columns: ${columns.join(", ") || "(none)"}
617
+ `);
618
+ ctx.stdout.write(`rows: ${rows.length}
619
+ `);
620
+ return 0;
621
+ }
622
+ };
623
+
624
+ // packages/text2sql/src/bin/commands/validate.ts
625
+ var ValidateCommand = class extends SqlQueryCommand {
626
+ name = "validate";
627
+ description = "Validate query syntax against <db>";
628
+ usage = '<db> "SELECT ..."';
629
+ helpDisplay = '<db> "SELECT ..."';
630
+ async afterValidation(ctx) {
631
+ ctx.stdout.write("valid\n");
632
+ return 0;
633
+ }
634
+ };
635
+
636
+ // packages/text2sql/src/bin/commands/registry.ts
637
+ var commands = [
638
+ new RunCommand(),
639
+ new ValidateCommand(),
640
+ new IndexCommand()
641
+ ];
642
+
643
+ // packages/text2sql/src/bin/load-adapters.ts
644
+ import { resolve as resolve3 } from "node:path";
645
+ import { pathToFileURL } from "node:url";
646
+ async function loadAdapters() {
647
+ const target = process.env.TEXT2SQL_ADAPTERS;
648
+ if (!target) {
649
+ throw new Error(
650
+ "TEXT2SQL_ADAPTERS env var is not set. Point it at a module whose default export is Record<string, Adapter>."
651
+ );
652
+ }
653
+ const specifier = target.startsWith(".") || target.startsWith("/") ? pathToFileURL(resolve3(target)).href : target;
654
+ let mod;
655
+ try {
656
+ mod = await import(specifier);
657
+ } catch (cause) {
658
+ throw new Error(
659
+ `TEXT2SQL_ADAPTERS=${target}: failed to import module - ${errorMessage(cause)}`
660
+ );
661
+ }
662
+ const exported = mod.default;
663
+ if (!exported || typeof exported !== "object" || Array.isArray(exported)) {
664
+ throw new Error(
665
+ `TEXT2SQL_ADAPTERS=${target}: default export must be a Record<string, Adapter> (got ${describe(exported)}).`
666
+ );
667
+ }
668
+ const entries = Object.entries(exported);
669
+ if (entries.length === 0) {
670
+ throw new Error(
671
+ `TEXT2SQL_ADAPTERS=${target}: default export is an empty object - declare at least one adapter.`
672
+ );
673
+ }
674
+ for (const [name, value] of entries) {
675
+ if (!isAdapterShape(value)) {
676
+ throw new Error(
677
+ `TEXT2SQL_ADAPTERS=${target}: adapter "${name}" is missing one of the required methods (format, validate, execute).`
678
+ );
679
+ }
680
+ }
681
+ try {
682
+ validateAdapterNames(entries.map(([name]) => name));
683
+ } catch (cause) {
684
+ throw new Error(`TEXT2SQL_ADAPTERS=${target}: ${errorMessage(cause)}`);
685
+ }
686
+ return exported;
687
+ }
688
+ function isAdapterShape(value) {
689
+ if (!value || typeof value !== "object") return false;
690
+ const v = value;
691
+ return typeof v.format === "function" && typeof v.validate === "function" && typeof v.execute === "function";
692
+ }
693
+ function describe(value) {
694
+ if (value === null) return "null";
695
+ if (Array.isArray(value)) return "array";
696
+ return typeof value;
697
+ }
698
+
699
+ // packages/text2sql/src/bin/sql.ts
700
+ var cli = cac("sql");
701
+ cli.usage("<run|validate|index> ...").help(
702
+ (sections) => sections.map(
703
+ (section) => section.title === "Commands" ? { ...section, body: rewriteCommandsHelp(section.body) } : section
704
+ )
705
+ );
706
+ for (const command of commands) {
707
+ registerCommand(cli, command);
708
+ }
709
+ process.exitCode = await runCli();
710
+ async function runCli() {
711
+ try {
712
+ cli.parse(process.argv, { run: false });
713
+ if (cli.options.help) return 0;
714
+ if (!cli.matchedCommand) {
715
+ try {
716
+ cli.globalCommand.checkUnknownOptions();
717
+ cli.globalCommand.checkOptionValue();
718
+ } catch (error) {
719
+ return writeCliError(error);
720
+ }
721
+ const [subcommand] = cli.args;
722
+ const label = subcommand ? `unknown subcommand "${String(subcommand)}"` : "missing subcommand";
723
+ process.stderr.write(`sql: ${label}
724
+ `);
725
+ cli.outputHelp();
726
+ return 2;
727
+ }
728
+ const result = await cli.runMatchedCommand();
729
+ return typeof result === "number" ? result : 0;
730
+ } catch (error) {
731
+ return writeCliError(error);
732
+ }
733
+ }
734
+ function registerCommand(cli2, command) {
735
+ const cliCommand = cli2.command(`${command.name} ${command.args}`, command.description).usage(command.usage);
736
+ for (const option of command.options) {
737
+ cliCommand.option(option.flag, option.description);
738
+ }
739
+ cliCommand.action(async (...callArgs) => {
740
+ const options = callArgs[callArgs.length - 1] ?? {};
741
+ const positional = callArgs.slice(0, -1);
742
+ return runCommand(command, positional, options);
743
+ });
744
+ }
745
+ async function runCommand(command, positional, options) {
746
+ let ctx;
747
+ try {
748
+ ctx = {
749
+ adapters: await loadAdapters(),
750
+ cwd: process.cwd(),
751
+ env: process.env,
752
+ stdout: process.stdout,
753
+ stderr: process.stderr
754
+ };
755
+ } catch (error) {
756
+ process.stderr.write(`${errorMessage(error)}
757
+ `);
758
+ return 2;
759
+ }
760
+ try {
761
+ return await command.execute(ctx, positional, options);
762
+ } catch (error) {
763
+ if (error instanceof CommandError) {
764
+ renderCommandError(error, process.stderr);
765
+ return error.exitCode;
766
+ }
767
+ throw error;
768
+ }
769
+ }
770
+ function rewriteCommandsHelp(body) {
771
+ return commands.reduce((current, command) => {
772
+ if (!command.helpDisplay) return current;
773
+ return current.replace(
774
+ `${command.name} ${command.args}`,
775
+ `${command.name} ${command.helpDisplay}`
776
+ );
777
+ }, body);
778
+ }
779
+ function writeCliError(error) {
780
+ if (error instanceof Error && error.name === "CACError") {
781
+ process.stderr.write(`sql: ${error.message}
782
+ `);
783
+ cli.outputHelp();
784
+ return 2;
785
+ }
786
+ if (error instanceof CommandError) {
787
+ renderCommandError(error, process.stderr);
788
+ return error.exitCode;
789
+ }
790
+ process.stderr.write(
791
+ `sql: unexpected error: ${error instanceof Error ? error.stack ?? error.message : String(error)}
792
+ `
793
+ );
794
+ return 1;
795
+ }