@deepagents/text2sql 7.1.0 → 7.1.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.
Files changed (47) hide show
  1. package/README.md +63 -76
  2. package/dist/bin/sql.js +42 -584
  3. package/dist/eval/src/evals/models.d.ts +12 -0
  4. package/dist/eval/src/evals/models.d.ts.map +1 -0
  5. package/dist/eval/src/evals/sql-create-context/sql-create-context.eval.d.ts +2 -0
  6. package/dist/eval/src/evals/sql-create-context/sql-create-context.eval.d.ts.map +1 -0
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +130 -211
  10. package/dist/index.js.map +4 -4
  11. package/dist/lib/adapter-index.d.ts +0 -2
  12. package/dist/lib/adapter-index.d.ts.map +1 -1
  13. package/dist/lib/adapters/posthog/index.js.map +2 -2
  14. package/dist/lib/adapters/posthog/posthog.d.ts.map +1 -1
  15. package/dist/lib/adapters/posthog/schema.posthog.grounding.d.ts.map +1 -1
  16. package/dist/lib/adapters/spreadsheet/index.js +4 -4
  17. package/dist/lib/adapters/spreadsheet/index.js.map +2 -2
  18. package/dist/lib/adapters/sqlite/index.js +0 -3
  19. package/dist/lib/adapters/sqlite/index.js.map +2 -2
  20. package/dist/lib/adapters/sqlite/sqlite.d.ts.map +1 -1
  21. package/dist/lib/adapters/sqlserver/index.js +43 -59
  22. package/dist/lib/adapters/sqlserver/index.js.map +2 -2
  23. package/dist/lib/adapters/sqlserver/table.sqlserver.grounding.d.ts +1 -1
  24. package/dist/lib/adapters/sqlserver/table.sqlserver.grounding.d.ts.map +1 -1
  25. package/dist/lib/agents/question.agent.d.ts.map +1 -1
  26. package/dist/lib/agents/suggestions.agents.d.ts.map +1 -1
  27. package/dist/lib/fs/sqlite/sqlite-fs.d.ts.map +1 -1
  28. package/dist/lib/index-cache.d.ts +2 -3
  29. package/dist/lib/index-cache.d.ts.map +1 -1
  30. package/dist/lib/sql-command.d.ts +1 -4
  31. package/dist/lib/sql-command.d.ts.map +1 -1
  32. package/dist/lib/sql.d.ts +0 -5
  33. package/dist/lib/sql.d.ts.map +1 -1
  34. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
  35. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -1
  36. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
  37. package/dist/lib/synthesis/index.js.map +2 -2
  38. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
  39. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
  40. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
  41. package/dist/scripts/scripts/bigquery-test.d.ts +2 -0
  42. package/dist/scripts/scripts/bigquery-test.d.ts.map +1 -0
  43. package/package.json +4 -6
  44. package/dist/lib/index-lock.d.ts +0 -60
  45. package/dist/lib/index-lock.d.ts.map +0 -1
  46. package/dist/lib/prompt.d.ts +0 -2
  47. package/dist/lib/prompt.d.ts.map +0 -1
package/README.md CHANGED
@@ -37,7 +37,7 @@ Requires Node.js LTS
37
37
  import { groq } from '@ai-sdk/groq';
38
38
  import pg from 'pg';
39
39
 
40
- import { FileIndexLock, Text2Sql } from '@deepagents/text2sql';
40
+ import { Text2Sql, toSql } from '@deepagents/text2sql';
41
41
  import {
42
42
  Postgres,
43
43
  columnValues,
@@ -69,22 +69,24 @@ const adapter = new Postgres({
69
69
  ],
70
70
  });
71
71
 
72
- const text2sql = new Text2Sql({
72
+ const text2sql = new Text2Sql({ adapters: { main: adapter } });
73
+
74
+ const fragments = await text2sql.index({ names: ['main'] });
75
+ const { sql } = await toSql({
76
+ input: 'Show me the top 10 customers by revenue',
77
+ adapter,
78
+ fragments,
73
79
  model: groq('openai/gpt-oss-20b'),
74
- adapters: { main: adapter },
75
- lock: new FileIndexLock(),
76
80
  });
77
-
78
- // Generate SQL
79
- const sql = await text2sql.toSql(
80
- 'Show me the top 10 customers by revenue',
81
- 'main',
82
- );
83
81
  console.log(sql);
84
82
  ```
85
83
 
84
+ `index()` introspects the database schema and returns it as context fragments.
85
+ `toSql()` gives these fragments to the model and validates the SQL that the
86
+ model writes.
87
+
86
88
  The adapter-map key (`main` here) is the adapter name. Reuse that same key in
87
- `text2sql.toSql(..., 'main')` and in any `sql validate <db> "..."` /
89
+ `text2sql.index({ names: ['main'] })` and in any `sql validate <db> "..."` /
88
90
  `sql run <db> "..."` calls from a sandbox where the package CLI is installed.
89
91
  This is the configured connection name, not the SQL-level database or schema
90
92
  name (for example, SQLite's default `main` schema).
@@ -223,7 +225,7 @@ The `sql` CLI caches introspected schema only when you opt in via env: set
223
225
  `TEXT2SQL_INDEX_CACHE_DIR` (where cache files live) and/or
224
226
  `TEXT2SQL_INDEX_VERSION` (an invalidation token — bump it when the schema
225
227
  changes). With neither set, every `sql index` introspects fresh. See
226
- [Schema index caching & coordination](#schema-index-caching--coordination) for
228
+ [Schema index caching](#schema-index-caching) for
227
229
  the underlying injectable primitives.
228
230
 
229
231
  For in-process or virtual-sandbox usage (without installing the package CLI),
@@ -234,16 +236,11 @@ import { InMemoryFs, createVirtualSandbox } from '@deepagents/context';
234
236
  import {
235
237
  type CreateSqlCommandOptions,
236
238
  type CreateSqlCommandResult,
237
- FileIndexLock,
238
239
  Text2Sql,
239
240
  createSqlCommand,
240
241
  } from '@deepagents/text2sql';
241
242
 
242
- const text2sql = new Text2Sql({
243
- model,
244
- adapters: { main: adapter },
245
- lock: new FileIndexLock(),
246
- });
243
+ const text2sql = new Text2Sql({ adapters: { main: adapter } });
247
244
 
248
245
  const commandOptions: CreateSqlCommandOptions = {
249
246
  outputDir: '/sql-artifacts',
@@ -318,9 +315,9 @@ the model loop. Shell-escaped dollar-prefixed PostHog properties such as
318
315
  ## Fragments
319
316
 
320
317
  Inject domain knowledge by setting fragments on the `ContextEngine` you build
321
- for the agent. Those fragments affect every `chat()` turn. If you need direct
322
- SQL generation with extra fragments, use the lower-level `toSql({ fragments })`
323
- helper exported from `@deepagents/text2sql`.
318
+ for the agent. Those fragments affect every `chat()` turn. For direct SQL
319
+ generation, give `toSql({ fragments })` the fragments from `index()` and your
320
+ own fragments.
324
321
 
325
322
  ```typescript
326
323
  import {
@@ -399,79 +396,70 @@ for await (const chunk of followUp) {
399
396
  }
400
397
  ```
401
398
 
402
- ## Schema index caching & coordination
399
+ ## Schema index caching
403
400
 
404
401
  Schema introspection is the expensive part of indexing. `Text2Sql` and
405
- `AdapterIndexer` own no storage and no locking — you inject them, keyed per
406
- adapter. A `lock` is **required**; a `cache` is optional:
402
+ `AdapterIndexer` own no storage. You can give them a cache. The cache key is
403
+ the adapter name:
407
404
 
408
405
  ```typescript
409
406
  export interface IndexCache {
410
407
  read(key: string): Promise<ContextFragment[] | null>;
411
408
  write(key: string, fragments: ContextFragment[]): Promise<void>;
412
409
  }
413
-
414
- export interface IndexLock {
415
- // Acquire (waiting if held elsewhere), run fn, release — even on throw.
416
- // Reject if the lock can't be acquired (callers fail closed).
417
- run<T>(key: string, fn: () => Promise<T>): Promise<T>;
418
- }
419
410
  ```
420
411
 
421
- - **`lock` only** (no `cache`) → introspection is serialized but not
422
- deduplicated: each waiter re-introspects, because the cache recheck under the
423
- lock is what turns serialization into single-flight.
424
- - **`lock` + `cache`** → introspection is single-flight: concurrent callers for
425
- the same adapter wait on the lock, then read the warm cache the holder wrote.
412
+ - **No `cache`**: each `index()` call introspects the database.
413
+ - **A `cache`**: `index()` reads the cache first. On a miss, it introspects the
414
+ database and writes the result to the cache.
426
415
 
427
- File-backed `IndexCache` and `IndexLock` implementations ship as composable
428
- primitives:
416
+ The package ships a file-backed cache:
429
417
 
430
418
  ```typescript
431
- import { FileIndexCache, FileIndexLock, Text2Sql } from '@deepagents/text2sql';
419
+ import { FileIndexCache, Text2Sql } from '@deepagents/text2sql';
432
420
 
433
421
  const text2sql = new Text2Sql({
434
- model,
435
422
  adapters: { main: adapter },
436
- // Point both `dir`s at a shared volume to share one cache + lock across
437
- // processes on the same filesystem.
438
423
  cache: new FileIndexCache({ dir: '/var/cache/text2sql', namespace: 'v1' }),
439
- lock: new FileIndexLock({ dir: '/var/cache/text2sql', namespace: 'v1' }),
440
424
  });
441
425
  ```
442
426
 
443
- `FileIndexCache` writes atomically (temp + rename) and treats a file that does
444
- not parse to context fragments as a miss, so a torn read self-heals into a
445
- re-introspect. `FileIndexLock`
446
- (built on `proper-lockfile`, options `{ dir?, namespace?, stale?, retries? }`)
447
- serializes processes that share a POSIX filesystem; a held lock auto-refreshes
448
- its mtime so a slow introspection is not mistaken for a crash, and acquisition
449
- fails closed once retries are exhausted. Both `dir`s default to the OS temp
450
- directory.
451
-
452
- ### Horizontally-scaled deployments
453
-
454
- Running many processes/containers against one database (e.g. a fleet of
455
- daemons)? Two coordination tiers:
456
-
457
- - The **lock alone** serializes introspection — never two concurrent
458
- introspections of the same database (caps peak DB load). The shipped
459
- `FileIndexLock` covers processes that share a POSIX filesystem (one host, or a
460
- shared volume — NFSv4 / EFS); for hosts without a shared filesystem, back it
461
- with a distributed lock you already operate (a Redis lock, a Postgres
462
- `pg_advisory_lock`, etc.).
463
- - The **lock plus a shared cache directory** gives fleet-wide single-flight:
464
- the lock holder writes the cache on the shared volume, and every waiter reads
465
- it instead of re-introspecting.
466
-
467
- > On object-storage-backed volumes (GCS/S3 FUSE) `rename` is not atomic and
468
- > file locks are unreliable — that is exactly why the lock and cache are
469
- > injectable. Use a real distributed lock; the `FileIndexCache` parse-as-miss
470
- > behavior plus the post-lock recheck tolerate a non-atomic write.
471
-
472
- See `demo/text2sql-daemon` for a daemon that injects a `FileIndexCache`
473
- (`TEXT2SQL_INDEX_CACHE_DIR` / `TEXT2SQL_INDEX_VERSION`) and a `pg_advisory_lock`
474
- backed `IndexLock`.
427
+ `FileIndexCache` writes atomically (temp + rename). It treats a file that does
428
+ not parse to context fragments as a miss, so a torn read causes a new
429
+ introspection. `dir` defaults to the OS temp directory.
430
+
431
+ ### Introspect once for concurrent callers
432
+
433
+ `index()` does not make callers wait for each other. When two callers miss the
434
+ cache at the same time, both introspect the database. The result stays
435
+ correct, because each write replaces the whole cache file.
436
+
437
+ To let only one caller introspect, acquire a key around `index()`. This example
438
+ uses [`@zukhruf/mutex`](https://www.npmjs.com/package/@zukhruf/mutex):
439
+
440
+ ```typescript
441
+ import { Mutex, SqliteStore } from '@zukhruf/mutex';
442
+
443
+ const mutex = new Mutex(new SqliteStore('/var/lib/my-app/locks'));
444
+
445
+ const fragments = await mutex.acquire('text2sql:main', () =>
446
+ text2sql.index({ names: ['main'] }),
447
+ );
448
+ ```
449
+
450
+ The first caller introspects the database and writes the cache. The other
451
+ callers wait. When each waiter gets the key, its `index()` call reads the cache
452
+ that the first caller wrote.
453
+
454
+ `SqliteStore` shares keys between the processes on one host. For a fleet of
455
+ hosts, acquire the key from a service that all hosts share, and give all hosts
456
+ the same cache. `demo/text2sql-daemon` uses a Postgres advisory lock for the
457
+ key and a `FileIndexCache` from `TEXT2SQL_INDEX_CACHE_DIR` /
458
+ `TEXT2SQL_INDEX_VERSION`.
459
+
460
+ > On object-storage-backed volumes (GCS/S3 FUSE) `rename` is not atomic. The
461
+ > `FileIndexCache` parse-as-miss behavior tolerates a torn write: the next
462
+ > `index()` call introspects again.
475
463
 
476
464
  ## Streaming Index Progress
477
465
 
@@ -484,12 +472,11 @@ import { createUIMessageStream } from 'ai';
484
472
 
485
473
  import {
486
474
  AdapterIndexer,
487
- FileIndexLock,
488
475
  TEXT2SQL_INDEX_PROGRESS_CHUNK,
489
476
  type Text2SqlIndexProgressEvent,
490
477
  } from '@deepagents/text2sql';
491
478
 
492
- const indexer = new AdapterIndexer({ adapters, lock: new FileIndexLock() });
479
+ const indexer = new AdapterIndexer({ adapters });
493
480
 
494
481
  await context.continue(user('Show me top 10 customers'));
495
482