@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.
- package/README.md +69 -15
- package/ROADMAP.md +4 -3
- package/dist/bin/sqlx-js.js +118 -23
- package/dist/src/cache.d.ts +3 -2
- package/dist/src/cache.js +26 -74
- package/dist/src/codegen.js +30 -15
- package/dist/src/commands/ci.d.ts +28 -0
- package/dist/src/commands/ci.js +103 -0
- package/dist/src/commands/init.js +14 -3
- package/dist/src/commands/prepare.d.ts +8 -1
- package/dist/src/commands/prepare.js +122 -48
- 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 +41 -14
- package/dist/src/scan/scanner.js +101 -13
- package/dist/src/sql-lex.d.ts +7 -0
- package/dist/src/sql-lex.js +76 -0
- package/dist/src/sql-params.d.ts +11 -0
- package/dist/src/sql-params.js +101 -0
- package/dist/src/typed.d.ts +2 -2
- package/package.json +6 -1
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
39
|
-
|
|
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
|
|
45
|
-
|
|
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
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
+
if (opts.jsonl)
|
|
201
|
+
event("error", watchErrorData(e));
|
|
202
|
+
else
|
|
203
|
+
err(`watch: initial prepare failed — ${e.message}`);
|
|
59
204
|
}
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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")}`;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/src/pg/schema.d.ts
CHANGED
|
@@ -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;
|
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;
|