@onreza/sqlx-js 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -13
- package/ROADMAP.md +2 -2
- package/dist/bin/sqlx-js.js +38 -16
- package/dist/src/cache.d.ts +1 -1
- package/dist/src/cache.js +1 -1
- package/dist/src/codegen.js +27 -14
- package/dist/src/commands/init.js +13 -3
- package/dist/src/commands/prepare.d.ts +8 -1
- package/dist/src/commands/prepare.js +99 -30
- package/dist/src/commands/watch.d.ts +14 -6
- package/dist/src/commands/watch.js +184 -21
- package/dist/src/function-cache.d.ts +2 -0
- package/dist/src/function-cache.js +6 -3
- package/dist/src/index.d.ts +17 -1
- package/dist/src/index.js +3 -0
- package/dist/src/pg/schema.d.ts +1 -0
- package/dist/src/pg/schema.js +42 -31
- package/dist/src/pg/wire.d.ts +1 -0
- package/dist/src/pg/wire.js +6 -0
- package/dist/src/postgres-runtime.d.ts +11 -1
- package/dist/src/postgres-runtime.js +22 -5
- package/dist/src/runtime.d.ts +5 -2
- package/dist/src/runtime.js +33 -12
- package/dist/src/scan/scanner.js +80 -13
- package/package.json +1 -1
package/dist/src/pg/schema.js
CHANGED
|
@@ -49,34 +49,38 @@ export class SchemaCache {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
async loadTableNames(names) {
|
|
52
|
-
const
|
|
52
|
+
const explicit = [];
|
|
53
|
+
const visible = [];
|
|
53
54
|
for (const n of names) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
}
|
package/dist/src/pg/wire.d.ts
CHANGED
package/dist/src/pg/wire.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/runtime.js
CHANGED
|
@@ -257,7 +257,7 @@ async function runRawQuery(client, query, params) {
|
|
|
257
257
|
const start = performance.now();
|
|
258
258
|
try {
|
|
259
259
|
const result = await client.query(query, encoded);
|
|
260
|
-
|
|
260
|
+
notifyQuery(client, {
|
|
261
261
|
query,
|
|
262
262
|
params,
|
|
263
263
|
durationMs: performance.now() - start,
|
|
@@ -267,10 +267,29 @@ async function runRawQuery(client, query, params) {
|
|
|
267
267
|
}
|
|
268
268
|
catch (e) {
|
|
269
269
|
const error = toPgError(e) ?? e;
|
|
270
|
-
|
|
270
|
+
notifyQuery(client, { query, params, durationMs: performance.now() - start, error });
|
|
271
271
|
throw error;
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
|
+
function notifyQuery(client, event) {
|
|
275
|
+
try {
|
|
276
|
+
const pending = client.onQuery?.(event);
|
|
277
|
+
if (pending)
|
|
278
|
+
void pending.catch((error) => notifyQueryHookError(client, error, event));
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
notifyQueryHookError(client, error, event);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function notifyQueryHookError(client, error, event) {
|
|
285
|
+
try {
|
|
286
|
+
const pending = client.onQueryHookError?.(error, event);
|
|
287
|
+
if (pending)
|
|
288
|
+
void pending.catch(() => { });
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
}
|
|
292
|
+
}
|
|
274
293
|
async function runQuery(client, query, params) {
|
|
275
294
|
return renameRows(await runRawQuery(client, query, params));
|
|
276
295
|
}
|
|
@@ -298,7 +317,7 @@ async function runOptional(client, query, params) {
|
|
|
298
317
|
throw new TooManyRowsError(rows.length, "0 or 1");
|
|
299
318
|
}
|
|
300
319
|
const sqlFileCache = new Map();
|
|
301
|
-
function loadSqlFile(path, fileRoot = process.env.SQLX_JS_FILE_ROOT ?? process.cwd()) {
|
|
320
|
+
function loadSqlFile(path, fileRoot = process.env.SQLX_JS_FILE_ROOT ?? process.cwd(), reload = false) {
|
|
302
321
|
const root = resolve(fileRoot);
|
|
303
322
|
if (isAbsolute(path)) {
|
|
304
323
|
throw new Error(`sqlx-js.sql.file: path must be relative to fileRoot: ${path}`);
|
|
@@ -309,8 +328,10 @@ function loadSqlFile(path, fileRoot = process.env.SQLX_JS_FILE_ROOT ?? process.c
|
|
|
309
328
|
throw new Error(`sqlx-js.sql.file: path escapes fileRoot: ${path}`);
|
|
310
329
|
}
|
|
311
330
|
try {
|
|
312
|
-
const st = statSync(full);
|
|
313
331
|
const cached = sqlFileCache.get(full);
|
|
332
|
+
if (cached && !reload)
|
|
333
|
+
return cached.content;
|
|
334
|
+
const st = statSync(full);
|
|
314
335
|
if (cached && cached.mtimeMs === st.mtimeMs && cached.size === st.size) {
|
|
315
336
|
return cached.content;
|
|
316
337
|
}
|
|
@@ -442,16 +463,16 @@ function makeBoundCallable(client) {
|
|
|
442
463
|
return runQuery(client, query, params);
|
|
443
464
|
});
|
|
444
465
|
const file = (async (path, ...params) => {
|
|
445
|
-
return runQuery(client, loadSqlFile(path, client.fileRoot), params);
|
|
466
|
+
return runQuery(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
446
467
|
});
|
|
447
468
|
file.one = (async (path, ...params) => {
|
|
448
|
-
return runOne(client, loadSqlFile(path, client.fileRoot), params);
|
|
469
|
+
return runOne(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
449
470
|
});
|
|
450
471
|
file.optional = (async (path, ...params) => {
|
|
451
|
-
return runOptional(client, loadSqlFile(path, client.fileRoot), params);
|
|
472
|
+
return runOptional(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
452
473
|
});
|
|
453
474
|
file.execute = (async (path, ...params) => {
|
|
454
|
-
return runExecute(client, loadSqlFile(path, client.fileRoot), params);
|
|
475
|
+
return runExecute(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
455
476
|
});
|
|
456
477
|
fn.file = file;
|
|
457
478
|
fn.one = (async (query, ...params) => {
|
|
@@ -486,19 +507,19 @@ export function createSqlRuntime(getClient) {
|
|
|
486
507
|
});
|
|
487
508
|
const rootFile = (async (path, ...params) => {
|
|
488
509
|
const client = getClient();
|
|
489
|
-
return runQuery(client, loadSqlFile(path, client.fileRoot), params);
|
|
510
|
+
return runQuery(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
490
511
|
});
|
|
491
512
|
rootFile.one = (async (path, ...params) => {
|
|
492
513
|
const client = getClient();
|
|
493
|
-
return runOne(client, loadSqlFile(path, client.fileRoot), params);
|
|
514
|
+
return runOne(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
494
515
|
});
|
|
495
516
|
rootFile.optional = (async (path, ...params) => {
|
|
496
517
|
const client = getClient();
|
|
497
|
-
return runOptional(client, loadSqlFile(path, client.fileRoot), params);
|
|
518
|
+
return runOptional(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
498
519
|
});
|
|
499
520
|
rootFile.execute = (async (path, ...params) => {
|
|
500
521
|
const client = getClient();
|
|
501
|
-
return runExecute(client, loadSqlFile(path, client.fileRoot), params);
|
|
522
|
+
return runExecute(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
502
523
|
});
|
|
503
524
|
root.file = rootFile;
|
|
504
525
|
root.one = (async (query, ...params) => {
|
package/dist/src/scan/scanner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
export class ScanError extends Error {
|
|
5
5
|
file;
|
|
6
6
|
line;
|
|
@@ -76,7 +76,7 @@ function classifyCallee(callee, scope) {
|
|
|
76
76
|
const methodName = callee.name.text;
|
|
77
77
|
if (ts.isIdentifier(callee.expression)) {
|
|
78
78
|
const id = callee.expression.text;
|
|
79
|
-
if (scope.namespaces.has(id)) {
|
|
79
|
+
if (scope.namespaces.has(id) || scope.clients.has(id)) {
|
|
80
80
|
if (methodName === "sql")
|
|
81
81
|
return { kind: "inline" };
|
|
82
82
|
return null;
|
|
@@ -95,8 +95,9 @@ function classifyCallee(callee, scope) {
|
|
|
95
95
|
const mid = callee.expression;
|
|
96
96
|
if (!ts.isIdentifier(mid.name))
|
|
97
97
|
return null;
|
|
98
|
-
// ns.sql.X(...) chains
|
|
99
|
-
if (ts.isIdentifier(mid.expression) &&
|
|
98
|
+
// ns.sql.X(...) and client.sql.X(...) chains
|
|
99
|
+
if (ts.isIdentifier(mid.expression) &&
|
|
100
|
+
(scope.namespaces.has(mid.expression.text) || scope.clients.has(mid.expression.text))) {
|
|
100
101
|
if (mid.name.text !== "sql")
|
|
101
102
|
return null;
|
|
102
103
|
if (methodName === "one" || methodName === "optional" || methodName === "execute")
|
|
@@ -118,11 +119,11 @@ function classifyCallee(callee, scope) {
|
|
|
118
119
|
return { kind: "file" };
|
|
119
120
|
return null;
|
|
120
121
|
}
|
|
121
|
-
// ns.sql.file.X(...) chains
|
|
122
|
+
// ns.sql.file.X(...) and client.sql.file.X(...) chains
|
|
122
123
|
if (ts.isPropertyAccessExpression(mid.expression) &&
|
|
123
124
|
ts.isIdentifier(mid.expression.expression) &&
|
|
124
125
|
ts.isIdentifier(mid.expression.name) &&
|
|
125
|
-
scope.namespaces.has(mid.expression.expression.text) &&
|
|
126
|
+
(scope.namespaces.has(mid.expression.expression.text) || scope.clients.has(mid.expression.expression.text)) &&
|
|
126
127
|
mid.expression.name.text === "sql" &&
|
|
127
128
|
mid.name.text === "file" &&
|
|
128
129
|
(methodName === "one" || methodName === "optional" || methodName === "execute")) {
|
|
@@ -133,9 +134,18 @@ function classifyCallee(callee, scope) {
|
|
|
133
134
|
}
|
|
134
135
|
export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
135
136
|
const text = readFileSync(absPath, "utf8");
|
|
136
|
-
const source = ts.createSourceFile(absPath, text, ts.ScriptTarget.ESNext, false,
|
|
137
|
+
const source = ts.createSourceFile(absPath, text, ts.ScriptTarget.ESNext, false, scriptKind(absPath));
|
|
138
|
+
const parseDiagnostics = source.parseDiagnostics ?? [];
|
|
139
|
+
const parseError = parseDiagnostics.find((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
140
|
+
if (parseError) {
|
|
141
|
+
const start = parseError.start ?? 0;
|
|
142
|
+
const { line, character } = source.getLineAndCharacterOfPosition(start);
|
|
143
|
+
const file = relative(root, absPath).replace(/\\/g, "/");
|
|
144
|
+
throw new ScanError(file, line + 1, character + 1, ts.flattenDiagnosticMessageText(parseError.messageText, "\n"));
|
|
145
|
+
}
|
|
137
146
|
const importedAliases = new Set();
|
|
138
147
|
const importedNamespaces = new Set();
|
|
148
|
+
const importedClientFactories = new Set();
|
|
139
149
|
for (const stmt of source.statements) {
|
|
140
150
|
if (!ts.isImportDeclaration(stmt))
|
|
141
151
|
continue;
|
|
@@ -158,17 +168,19 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
158
168
|
const orig = (elem.propertyName ?? elem.name).text;
|
|
159
169
|
if (orig === "sql")
|
|
160
170
|
importedAliases.add(elem.name.text);
|
|
171
|
+
if (orig === "createSqlClient")
|
|
172
|
+
importedClientFactories.add(elem.name.text);
|
|
161
173
|
}
|
|
162
174
|
}
|
|
163
175
|
}
|
|
164
|
-
if (importedAliases.size === 0 && importedNamespaces.size === 0)
|
|
176
|
+
if (importedAliases.size === 0 && importedNamespaces.size === 0 && importedClientFactories.size === 0)
|
|
165
177
|
return [];
|
|
166
178
|
const out = [];
|
|
167
179
|
const here = (node) => {
|
|
168
180
|
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
169
181
|
return { line: line + 1, column: character + 1 };
|
|
170
182
|
};
|
|
171
|
-
const fileRel = relative(root, absPath);
|
|
183
|
+
const fileRel = relative(root, absPath).replace(/\\/g, "/");
|
|
172
184
|
const recordInline = (first, args) => {
|
|
173
185
|
if (!ts.isStringLiteralLike(first)) {
|
|
174
186
|
const pos = here(first);
|
|
@@ -233,6 +245,8 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
233
245
|
let changed = false;
|
|
234
246
|
const nextSql = new Set(scope.sqlAliases);
|
|
235
247
|
const nextNs = new Set(scope.namespaces);
|
|
248
|
+
const nextFactories = new Set(scope.clientFactories);
|
|
249
|
+
const nextClients = new Set(scope.clients);
|
|
236
250
|
for (const binding of bindings) {
|
|
237
251
|
for (const a of scope.sqlAliases) {
|
|
238
252
|
if (bindingDeclares(binding, a)) {
|
|
@@ -246,8 +260,44 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
246
260
|
changed = true;
|
|
247
261
|
}
|
|
248
262
|
}
|
|
263
|
+
for (const a of scope.clientFactories) {
|
|
264
|
+
if (bindingDeclares(binding, a)) {
|
|
265
|
+
nextFactories.delete(a);
|
|
266
|
+
changed = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const a of scope.clients) {
|
|
270
|
+
if (bindingDeclares(binding, a)) {
|
|
271
|
+
nextClients.delete(a);
|
|
272
|
+
changed = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return changed
|
|
277
|
+
? { sqlAliases: nextSql, namespaces: nextNs, clientFactories: nextFactories, clients: nextClients }
|
|
278
|
+
: scope;
|
|
279
|
+
};
|
|
280
|
+
const isClientFactoryCall = (initializer, scope) => {
|
|
281
|
+
if (!initializer || !ts.isCallExpression(initializer))
|
|
282
|
+
return false;
|
|
283
|
+
const callee = initializer.expression;
|
|
284
|
+
if (ts.isIdentifier(callee))
|
|
285
|
+
return scope.clientFactories.has(callee.text);
|
|
286
|
+
return ts.isPropertyAccessExpression(callee) &&
|
|
287
|
+
ts.isIdentifier(callee.expression) &&
|
|
288
|
+
scope.namespaces.has(callee.expression.text) &&
|
|
289
|
+
callee.name.text === "createSqlClient";
|
|
290
|
+
};
|
|
291
|
+
const scopeWithClientDeclarations = (scope, declarations) => {
|
|
292
|
+
const nextClients = new Set(scope.clients);
|
|
293
|
+
let changed = false;
|
|
294
|
+
for (const declaration of declarations) {
|
|
295
|
+
if (!ts.isIdentifier(declaration.name) || !isClientFactoryCall(declaration.initializer, scope))
|
|
296
|
+
continue;
|
|
297
|
+
nextClients.add(declaration.name.text);
|
|
298
|
+
changed = true;
|
|
249
299
|
}
|
|
250
|
-
return changed ? {
|
|
300
|
+
return changed ? { ...scope, clients: nextClients } : scope;
|
|
251
301
|
};
|
|
252
302
|
const visit = (node, scope) => {
|
|
253
303
|
if (ts.isCallExpression(node)) {
|
|
@@ -262,7 +312,7 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
262
312
|
if (param && ts.isIdentifier(param.name)) {
|
|
263
313
|
innerSql.add(param.name.text);
|
|
264
314
|
}
|
|
265
|
-
visit(fn.body, { sqlAliases: innerSql
|
|
315
|
+
visit(fn.body, { ...shadowed, sqlAliases: innerSql });
|
|
266
316
|
return;
|
|
267
317
|
}
|
|
268
318
|
}
|
|
@@ -283,7 +333,11 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
283
333
|
const stmts = node.statements;
|
|
284
334
|
for (const stmt of stmts) {
|
|
285
335
|
if (ts.isVariableStatement(stmt)) {
|
|
286
|
-
|
|
336
|
+
const declarations = stmt.declarationList.declarations;
|
|
337
|
+
current = scopeWithoutBindingShadows(current, declarations.map((d) => d.name));
|
|
338
|
+
visit(stmt, current);
|
|
339
|
+
current = scopeWithClientDeclarations(current, declarations);
|
|
340
|
+
continue;
|
|
287
341
|
}
|
|
288
342
|
else if (ts.isFunctionDeclaration(stmt) && stmt.name) {
|
|
289
343
|
current = scopeWithoutBindingShadows(current, [stmt.name]);
|
|
@@ -320,9 +374,22 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
320
374
|
}
|
|
321
375
|
ts.forEachChild(node, (child) => visit(child, scope));
|
|
322
376
|
};
|
|
323
|
-
visit(source, {
|
|
377
|
+
visit(source, {
|
|
378
|
+
sqlAliases: importedAliases,
|
|
379
|
+
namespaces: importedNamespaces,
|
|
380
|
+
clientFactories: importedClientFactories,
|
|
381
|
+
clients: new Set(),
|
|
382
|
+
});
|
|
324
383
|
return out;
|
|
325
384
|
}
|
|
385
|
+
function scriptKind(path) {
|
|
386
|
+
switch (extname(path).toLowerCase()) {
|
|
387
|
+
case ".tsx": return ts.ScriptKind.TSX;
|
|
388
|
+
case ".mts":
|
|
389
|
+
case ".cts":
|
|
390
|
+
default: return ts.ScriptKind.TS;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
326
393
|
export function scanProject(root, scan = {}) {
|
|
327
394
|
const files = findSourceFiles(root, scan);
|
|
328
395
|
const out = [];
|