@onreza/sqlx-js 0.6.0 → 0.8.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,7 +1,9 @@
1
- import { watch as fsWatch } from "node:fs";
2
- import { basename } from "node:path";
3
- import { openSession, prepareOnce } from "./prepare.js";
1
+ import { existsSync, watch as fsWatch } from "node:fs";
2
+ import { basename, relative, resolve } from "node:path";
3
+ import { openSession, prepareOnce, PrepareFatalError, } from "./prepare.js";
4
4
  import { configHash, loadConfig } from "../config.js";
5
+ import { fingerprint } from "../cache.js";
6
+ import { findSourceFiles, scanFile, scanProject } from "../scan/scanner.js";
5
7
  const EXT_RE = /\.(ts|tsx|mts|cts|sql)$/;
6
8
  const SKIP_DIRS = ["node_modules", ".git", ".sqlx-js", "dist", "build", ".next"];
7
9
  const DEBOUNCE_MS = 150;
@@ -23,7 +25,25 @@ export function shouldWatchFile(filename) {
23
25
  return true;
24
26
  return EXT_RE.test(file);
25
27
  }
26
- const DEFAULT_DEPS = { openSession, prepareOnce };
28
+ export function formatWatchEvent(name, data = {}, timestamp = new Date().toISOString()) {
29
+ return JSON.stringify({ formatVersion: 1, event: name, timestamp, ...data });
30
+ }
31
+ export function watchErrorData(error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ if (!(error instanceof PrepareFatalError))
34
+ return { message };
35
+ return {
36
+ diagnostic: {
37
+ severity: "error",
38
+ phase: error.phase,
39
+ message,
40
+ ...(error.file === undefined ? {} : { file: error.file }),
41
+ ...(error.line === undefined ? {} : { line: error.line }),
42
+ ...(error.column === undefined ? {} : { column: error.column }),
43
+ },
44
+ };
45
+ }
46
+ const DEFAULT_DEPS = { openSession, prepareOnce, loadConfig, scanProject, scanFile, findSourceFiles };
27
47
  async function closeSession(session) {
28
48
  if (!session)
29
49
  return;
@@ -32,35 +52,164 @@ async function closeSession(session) {
32
52
  }
33
53
  catch { }
34
54
  }
35
- export async function prepareWatchedOnce(opts, state, log, err, deps = DEFAULT_DEPS) {
55
+ export async function prepareWatchedOnce(opts, state, log, err, deps = {}, changedFiles = []) {
56
+ const active = { ...DEFAULT_DEPS, ...deps };
36
57
  const hookResult = await opts.beforePrepare?.();
37
- const currentConfig = await loadConfig(opts.root);
38
- if (hookResult?.resetSession === true ||
39
- (state.session !== null && configHash(state.session.userCfg) !== configHash(currentConfig))) {
58
+ const currentConfig = await active.loadConfig(opts.root);
59
+ const configChanged = state.session !== null && configHash(state.session.userCfg) !== configHash(currentConfig);
60
+ const resetSession = hookResult?.resetSession === true || configChanged;
61
+ if (resetSession) {
40
62
  await closeSession(state.session);
41
63
  state.session = null;
42
64
  }
43
65
  if (!state.session)
44
- state.session = await deps.openSession(opts);
45
- return await deps.prepareOnce(opts, state.session, log, err, 1);
66
+ state.session = await active.openSession(opts);
67
+ const full = resetSession || !state.sitesByFile || changedFiles.some(isProjectGraphFile);
68
+ let nextSites;
69
+ let changedFps = new Set();
70
+ if (full) {
71
+ const sites = active.scanProject(opts.root, currentConfig.scan);
72
+ nextSites = groupSites(sites);
73
+ changedFps = new Set(sites.map((site) => fingerprint(site.query)));
74
+ }
75
+ else {
76
+ const previousSites = state.sitesByFile;
77
+ const dirtyFiles = state.dirtyFiles ?? new Set();
78
+ const requested = new Set([...changedFiles, ...dirtyFiles].map((file) => projectPath(opts.root, file)));
79
+ const affectedSources = new Set();
80
+ for (const changed of requested) {
81
+ if (/\.(ts|tsx|mts|cts)$/.test(changed))
82
+ affectedSources.add(changed);
83
+ if (changed.endsWith(".sql")) {
84
+ for (const [source, sites] of previousSites) {
85
+ if (sites.some((site) => site.sqlFilePath && projectPath(opts.root, site.sqlFilePath) === changed)) {
86
+ affectedSources.add(source);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ nextSites = new Map(previousSites);
92
+ const allowed = new Set(active.findSourceFiles(opts.root, currentConfig.scan).map((file) => projectPath(opts.root, file)));
93
+ for (const source of nextSites.keys()) {
94
+ if (!allowed.has(source))
95
+ affectedSources.add(source);
96
+ }
97
+ try {
98
+ for (const source of affectedSources) {
99
+ const previous = nextSites.get(source) ?? [];
100
+ for (const site of previous)
101
+ changedFps.add(fingerprint(site.query));
102
+ const absolute = resolve(opts.root, source);
103
+ if (!existsSync(absolute) || !allowed.has(source)) {
104
+ nextSites.delete(source);
105
+ continue;
106
+ }
107
+ const scanned = active.scanFile(absolute, opts.root, currentConfig.scan?.modules);
108
+ if (scanned.length > 0)
109
+ nextSites.set(source, scanned);
110
+ else
111
+ nextSites.delete(source);
112
+ for (const site of scanned)
113
+ changedFps.add(fingerprint(site.query));
114
+ }
115
+ for (const source of affectedSources)
116
+ dirtyFiles.delete(source);
117
+ }
118
+ catch (error) {
119
+ for (const source of affectedSources)
120
+ dirtyFiles.add(source);
121
+ state.dirtyFiles = dirtyFiles;
122
+ throw error;
123
+ }
124
+ state.dirtyFiles = dirtyFiles;
125
+ }
126
+ const sites = [...nextSites.values()].flat();
127
+ const dirtyFps = state.dirtyFps ?? new Set();
128
+ const reuseCacheFps = new Set(sites
129
+ .map((site) => fingerprint(site.query))
130
+ .filter((fp) => !changedFps.has(fp) && !dirtyFps.has(fp)));
131
+ const result = await active.prepareOnce(opts, state.session, log, err, 1, {
132
+ sites,
133
+ reuseCacheFps,
134
+ reuseFunctions: !full,
135
+ });
136
+ state.sitesByFile = nextSites;
137
+ if (result.failures === 0) {
138
+ state.dirtyFps = new Set();
139
+ }
140
+ else {
141
+ state.dirtyFps = new Set([...dirtyFps, ...changedFps]);
142
+ }
143
+ return result;
144
+ }
145
+ function normalizePath(path) {
146
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
147
+ }
148
+ function projectPath(root, path) {
149
+ return normalizePath(relative(resolve(root), resolve(root, path)));
150
+ }
151
+ function isProjectGraphFile(path) {
152
+ const base = basename(path);
153
+ return CONFIG_FILES.has(base) || /^tsconfig(?:\.[^.]+)*\.json$/.test(base);
154
+ }
155
+ function groupSites(sites) {
156
+ const grouped = new Map();
157
+ for (const site of sites) {
158
+ const file = normalizePath(site.file);
159
+ const current = grouped.get(file) ?? [];
160
+ current.push(site);
161
+ grouped.set(file, current);
162
+ }
163
+ return grouped;
46
164
  }
47
165
  export async function runWatch(opts) {
48
166
  const stamp = () => new Date().toTimeString().slice(0, 8);
49
- const log = (m) => console.log(`[${stamp()}] ${m}`);
50
- const err = (m) => console.error(`[${stamp()}] ${m}`);
167
+ const event = (name, data = {}) => {
168
+ console.log(formatWatchEvent(name, data));
169
+ };
170
+ const log = opts.jsonl ? () => { } : (m) => console.log(`[${stamp()}] ${m}`);
171
+ const err = opts.jsonl ? () => { } : (m) => console.error(`[${stamp()}] ${m}`);
51
172
  const state = { session: null };
52
- log("watch: initial prepare");
173
+ const report = (result, durationMs) => {
174
+ if (!opts.jsonl)
175
+ return;
176
+ for (const diagnostic of result.diagnostics)
177
+ event("diagnostic", { diagnostic });
178
+ event("prepared", {
179
+ ok: result.failures === 0,
180
+ sites: result.sites,
181
+ entries: result.entries,
182
+ failures: result.failures,
183
+ pruned: result.pruned,
184
+ functions: result.functions,
185
+ ...(durationMs === undefined ? {} : { durationMs }),
186
+ });
187
+ };
188
+ if (opts.jsonl)
189
+ event("start", { root: opts.root });
190
+ else
191
+ log("watch: initial prepare");
53
192
  try {
54
193
  const r = await prepareWatchedOnce(opts, state, log, err);
55
- log(`watch: ready — ${r.entries} queries, ${r.failures} failures`);
194
+ if (opts.jsonl)
195
+ report(r);
196
+ else
197
+ log(`watch: ready — ${r.entries} queries, ${r.failures} failures`);
56
198
  }
57
199
  catch (e) {
58
- err(`watch: initial prepare failed — ${e.message}`);
200
+ if (opts.jsonl)
201
+ event("error", watchErrorData(e));
202
+ else
203
+ err(`watch: initial prepare failed — ${e.message}`);
59
204
  }
60
- log(`watch: monitoring ${opts.root}`);
205
+ if (opts.jsonl)
206
+ event("watching", { root: opts.root });
207
+ else
208
+ log(`watch: monitoring ${opts.root}`);
61
209
  let pending = false;
62
210
  let running = null;
63
211
  let timer = null;
212
+ const changedFiles = new Set();
64
213
  const trigger = () => {
65
214
  if (timer)
66
215
  clearTimeout(timer);
@@ -71,13 +220,22 @@ export async function runWatch(opts) {
71
220
  return;
72
221
  }
73
222
  const start = Date.now();
223
+ const batch = [...changedFiles];
224
+ changedFiles.clear();
74
225
  running = (async () => {
75
226
  try {
76
- const r = await prepareWatchedOnce(opts, state, log, err);
77
- log(`watch: re-prepared in ${Date.now() - start}ms (${r.entries} queries, ${r.failures} failures)`);
227
+ const r = await prepareWatchedOnce(opts, state, log, err, {}, batch);
228
+ const durationMs = Date.now() - start;
229
+ if (opts.jsonl)
230
+ report(r, durationMs);
231
+ else
232
+ log(`watch: re-prepared in ${durationMs}ms (${r.entries} queries, ${r.failures} failures)`);
78
233
  }
79
234
  catch (e) {
80
- err(`watch: prepare error — ${e.message}`);
235
+ if (opts.jsonl)
236
+ event("error", watchErrorData(e));
237
+ else
238
+ err(`watch: prepare error — ${e.message}`);
81
239
  }
82
240
  })();
83
241
  await running;
@@ -93,11 +251,16 @@ export async function runWatch(opts) {
93
251
  return;
94
252
  if (!shouldWatchFile(filename.toString()))
95
253
  return;
254
+ changedFiles.add(normalizePath(filename.toString()));
96
255
  trigger();
97
256
  });
98
257
  const shutdown = async () => {
99
- console.log();
100
- log("watch: stopping");
258
+ if (opts.jsonl)
259
+ event("stopping");
260
+ else {
261
+ console.log();
262
+ log("watch: stopping");
263
+ }
101
264
  watcher.close();
102
265
  await closeSession(state.session);
103
266
  process.exit(0);
@@ -14,5 +14,7 @@ export type FunctionEntry = {
14
14
  returns: string;
15
15
  returnsSet: boolean;
16
16
  };
17
+ export declare function functionCachePath(cacheDir: string): string;
18
+ export declare function functionCacheExists(cacheDir: string): boolean;
17
19
  export declare function readFunctionCache(cacheDir: string): FunctionEntry[];
18
20
  export declare function writeFunctionCache(cacheDir: string, functions: FunctionEntry[]): void;
@@ -1,9 +1,12 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { randomBytes } from "node:crypto";
4
- function cachePath(cacheDir) {
4
+ export function functionCachePath(cacheDir) {
5
5
  return join(cacheDir, "functions", "functions.json");
6
6
  }
7
+ export function functionCacheExists(cacheDir) {
8
+ return existsSync(functionCachePath(cacheDir));
9
+ }
7
10
  function parseFunctionCache(raw) {
8
11
  if (!raw || typeof raw !== "object")
9
12
  return [];
@@ -13,14 +16,14 @@ function parseFunctionCache(raw) {
13
16
  return obj.functions;
14
17
  }
15
18
  export function readFunctionCache(cacheDir) {
16
- const path = cachePath(cacheDir);
19
+ const path = functionCachePath(cacheDir);
17
20
  if (!existsSync(path))
18
21
  return [];
19
22
  const text = readFileSync(path, "utf8");
20
23
  return parseFunctionCache(JSON.parse(text));
21
24
  }
22
25
  export function writeFunctionCache(cacheDir, functions) {
23
- const path = cachePath(cacheDir);
26
+ const path = functionCachePath(cacheDir);
24
27
  mkdirSync(dirname(path), { recursive: true });
25
28
  const payload = { version: 1, functions };
26
29
  const tmp = `${path}.tmp-${randomBytes(4).toString("hex")}`;
@@ -23,17 +23,33 @@ export type { ScanConfig, SqlxJsConfig } from "./config.js";
23
23
  export type { SslMode, ConnConfig } from "./pg/wire.js";
24
24
  export { PgError, ConnectionLostError } from "./pg/wire.js";
25
25
  export { NoRowsError, TooManyRowsError } from "./runtime.js";
26
- export type { TransactionOptions, MigrateOptions, OnQueryEvent, OnQueryHook } from "./runtime.js";
26
+ export type { TransactionOptions, MigrateOptions, OnQueryEvent, OnQueryHook, OnQueryHookError } from "./runtime.js";
27
27
  export type { ExecuteResult, JsonParameter, PgArrayParameter } from "./runtime.js";
28
28
  export type { PostgresClient, PostgresOptions, CreateClientOptions } from "./postgres-runtime.js";
29
29
  export type TypedFile = TypedFileFor<KnownFileQueries>;
30
30
  export type TypedSql = TypedSqlFor<KnownQueries, KnownFileQueries>;
31
31
  export type Typed = TypedFor<KnownQueries, KnownFileQueries, import("./runtime.js").TransactionOptions>;
32
+ export type QueryRegistry = {
33
+ queries: object;
34
+ fileQueries: object;
35
+ };
36
+ export interface DefaultQueryRegistry {
37
+ queries: KnownQueries;
38
+ fileQueries: KnownFileQueries;
39
+ functions: KnownFunctions;
40
+ }
41
+ export type SqlClient<Registry extends QueryRegistry = DefaultQueryRegistry> = {
42
+ sql: TypedFor<Registry["queries"], Registry["fileQueries"], import("./runtime.js").TransactionOptions>;
43
+ unsafe: Unsafe;
44
+ client: import("./postgres-runtime.js").PostgresClient;
45
+ close: () => Promise<void>;
46
+ };
32
47
  export declare const sql: Typed;
33
48
  export type Unsafe = (query: string, ...params: unknown[]) => Promise<Record<string, unknown>[]>;
34
49
  export declare const unsafe: Unsafe;
35
50
  export declare const getClient: typeof rt.getClient;
36
51
  export declare const setClient: typeof rt.setClient;
37
52
  export declare const createClient: typeof rt.createClient;
53
+ export declare function createSqlClient<Registry extends QueryRegistry = DefaultQueryRegistry>(url?: string, options?: import("./postgres-runtime.js").CreateClientOptions): SqlClient<Registry>;
38
54
  export declare const close: typeof rt.close;
39
55
  export { migrate, clearSqlFileCache, encodePgArrayLiteral, id, json, array } from "./runtime.js";
package/dist/src/index.js CHANGED
@@ -7,5 +7,8 @@ export const unsafe = rt.unsafe;
7
7
  export const getClient = rt.getClient;
8
8
  export const setClient = rt.setClient;
9
9
  export const createClient = rt.createClient;
10
+ export function createSqlClient(url, options) {
11
+ return rt.createSqlClient(url, options);
12
+ }
10
13
  export const close = rt.close;
11
14
  export { migrate, clearSqlFileCache, encodePgArrayLiteral, id, json, array } from "./runtime.js";
@@ -68,6 +68,7 @@ export declare class SchemaCache {
68
68
  name: string;
69
69
  } | undefined;
70
70
  loadTableNamesByOid(oids: number[]): Promise<void>;
71
+ private recordTable;
71
72
  columnNameByAttno(tableOid: number, attno: number): string | undefined;
72
73
  loadColumnsForTables(tableOids: number[]): Promise<void>;
73
74
  columnsOf(tableOid: number): Map<string, ColumnInfo> | undefined;
@@ -49,34 +49,38 @@ export class SchemaCache {
49
49
  }
50
50
  }
51
51
  async loadTableNames(names) {
52
- const need = [];
52
+ const explicit = [];
53
+ const visible = [];
53
54
  for (const n of names) {
54
- const schema = n.schema ?? "public";
55
- const key = `${schema}.${n.name}`;
56
- if (!this.nameToOid.has(key))
57
- need.push({ schema, name: n.name });
55
+ if (n.schema) {
56
+ if (!this.nameToOid.has(`${n.schema}.${n.name}`))
57
+ explicit.push({ schema: n.schema, name: n.name });
58
+ }
59
+ else if (!this.nameToOid.has(unqualifiedKey(n.name))) {
60
+ visible.push(n.name);
61
+ }
58
62
  }
59
- if (need.length === 0)
60
- return;
61
- const where = need.map((n) => `(n.nspname = ${quote(n.schema)} AND c.relname = ${quote(n.name)})`).join(" OR ");
62
- const sql = `SELECT c.oid::int8, n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE ${where}`;
63
- const r = await this.client.simpleQueryAll(sql);
64
- for (const row of r.rows) {
65
- const oid = Number(decodeText(row[0]));
66
- const schema = decodeText(row[1]);
67
- const name = decodeText(row[2]);
68
- const k = `${schema}.${name}`;
69
- const arr = this.nameToOid.get(k) ?? [];
70
- arr.push(oid);
71
- this.nameToOid.set(k, arr);
72
- this.oidToName.set(oid, { schema, name });
63
+ if (explicit.length > 0) {
64
+ const where = explicit.map((n) => `(n.nspname = ${quote(n.schema)} AND c.relname = ${quote(n.name)})`).join(" OR ");
65
+ const sql = `SELECT c.oid::int8, n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE ${where}`;
66
+ const result = await this.client.simpleQueryAll(sql);
67
+ for (const row of result.rows)
68
+ this.recordTable(row);
69
+ }
70
+ if (visible.length > 0) {
71
+ const requested = [...new Set(visible)].map(quote).join(",");
72
+ const sql = `SELECT c.oid::int8, n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relname IN (${requested}) AND pg_table_is_visible(c.oid)`;
73
+ const result = await this.client.simpleQueryAll(sql);
74
+ for (const row of result.rows) {
75
+ this.recordTable(row, true);
76
+ }
73
77
  }
74
78
  }
75
79
  isNotNull(tableOid, attno) {
76
80
  return this.byOidNum.get(key(tableOid, attno))?.notNull;
77
81
  }
78
82
  resolveTable(schema, name) {
79
- const k = `${schema ?? "public"}.${name}`;
83
+ const k = schema ? `${schema}.${name}` : unqualifiedKey(name);
80
84
  const arr = this.nameToOid.get(k);
81
85
  return arr?.[0];
82
86
  }
@@ -89,17 +93,21 @@ export class SchemaCache {
89
93
  return;
90
94
  const sql = `SELECT c.oid::int8, n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid IN (${need.join(",")})`;
91
95
  const r = await this.client.simpleQueryAll(sql);
92
- for (const row of r.rows) {
93
- const oid = Number(decodeText(row[0]));
94
- const schema = decodeText(row[1]);
95
- const name = decodeText(row[2]);
96
- const k = `${schema}.${name}`;
97
- const arr = this.nameToOid.get(k) ?? [];
98
- if (!arr.includes(oid))
99
- arr.push(oid);
100
- this.nameToOid.set(k, arr);
101
- this.oidToName.set(oid, { schema, name });
102
- }
96
+ for (const row of r.rows)
97
+ this.recordTable(row);
98
+ }
99
+ recordTable(row, visible = false) {
100
+ const oid = Number(decodeText(row[0]));
101
+ const schema = decodeText(row[1]);
102
+ const name = decodeText(row[2]);
103
+ const key = `${schema}.${name}`;
104
+ const arr = this.nameToOid.get(key) ?? [];
105
+ if (!arr.includes(oid))
106
+ arr.push(oid);
107
+ this.nameToOid.set(key, arr);
108
+ if (visible)
109
+ this.nameToOid.set(unqualifiedKey(name), [oid]);
110
+ this.oidToName.set(oid, { schema, name });
103
111
  }
104
112
  columnNameByAttno(tableOid, attno) {
105
113
  return this.byOidNum.get(key(tableOid, attno))?.name;
@@ -269,6 +277,9 @@ export class SchemaCache {
269
277
  function key(oid, attno) {
270
278
  return `${oid}/${attno}`;
271
279
  }
280
+ function unqualifiedKey(name) {
281
+ return `\0${name}`;
282
+ }
272
283
  function quote(s) {
273
284
  return `'${s.replace(/'/g, "''")}'`;
274
285
  }
@@ -8,6 +8,7 @@ export type ConnConfig = {
8
8
  database: string;
9
9
  sslmode?: SslMode;
10
10
  applicationName?: string;
11
+ startupOptions?: string;
11
12
  connectTimeoutMs?: number;
12
13
  statementTimeoutMs?: number;
13
14
  sslRootCert?: string;
@@ -26,6 +26,9 @@ export function parseDatabaseUrl(url) {
26
26
  const appName = params.get("application_name");
27
27
  if (appName)
28
28
  cfg.applicationName = appName;
29
+ const startupOptions = params.get("options");
30
+ if (startupOptions)
31
+ cfg.startupOptions = startupOptions;
29
32
  const ct = params.get("connect_timeout");
30
33
  if (ct) {
31
34
  const n = Number(ct);
@@ -446,6 +449,9 @@ export class PgClient {
446
449
  if (this.cfg.applicationName) {
447
450
  pairs.push(cstr("application_name"), cstr(this.cfg.applicationName));
448
451
  }
452
+ if (this.cfg.startupOptions) {
453
+ pairs.push(cstr("options"), cstr(this.cfg.startupOptions));
454
+ }
449
455
  if (this.cfg.statementTimeoutMs !== undefined) {
450
456
  pairs.push(cstr("statement_timeout"), cstr(String(this.cfg.statementTimeoutMs)));
451
457
  }
@@ -1,21 +1,31 @@
1
1
  import postgres from "postgres";
2
- import { type OnQueryHook } from "./runtime.js";
2
+ import { type OnQueryHook, type OnQueryHookError } from "./runtime.js";
3
3
  export type PostgresClient = postgres.Sql<{
4
4
  bigint: bigint;
5
5
  }>;
6
6
  export type PostgresOptions = postgres.Options<Record<string, postgres.PostgresType>>;
7
7
  export type CreateClientOptions = PostgresOptions & {
8
8
  onQuery?: OnQueryHook;
9
+ onQueryHookError?: OnQueryHookError;
9
10
  statementTimeoutMs?: number;
10
11
  fileRoot?: string;
12
+ reloadSqlFiles?: boolean;
11
13
  };
12
14
  export declare function createClient(url?: string | undefined, options?: CreateClientOptions): PostgresClient;
13
15
  export declare function getClient(): PostgresClient;
14
16
  export declare function setClient(client: PostgresClient, options?: {
15
17
  onQuery?: OnQueryHook;
18
+ onQueryHookError?: OnQueryHookError;
16
19
  prepare?: boolean;
17
20
  fileRoot?: string;
21
+ reloadSqlFiles?: boolean;
18
22
  }): void;
19
23
  export declare function close(): Promise<void>;
24
+ export declare function createSqlClient(url?: string | undefined, options?: CreateClientOptions): {
25
+ client: PostgresClient;
26
+ close: () => Promise<void>;
27
+ sql: import("./runtime.js").SqlRoot;
28
+ unsafe: (query: string, ...params: unknown[]) => Promise<Record<string, unknown>[]>;
29
+ };
20
30
  export declare const sql: import("./runtime.js").SqlRoot;
21
31
  export declare const unsafe: (query: string, ...params: unknown[]) => Promise<Record<string, unknown>[]>;
@@ -9,13 +9,17 @@ function resolvedFileRoot(value) {
9
9
  class PostgresRuntimeClient {
10
10
  client;
11
11
  onQuery;
12
+ onQueryHookError;
12
13
  prepare;
13
14
  fileRoot;
14
- constructor(client, onQuery, prepare = true, fileRoot = resolvedFileRoot()) {
15
+ reloadSqlFiles;
16
+ constructor(client, onQuery, onQueryHookError, prepare = true, fileRoot = resolvedFileRoot(), reloadSqlFiles = false) {
15
17
  this.client = client;
16
18
  this.onQuery = onQuery;
19
+ this.onQueryHookError = onQueryHookError;
17
20
  this.prepare = prepare;
18
21
  this.fileRoot = fileRoot;
22
+ this.reloadSqlFiles = reloadSqlFiles;
19
23
  }
20
24
  async query(query, params) {
21
25
  return await this.client.unsafe(query, params, { prepare: this.prepare });
@@ -39,7 +43,7 @@ class PostgresRuntimeClient {
39
43
  if (!("begin" in this.client))
40
44
  throw new Error("sqlx-js.transaction: nested transactions are not supported");
41
45
  return await this.client.begin(async (tx) => {
42
- return await fn(new PostgresRuntimeClient(tx, this.onQuery, this.prepare, this.fileRoot));
46
+ return await fn(new PostgresRuntimeClient(tx, this.onQuery, this.onQueryHookError, this.prepare, this.fileRoot, this.reloadSqlFiles));
43
47
  });
44
48
  }
45
49
  async close() {
@@ -158,7 +162,7 @@ function installJsonArrayCodecs(client) {
158
162
  export function createClient(url = process.env.DATABASE_URL, options = {}) {
159
163
  if (!url)
160
164
  throw new Error("sqlx-js: DATABASE_URL is not set");
161
- const { onQuery, statementTimeoutMs, fileRoot, ...pgOptions } = options;
165
+ const { onQuery, onQueryHookError, statementTimeoutMs, fileRoot, reloadSqlFiles, ...pgOptions } = options;
162
166
  const connection = statementTimeoutMs !== undefined
163
167
  ? { ...(pgOptions.connection ?? {}), statement_timeout: statementTimeoutMs }
164
168
  : pgOptions.connection;
@@ -169,15 +173,17 @@ export function createClient(url = process.env.DATABASE_URL, options = {}) {
169
173
  });
170
174
  client[HOOKS] = {
171
175
  onQuery,
176
+ onQueryHookError,
172
177
  prepare: pgOptions.prepare ?? true,
173
178
  fileRoot: resolvedFileRoot(fileRoot),
179
+ reloadSqlFiles: reloadSqlFiles ?? false,
174
180
  };
175
181
  return client;
176
182
  }
177
183
  function createDefaultClient() {
178
184
  const client = createClient();
179
185
  const attached = client[HOOKS];
180
- return new PostgresRuntimeClient(client, attached.onQuery, attached.prepare, attached.fileRoot);
186
+ return new PostgresRuntimeClient(client, attached.onQuery, attached.onQueryHookError, attached.prepare, attached.fileRoot, attached.reloadSqlFiles);
181
187
  }
182
188
  function getRuntimeClient() {
183
189
  defaultClient ??= createDefaultClient();
@@ -189,7 +195,7 @@ export function getClient() {
189
195
  export function setClient(client, options) {
190
196
  installJsonArrayCodecs(client);
191
197
  const attached = client[HOOKS];
192
- defaultClient = new PostgresRuntimeClient(client, options?.onQuery ?? attached?.onQuery, options?.prepare ?? attached?.prepare ?? client.options?.prepare ?? true, resolvedFileRoot(options?.fileRoot ?? attached?.fileRoot));
198
+ defaultClient = new PostgresRuntimeClient(client, options?.onQuery ?? attached?.onQuery, options?.onQueryHookError ?? attached?.onQueryHookError, options?.prepare ?? attached?.prepare ?? client.options?.prepare ?? true, resolvedFileRoot(options?.fileRoot ?? attached?.fileRoot), options?.reloadSqlFiles ?? attached?.reloadSqlFiles ?? false);
193
199
  }
194
200
  export async function close() {
195
201
  if (defaultClient) {
@@ -197,6 +203,17 @@ export async function close() {
197
203
  defaultClient = null;
198
204
  }
199
205
  }
206
+ export function createSqlClient(url = process.env.DATABASE_URL, options = {}) {
207
+ const client = createClient(url, options);
208
+ const attached = client[HOOKS];
209
+ const runtimeClient = new PostgresRuntimeClient(client, attached.onQuery, attached.onQueryHookError, attached.prepare, attached.fileRoot, attached.reloadSqlFiles);
210
+ const runtime = createSqlRuntime(() => runtimeClient);
211
+ return {
212
+ ...runtime,
213
+ client,
214
+ close: async () => runtimeClient.close(),
215
+ };
216
+ }
200
217
  const runtime = createSqlRuntime(getRuntimeClient);
201
218
  export const sql = runtime.sql;
202
219
  export const unsafe = runtime.unsafe;
@@ -6,7 +6,8 @@ export type OnQueryEvent = {
6
6
  rowCount?: number;
7
7
  error?: unknown;
8
8
  };
9
- export type OnQueryHook = (event: OnQueryEvent) => void;
9
+ export type OnQueryHook = (event: OnQueryEvent) => void | Promise<void>;
10
+ export type OnQueryHookError = (error: unknown, event: OnQueryEvent) => void | Promise<void>;
10
11
  export type RuntimeQueryResult = unknown[] & {
11
12
  count?: number | null;
12
13
  command?: string | null;
@@ -17,7 +18,9 @@ export type RuntimeClient = {
17
18
  transaction: <R>(fn: (client: RuntimeClient) => Promise<R>) => Promise<R>;
18
19
  close: () => Promise<void>;
19
20
  onQuery?: OnQueryHook;
21
+ onQueryHookError?: OnQueryHookError;
20
22
  fileRoot?: string;
23
+ reloadSqlFiles?: boolean;
21
24
  };
22
25
  type AnyFn = (...args: unknown[]) => Promise<unknown[]>;
23
26
  type AnyOneFn = (...args: unknown[]) => Promise<unknown>;
@@ -71,7 +74,7 @@ export declare const _internal: {
71
74
  parameterKind: typeof parameterKind;
72
75
  toPgError: typeof toPgError;
73
76
  };
74
- declare function loadSqlFile(path: string, fileRoot?: string): string;
77
+ declare function loadSqlFile(path: string, fileRoot?: string, reload?: boolean): string;
75
78
  export declare function clearSqlFileCache(): void;
76
79
  declare function clearIdentifierCache(): void;
77
80
  export declare function id(...parts: string[]): string;