@onreza/sqlx-js 0.4.0 → 0.5.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.
@@ -1,18 +1,28 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  import { PgClient, parseDatabaseUrl, PgError } from "../pg/wire.js";
2
5
  import { SchemaCache, compositeLiteral } from "../pg/schema.js";
3
6
  import { analyzeQuery } from "../pg/analyze.js";
4
- import { isBuiltinOid, oidToTs } from "../pg/oids.js";
5
- import { scanProject } from "../scan/scanner.js";
6
- import { Cache, fingerprint, effectiveNullable } from "../cache.js";
7
+ import { arrayElementOid, isBuiltinOid, oidToTs } from "../pg/oids.js";
8
+ import { ScanError, scanProject } from "../scan/scanner.js";
9
+ import { assertCacheManifest, Cache, fingerprint, effectiveNullable, portableCacheOid, writeCacheManifest, } from "../cache.js";
7
10
  import { emitDts } from "../codegen.js";
8
- import { loadConfig, lookupJsonbType } from "../config.js";
11
+ import { loadConfig, lookupJsonbType, prepareConfigHash } from "../config.js";
9
12
  import { readFunctionCache, writeFunctionCache } from "../function-cache.js";
10
13
  import { introspectFunctions } from "../pg/functions.js";
11
14
  import { buildParamMap } from "../pg/param-map.js";
12
15
  import { mergeExtensionTypes } from "../pg/extensions.js";
16
+ import { compareArtifacts } from "../artifacts.js";
13
17
  const JSON_OIDS = new Set([114, 3802]);
14
18
  const JSON_ARRAY_OIDS = new Set([199, 3807]);
15
- const JSON_INPUT = 'import("@onreza/sqlx-js").JsonInput';
19
+ const JSON_INPUT_VALUE = 'import("@onreza/sqlx-js").JsonInputValue';
20
+ function jsonParameter(type) {
21
+ return `import("@onreza/sqlx-js").JsonParameter<${type}>`;
22
+ }
23
+ function arrayParameter(type) {
24
+ return `import("@onreza/sqlx-js").PgArrayParameter<${type}>`;
25
+ }
16
26
  function enumUnion(values) {
17
27
  if (values.length === 0)
18
28
  return "never";
@@ -56,15 +66,39 @@ function resolveColumnTs(f, schema, cfg) {
56
66
  return resolveTs(f.typeOid, (oid) => schema.customType(oid));
57
67
  }
58
68
  function resolveParamTs(paramIndex, paramOid, paramMap, schema, cfg) {
59
- if (JSON_OIDS.has(paramOid) || JSON_ARRAY_OIDS.has(paramOid)) {
69
+ if (JSON_OIDS.has(paramOid)) {
60
70
  const target = paramMap.get(paramIndex);
61
71
  if (target) {
62
72
  const column = resolveTargetColumn(target, schema);
63
73
  const decl = column ? lookupJsonbType(cfg, target.schema ?? "public", target.table, column) : undefined;
64
74
  if (decl)
65
- return JSON_ARRAY_OIDS.has(paramOid) ? `(${decl})[]` : decl;
75
+ return jsonParameter(decl);
66
76
  }
67
- return JSON_ARRAY_OIDS.has(paramOid) ? `(${JSON_INPUT})[]` : JSON_INPUT;
77
+ return jsonParameter(JSON_INPUT_VALUE);
78
+ }
79
+ if (JSON_ARRAY_OIDS.has(paramOid)) {
80
+ const target = paramMap.get(paramIndex);
81
+ if (target) {
82
+ const column = resolveTargetColumn(target, schema);
83
+ const decl = column ? lookupJsonbType(cfg, target.schema ?? "public", target.table, column) : undefined;
84
+ if (decl)
85
+ return arrayParameter(jsonParameter(decl));
86
+ }
87
+ return arrayParameter(jsonParameter(JSON_INPUT_VALUE));
88
+ }
89
+ const builtinElementOid = arrayElementOid(paramOid);
90
+ if (builtinElementOid !== undefined) {
91
+ return arrayParameter(resolveTs(builtinElementOid, (oid) => schema.customType(oid)));
92
+ }
93
+ const custom = schema.customType(paramOid);
94
+ if (custom?.kind === "enumArray")
95
+ return arrayParameter(enumUnion(custom.element.values));
96
+ if (custom?.kind === "scalarArray")
97
+ return arrayParameter(custom.element.tsType);
98
+ if (custom?.kind === "compositeArray")
99
+ return arrayParameter(compositeLiteral(custom.element));
100
+ if (custom) {
101
+ return resolveTs(paramOid, () => custom);
68
102
  }
69
103
  return resolveTs(paramOid, (oid) => schema.customType(oid));
70
104
  }
@@ -114,6 +148,29 @@ function isAliasOrExpression(f, schema) {
114
148
  const real = schema.columnNameByAttno(f.tableOid, f.columnAttr);
115
149
  return real !== undefined && real !== f.name;
116
150
  }
151
+ export class PrepareFatalError extends Error {
152
+ phase;
153
+ file;
154
+ line;
155
+ column;
156
+ constructor(phase, message, location = {}, options) {
157
+ super(message, options);
158
+ this.phase = phase;
159
+ this.name = "PrepareFatalError";
160
+ this.file = location.file;
161
+ this.line = location.line;
162
+ this.column = location.column;
163
+ }
164
+ }
165
+ function fatal(phase, error) {
166
+ if (error instanceof PrepareFatalError)
167
+ return error;
168
+ const message = error instanceof Error ? error.message : String(error);
169
+ const location = error instanceof ScanError
170
+ ? { file: error.file, line: error.line, column: error.column }
171
+ : {};
172
+ return new PrepareFatalError(phase, message, location, { cause: error });
173
+ }
117
174
  function formatSite(s) {
118
175
  return `${s.file}:${s.line}:${s.column}`;
119
176
  }
@@ -131,10 +188,28 @@ function siteUsage(sites) {
131
188
  };
132
189
  }
133
190
  export async function openSession(opts) {
134
- const userCfg = await loadConfig(opts.root);
135
- const cfg = parseDatabaseUrl(opts.databaseUrl);
191
+ let userCfg;
192
+ try {
193
+ userCfg = await loadConfig(opts.root);
194
+ }
195
+ catch (error) {
196
+ throw fatal("config", error);
197
+ }
198
+ let cfg;
199
+ try {
200
+ cfg = parseDatabaseUrl(opts.databaseUrl);
201
+ }
202
+ catch (error) {
203
+ throw fatal("connect", error);
204
+ }
136
205
  const client = new PgClient(cfg);
137
- await client.connect();
206
+ try {
207
+ await client.connect();
208
+ }
209
+ catch (error) {
210
+ await client.end().catch(() => { });
211
+ throw fatal("connect", error);
212
+ }
138
213
  const schema = new SchemaCache(client);
139
214
  schema.setTypeRegistry(mergeExtensionTypes(userCfg.customTypes));
140
215
  return { client, schema, userCfg };
@@ -192,8 +267,15 @@ export async function describeAll(cfg, sessionClient, queries, concurrency) {
192
267
  return results;
193
268
  }
194
269
  export async function prepareOnce(opts, session, log = console.log, err = console.error, concurrency = defaultPrepareConcurrency()) {
195
- const sites = scanProject(opts.root);
270
+ let sites;
271
+ try {
272
+ sites = scanProject(opts.root, session.userCfg.scan);
273
+ }
274
+ catch (error) {
275
+ throw fatal("scan", error);
276
+ }
196
277
  log(`scanned: found ${sites.length} sql() call site(s)`);
278
+ const diagnostics = [];
197
279
  const cache = new Cache(opts.cacheDir);
198
280
  const unique = new Map();
199
281
  for (const s of sites) {
@@ -219,6 +301,18 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
219
301
  failures++;
220
302
  const e = outcome.error;
221
303
  if (e instanceof PgError) {
304
+ diagnostics.push({
305
+ severity: "error",
306
+ phase: "describe",
307
+ message: e.message,
308
+ file: site.file,
309
+ line: site.line,
310
+ column: site.column,
311
+ query,
312
+ ...(e.code ? { code: e.code } : {}),
313
+ ...(e.position ? { position: e.position } : {}),
314
+ ...(e.hint ? { hint: e.hint } : {}),
315
+ });
222
316
  const extras = [];
223
317
  if (e.position)
224
318
  extras.push(`pos ${e.position}`);
@@ -231,6 +325,15 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
231
325
  err(` query: ${snippet(query)}`);
232
326
  }
233
327
  else {
328
+ diagnostics.push({
329
+ severity: "error",
330
+ phase: "describe",
331
+ message: e.message,
332
+ file: site.file,
333
+ line: site.line,
334
+ column: site.column,
335
+ query,
336
+ });
234
337
  err(` ✗ ${formatSite(site)} — describe failed: ${e.message}`);
235
338
  err(` query: ${snippet(query)}`);
236
339
  }
@@ -245,8 +348,13 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
245
348
  }
246
349
  }
247
350
  }
248
- await schema.loadAttributes(allAttrRefs);
249
- await schema.loadTableNamesByOid(allTableOids);
351
+ try {
352
+ await schema.loadAttributes(allAttrRefs);
353
+ await schema.loadTableNamesByOid(allTableOids);
354
+ }
355
+ catch (error) {
356
+ throw fatal("introspect", error);
357
+ }
250
358
  const analyses = new Map();
251
359
  const paramMaps = new Map();
252
360
  const failedFps = new Set();
@@ -258,6 +366,15 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
258
366
  catch (e) {
259
367
  failures++;
260
368
  failedFps.add(r.fp);
369
+ diagnostics.push({
370
+ severity: "error",
371
+ phase: "analyze",
372
+ message: e.message,
373
+ file: site.file,
374
+ line: site.line,
375
+ column: site.column,
376
+ query: r.query,
377
+ });
261
378
  err(` ✗ ${formatSite(site)} — analyze failed: ${e.message}`);
262
379
  err(` query: ${snippet(r.query)}`);
263
380
  continue;
@@ -268,31 +385,44 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
268
385
  catch (e) {
269
386
  failures++;
270
387
  failedFps.add(r.fp);
388
+ diagnostics.push({
389
+ severity: "error",
390
+ phase: "param-map",
391
+ message: e.message,
392
+ file: site.file,
393
+ line: site.line,
394
+ column: site.column,
395
+ query: r.query,
396
+ });
271
397
  err(` ✗ ${formatSite(site)} — paramMap failed: ${e.message}`);
272
398
  err(` query: ${snippet(r.query)}`);
273
399
  }
274
400
  }
275
- const dmlTablesToLoad = new Set();
401
+ const dmlTablesToLoad = new Map();
276
402
  for (const pm of paramMaps.values()) {
277
403
  for (const idx of pm.dmlBound) {
278
404
  const t = pm.targets.get(idx);
279
- if (t)
280
- dmlTablesToLoad.add(`${t.schema ?? "public"}.${t.table}`);
405
+ if (t) {
406
+ const schema = t.schema ?? "public";
407
+ dmlTablesToLoad.set(JSON.stringify([schema, t.table]), { schema, name: t.table });
408
+ }
281
409
  }
282
410
  }
283
- if (dmlTablesToLoad.size > 0) {
284
- const names = [...dmlTablesToLoad].map((k) => {
285
- const [schema, name] = k.split(".");
286
- return { schema, name: name };
287
- });
288
- await schema.loadTableNames(names);
289
- const oids = [];
290
- for (const n of names) {
291
- const oid = schema.resolveTable(n.schema, n.name);
292
- if (oid !== undefined)
293
- oids.push(oid);
411
+ try {
412
+ if (dmlTablesToLoad.size > 0) {
413
+ const names = [...dmlTablesToLoad.values()];
414
+ await schema.loadTableNames(names);
415
+ const oids = [];
416
+ for (const n of names) {
417
+ const oid = schema.resolveTable(n.schema, n.name);
418
+ if (oid !== undefined)
419
+ oids.push(oid);
420
+ }
421
+ await schema.loadColumnsForTables(oids);
294
422
  }
295
- await schema.loadColumnsForTables(oids);
423
+ }
424
+ catch (error) {
425
+ throw fatal("introspect", error);
296
426
  }
297
427
  const unknownOids = new Set();
298
428
  for (const r of raw) {
@@ -303,8 +433,14 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
303
433
  if (!isBuiltinOid(f.typeOid))
304
434
  unknownOids.add(f.typeOid);
305
435
  }
306
- await schema.loadCustomTypes([...unknownOids]);
436
+ try {
437
+ await schema.loadCustomTypes([...unknownOids]);
438
+ }
439
+ catch (error) {
440
+ throw fatal("introspect", error);
441
+ }
307
442
  const entries = [];
443
+ const generated = [];
308
444
  for (const r of raw) {
309
445
  if (failedFps.has(r.fp))
310
446
  continue;
@@ -317,7 +453,7 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
317
453
  const entry = {
318
454
  query: r.query,
319
455
  ...siteUsage(r.sites),
320
- paramOids: r.paramOids,
456
+ paramOids: r.paramOids.map(portableCacheOid),
321
457
  paramTsTypes: r.paramOids.map((o, idx) => resolveParamTs(idx + 1, o, pm.targets, schema, userCfg)),
322
458
  paramNullable: r.paramOids.map((_o, idx) => resolveParamNullable(idx + 1, pm, schema)),
323
459
  columns: r.fields.map((f, i) => {
@@ -325,7 +461,7 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
325
461
  const treatAsOverride = parsed.override !== undefined && isAliasOrExpression(f, schema);
326
462
  return {
327
463
  name: parsed.name,
328
- typeOid: f.typeOid,
464
+ typeOid: portableCacheOid(f.typeOid),
329
465
  tsType: resolveColumnTs(f, schema, userCfg),
330
466
  nullable: analysis.perColumnNullable[i] ?? true,
331
467
  ...(treatAsOverride ? { override: parsed.override } : {}),
@@ -334,31 +470,76 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
334
470
  hasResultSet: r.fields.length > 0,
335
471
  ...(analysis.degraded ? { degraded: analysis.degraded } : {}),
336
472
  };
337
- cache.write(r.fp, entry);
338
473
  entries.push(entry);
474
+ generated.push({ fp: r.fp, entry });
339
475
  const nn = entry.columns.filter((c) => !effectiveNullable(c)).length;
340
476
  const tag = entry.degraded ? ` [degraded: ${entry.degraded.reason}]` : "";
341
477
  log(` ✓ ${formatSite(r.sites[0])} → ${r.paramOids.length} param(s), ${r.fields.length} col(s) [${nn} non-null]${tag}`);
342
478
  }
343
- let pruned = 0;
344
- if (opts.prune !== false) {
345
- pruned = cache.prune(unique.keys()).length;
346
- if (pruned > 0)
347
- log(`pruned ${pruned} orphaned cache entry/entries`);
479
+ if (failures > 0) {
480
+ return { sites: sites.length, entries: entries.length, failures, pruned: 0, functions: 0, diagnostics };
348
481
  }
349
- let functions = [];
350
- if (failures === 0) {
482
+ let functions;
483
+ try {
351
484
  functions = await introspectFunctions(client, schema);
485
+ }
486
+ catch (error) {
487
+ throw fatal("introspect", error);
488
+ }
489
+ let pruned;
490
+ try {
491
+ pruned = cache.replaceAll(generated, opts.prune !== false).length;
492
+ if (pruned > 0)
493
+ log(`pruned ${pruned} orphaned cache entry/entries`);
352
494
  writeFunctionCache(opts.cacheDir, functions);
495
+ writeCacheManifest(opts.cacheDir, prepareConfigHash(userCfg));
496
+ emitDts(opts.dtsPath, entries, functions);
497
+ }
498
+ catch (error) {
499
+ throw fatal("cache", error);
353
500
  }
354
- emitDts(opts.dtsPath, entries, functions);
355
- return { entries: entries.length, failures, pruned, functions: functions.length };
501
+ return { sites: sites.length, entries: entries.length, failures, pruned, functions: functions.length, diagnostics };
356
502
  }
357
503
  export async function runPrepare(opts) {
504
+ if (opts.verify) {
505
+ const verification = await verifyPrepareArtifacts(opts, opts.json ? () => { } : console.log, opts.json ? () => { } : console.error);
506
+ if (opts.json) {
507
+ console.log(JSON.stringify({
508
+ formatVersion: 1,
509
+ ok: verification.ok,
510
+ mode: "verify",
511
+ ...verification.result,
512
+ changed: verification.changed,
513
+ }, null, 2));
514
+ }
515
+ if (!verification.ok)
516
+ process.exitCode = 1;
517
+ return;
518
+ }
358
519
  if (opts.check) {
359
- const sites = scanProject(opts.root);
360
- console.log(`scanned: found ${sites.length} sql() call site(s)`);
520
+ let userCfg;
521
+ try {
522
+ userCfg = await loadConfig(opts.root);
523
+ }
524
+ catch (error) {
525
+ throw fatal("config", error);
526
+ }
527
+ let sites;
528
+ try {
529
+ sites = scanProject(opts.root, userCfg.scan);
530
+ }
531
+ catch (error) {
532
+ throw fatal("scan", error);
533
+ }
534
+ if (!opts.json)
535
+ console.log(`scanned: found ${sites.length} sql() call site(s)`);
361
536
  const cache = new Cache(opts.cacheDir);
537
+ try {
538
+ assertCacheManifest(opts.cacheDir, prepareConfigHash(userCfg));
539
+ }
540
+ catch (error) {
541
+ throw fatal("cache", error);
542
+ }
362
543
  const unique = new Map();
363
544
  for (const s of sites) {
364
545
  const fp = fingerprint(s.query);
@@ -368,38 +549,135 @@ export async function runPrepare(opts) {
368
549
  else
369
550
  unique.set(fp, { fp, query: s.query, sites: [s] });
370
551
  }
371
- let stale = 0;
552
+ const diagnostics = [];
372
553
  for (const { fp, query, sites: ss } of unique.values()) {
373
554
  if (!cache.has(fp)) {
374
- stale++;
375
- console.error(`stale: ${formatSite(ss[0])} — query not in cache`);
376
- console.error(` query: ${snippet(query)}`);
555
+ const site = ss[0];
556
+ diagnostics.push({
557
+ severity: "error",
558
+ phase: "cache",
559
+ message: "query is not in the offline cache",
560
+ file: site.file,
561
+ line: site.line,
562
+ column: site.column,
563
+ query,
564
+ });
565
+ if (!opts.json) {
566
+ console.error(`stale: ${formatSite(site)} — query not in cache`);
567
+ console.error(` query: ${snippet(query)}`);
568
+ }
377
569
  }
378
570
  }
379
- if (stale > 0) {
380
- console.error(`\nsqlx-js prepare --check: ${stale} stale/missing entries. Run \`sqlx-js prepare\` against a live DB.`);
381
- process.exit(1);
571
+ if (diagnostics.length > 0) {
572
+ if (opts.json) {
573
+ console.log(JSON.stringify({
574
+ formatVersion: 1,
575
+ ok: false,
576
+ mode: "check",
577
+ sites: sites.length,
578
+ entries: unique.size - diagnostics.length,
579
+ failures: diagnostics.length,
580
+ pruned: 0,
581
+ functions: 0,
582
+ diagnostics,
583
+ }, null, 2));
584
+ }
585
+ else {
586
+ console.error(`\nsqlx-js prepare --check: ${diagnostics.length} stale/missing entries. Run \`sqlx-js prepare\` against a live DB.`);
587
+ }
588
+ process.exitCode = 1;
589
+ return;
590
+ }
591
+ let entries;
592
+ let functions;
593
+ try {
594
+ entries = [...unique.values()].map((u) => {
595
+ const entry = cache.read(u.fp);
596
+ return entry ? { ...entry, ...siteUsage(u.sites) } : null;
597
+ }).filter((e) => e !== null);
598
+ functions = readFunctionCache(opts.cacheDir);
599
+ emitDts(opts.dtsPath, entries, functions);
600
+ }
601
+ catch (error) {
602
+ throw fatal("cache", error);
603
+ }
604
+ if (opts.json) {
605
+ console.log(JSON.stringify({
606
+ formatVersion: 1,
607
+ ok: true,
608
+ mode: "check",
609
+ sites: sites.length,
610
+ entries: entries.length,
611
+ failures: 0,
612
+ pruned: 0,
613
+ functions: functions.length,
614
+ diagnostics: [],
615
+ }, null, 2));
616
+ }
617
+ else {
618
+ console.log(`ok — ${entries.length} unique queries, ${functions.length} function(s), types regenerated`);
382
619
  }
383
- const entries = [...unique.values()].map((u) => {
384
- const entry = cache.read(u.fp);
385
- return entry ? { ...entry, ...siteUsage(u.sites) } : null;
386
- }).filter((e) => e !== null);
387
- const functions = readFunctionCache(opts.cacheDir);
388
- emitDts(opts.dtsPath, entries, functions);
389
- console.log(`ok — ${entries.length} unique queries, ${functions.length} function(s), types regenerated`);
390
620
  return;
391
621
  }
392
622
  const session = await openSession(opts);
393
623
  try {
394
- const r = await prepareOnce(opts, session);
624
+ const r = await prepareOnce(opts, session, opts.json ? () => { } : console.log, opts.json ? () => { } : console.error);
625
+ if (opts.json) {
626
+ console.log(JSON.stringify({ formatVersion: 1, ok: r.failures === 0, mode: "prepare", ...r }, null, 2));
627
+ }
395
628
  if (r.failures > 0) {
396
- console.error(`\n${r.failures} query/queries failed to prepare`);
397
- await session.client.end();
398
- process.exit(1);
629
+ if (!opts.json)
630
+ console.error(`\n${r.failures} query/queries failed to prepare`);
631
+ process.exitCode = 1;
632
+ return;
399
633
  }
400
- console.log(`\nprepared ${r.entries} unique query/queries, ${r.functions} function(s) → ${opts.dtsPath}`);
634
+ if (!opts.json)
635
+ console.log(`\nprepared ${r.entries} unique query/queries, ${r.functions} function(s) → ${opts.dtsPath}`);
401
636
  }
402
637
  finally {
403
638
  await session.client.end();
404
639
  }
405
640
  }
641
+ export async function verifyPrepareArtifacts(opts, log = console.log, err = console.error) {
642
+ const tmp = mkdtempSync(join(tmpdir(), "sqlx-js-verify-"));
643
+ const cacheDir = join(tmp, "cache");
644
+ const dtsPath = join(tmp, "sqlx-js-env.d.ts");
645
+ const verifyOpts = {
646
+ ...opts,
647
+ cacheDir,
648
+ dtsPath,
649
+ check: false,
650
+ verify: false,
651
+ prune: true,
652
+ };
653
+ let session;
654
+ try {
655
+ session = await openSession(verifyOpts);
656
+ const result = await prepareOnce(verifyOpts, session, log, err);
657
+ if (result.failures > 0) {
658
+ err(`\n${result.failures} query/queries failed to prepare`);
659
+ return { ok: false, result, changed: [] };
660
+ }
661
+ let comparison;
662
+ try {
663
+ comparison = compareArtifacts({ cacheDir: opts.cacheDir, dtsPath: opts.dtsPath }, { cacheDir, dtsPath });
664
+ }
665
+ catch (error) {
666
+ throw fatal("verify", error);
667
+ }
668
+ if (!comparison.ok) {
669
+ err("sqlx-js prepare --verify: generated artifacts are stale:");
670
+ for (const file of comparison.changed)
671
+ err(` ${file}`);
672
+ err("Run `sqlx-js prepare` and commit the regenerated artifacts.");
673
+ return { ok: false, result, changed: comparison.changed };
674
+ }
675
+ log(`verified ${result.entries} query/queries and ${result.functions} function(s); generated artifacts are current`);
676
+ return { ok: true, result, changed: [] };
677
+ }
678
+ finally {
679
+ if (session)
680
+ await session.client.end();
681
+ rmSync(tmp, { recursive: true, force: true });
682
+ }
683
+ }
@@ -1,4 +1,5 @@
1
1
  import { openSession, prepareOnce, type PrepareOptions, type PrepareSession } from "./prepare.js";
2
+ export declare function shouldWatchFile(filename: string): boolean;
2
3
  export type WatchPrepareHookResult = {
3
4
  resetSession?: boolean;
4
5
  };
@@ -1,9 +1,28 @@
1
1
  import { watch as fsWatch } from "node:fs";
2
2
  import { basename } from "node:path";
3
3
  import { openSession, prepareOnce } from "./prepare.js";
4
+ import { configHash, loadConfig } from "../config.js";
4
5
  const EXT_RE = /\.(ts|tsx|mts|cts|sql)$/;
5
6
  const SKIP_DIRS = ["node_modules", ".git", ".sqlx-js", "dist", "build", ".next"];
6
7
  const DEBOUNCE_MS = 150;
8
+ const CONFIG_FILES = new Set([
9
+ "sqlx-js.config.ts",
10
+ "sqlx-js.config.mts",
11
+ "sqlx-js.config.js",
12
+ "sqlx-js.config.mjs",
13
+ ]);
14
+ export function shouldWatchFile(filename) {
15
+ const file = filename.replace(/\\/g, "/");
16
+ if (SKIP_DIRS.some((dir) => file === dir || file.startsWith(`${dir}/`) || file.includes(`/${dir}/`))) {
17
+ return false;
18
+ }
19
+ const base = basename(file);
20
+ if (base === "sqlx-js-env.d.ts" || base === "sqlx-js.d.ts")
21
+ return false;
22
+ if (CONFIG_FILES.has(base) || /^tsconfig(?:\.[^.]+)*\.json$/.test(base))
23
+ return true;
24
+ return EXT_RE.test(file);
25
+ }
7
26
  const DEFAULT_DEPS = { openSession, prepareOnce };
8
27
  async function closeSession(session) {
9
28
  if (!session)
@@ -15,7 +34,9 @@ async function closeSession(session) {
15
34
  }
16
35
  export async function prepareWatchedOnce(opts, state, log, err, deps = DEFAULT_DEPS) {
17
36
  const hookResult = await opts.beforePrepare?.();
18
- if (hookResult?.resetSession === true) {
37
+ const currentConfig = await loadConfig(opts.root);
38
+ if (hookResult?.resetSession === true ||
39
+ (state.session !== null && configHash(state.session.userCfg) !== configHash(currentConfig))) {
19
40
  await closeSession(state.session);
20
41
  state.session = null;
21
42
  }
@@ -70,14 +91,7 @@ export async function runWatch(opts) {
70
91
  const watcher = fsWatch(opts.root, { recursive: true }, (_event, filename) => {
71
92
  if (!filename)
72
93
  return;
73
- const raw = filename.toString();
74
- const f = raw.replace(/\\/g, "/");
75
- if (SKIP_DIRS.some((d) => f === d || f.startsWith(`${d}/`) || f.includes(`/${d}/`)))
76
- return;
77
- const base = basename(f);
78
- if (base === "sqlx-js-env.d.ts" || base === "sqlx-js.d.ts")
79
- return;
80
- if (!EXT_RE.test(f))
94
+ if (!shouldWatchFile(filename.toString()))
81
95
  return;
82
96
  trigger();
83
97
  });
@@ -1,6 +1,11 @@
1
+ export type ScanConfig = {
2
+ include?: string[];
3
+ exclude?: string[];
4
+ };
1
5
  export type SqlxJsConfig = {
2
6
  jsonbTypes?: Record<string, string>;
3
7
  customTypes?: Record<string, string>;
8
+ scan?: ScanConfig;
4
9
  schema?: {
5
10
  provider?: "builtin" | "pgschema";
6
11
  file?: string;
@@ -8,5 +13,21 @@ export type SqlxJsConfig = {
8
13
  command?: string;
9
14
  };
10
15
  };
16
+ export declare function defineConfig<T extends SqlxJsConfig>(config: T): T;
17
+ export declare function loadRootEnv(root: string): string | undefined;
18
+ export declare function configPath(root: string): string | undefined;
11
19
  export declare function loadConfig(root: string): Promise<SqlxJsConfig>;
20
+ export declare function prepareConfigHash(cfg: SqlxJsConfig): string;
21
+ export declare function configHash(cfg: SqlxJsConfig): string;
22
+ export declare function loadConfigInfo(root: string): Promise<{
23
+ config: SqlxJsConfig;
24
+ path?: string;
25
+ }>;
26
+ export declare function assertSupportedRuntime(): void;
27
+ export declare function runtimeVersion(): {
28
+ runtime: "node" | "bun";
29
+ version: string;
30
+ };
31
+ export declare function nativeTypeScriptEnabled(): boolean | string;
32
+ export declare function envFilePath(root: string): string;
12
33
  export declare function lookupJsonbType(cfg: SqlxJsConfig, schema: string, table: string, column: string): string | undefined;