@onreza/sqlx-js 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -14
- package/ROADMAP.md +1 -1
- package/dist/bin/sqlx-js.js +39 -2
- package/dist/src/cache.d.ts +4 -1
- package/dist/src/cache.js +12 -63
- package/dist/src/commands/init.js +8 -0
- package/dist/src/commands/prepare.d.ts +2 -0
- package/dist/src/commands/prepare.js +46 -35
- package/dist/src/commands/queries.d.ts +38 -0
- package/dist/src/commands/queries.js +143 -0
- package/dist/src/config.d.ts +5 -0
- package/dist/src/config.js +29 -0
- package/dist/src/index.d.ts +14 -6
- package/dist/src/index.js +3 -1
- package/dist/src/pg/functions.d.ts +3 -1
- package/dist/src/pg/functions.js +11 -3
- package/dist/src/postgres-runtime.d.ts +2 -0
- package/dist/src/postgres-runtime.js +9 -6
- package/dist/src/query-id.d.ts +1 -0
- package/dist/src/query-id.js +61 -0
- package/dist/src/query.d.ts +62 -0
- package/dist/src/query.js +38 -0
- package/dist/src/runtime.d.ts +27 -3
- package/dist/src/runtime.js +67 -21
- package/dist/src/scan/scanner.d.ts +2 -0
- package/dist/src/scan/scanner.js +102 -17
- package/dist/src/typed.d.ts +29 -11
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isEscapeStringPrefix, isIdentifierContinuation, readBlockComment, readDollarQuoted, readLineComment, readQuotedIdentifier, readSingleQuoted, } from "./sql-lex.js";
|
|
3
|
+
export function queryId(query) {
|
|
4
|
+
return createHash("sha256").update(normalizeQuery(query)).digest("hex").slice(0, 16);
|
|
5
|
+
}
|
|
6
|
+
function normalizeQuery(query) {
|
|
7
|
+
let out = "";
|
|
8
|
+
let pendingSpace = false;
|
|
9
|
+
let i = 0;
|
|
10
|
+
const emit = (text) => {
|
|
11
|
+
if (pendingSpace && out.length > 0)
|
|
12
|
+
out += " ";
|
|
13
|
+
out += text;
|
|
14
|
+
pendingSpace = false;
|
|
15
|
+
};
|
|
16
|
+
const markSpace = () => {
|
|
17
|
+
if (out.length > 0)
|
|
18
|
+
pendingSpace = true;
|
|
19
|
+
};
|
|
20
|
+
while (i < query.length) {
|
|
21
|
+
const ch = query[i];
|
|
22
|
+
if (/\s/.test(ch)) {
|
|
23
|
+
markSpace();
|
|
24
|
+
i++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (ch === "-" && query[i + 1] === "-") {
|
|
28
|
+
i = readLineComment(query, i);
|
|
29
|
+
markSpace();
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (ch === "/" && query[i + 1] === "*") {
|
|
33
|
+
i = readBlockComment(query, i);
|
|
34
|
+
markSpace();
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (ch === "'") {
|
|
38
|
+
const next = readSingleQuoted(query, i, isEscapeStringPrefix(query, i));
|
|
39
|
+
emit(query.slice(i, next));
|
|
40
|
+
i = next;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ch === "\"") {
|
|
44
|
+
const next = readQuotedIdentifier(query, i);
|
|
45
|
+
emit(query.slice(i, next));
|
|
46
|
+
i = next;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (ch === "$") {
|
|
50
|
+
const next = i === 0 || !isIdentifierContinuation(query[i - 1]) ? readDollarQuoted(query, i) : null;
|
|
51
|
+
if (next !== null) {
|
|
52
|
+
emit(query.slice(i, next));
|
|
53
|
+
i = next;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
emit(ch);
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ExecuteResult } from "./runtime.js";
|
|
2
|
+
import type { TypedSqlForRegistry } from "./typed.js";
|
|
3
|
+
export type QueryExecutionMode = "many" | "one" | "optional" | "execute";
|
|
4
|
+
export type QueryExecutionMetadata = {
|
|
5
|
+
queryId: string;
|
|
6
|
+
queryName?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const QUERY_EXECUTOR: unique symbol;
|
|
9
|
+
export type QueryExecutorMethod = (mode: QueryExecutionMode, query: string, params: unknown[], metadata: QueryExecutionMetadata) => Promise<unknown>;
|
|
10
|
+
type NamedQueryEntry = {
|
|
11
|
+
params: Record<string, unknown>;
|
|
12
|
+
row: unknown;
|
|
13
|
+
};
|
|
14
|
+
type PositionalQueryEntry = {
|
|
15
|
+
params: readonly unknown[];
|
|
16
|
+
row: unknown;
|
|
17
|
+
};
|
|
18
|
+
type QueryModeResult<Mode extends QueryExecutionMode, Row> = Mode extends "many" ? Row[] : Mode extends "one" ? Row : Mode extends "optional" ? Row | null : ExecuteResult;
|
|
19
|
+
export type QueryDefinition<Query extends string = string, Mode extends QueryExecutionMode = QueryExecutionMode> = {
|
|
20
|
+
readonly query: Query;
|
|
21
|
+
readonly mode: Mode;
|
|
22
|
+
readonly queryId: string;
|
|
23
|
+
readonly queryName?: string;
|
|
24
|
+
run<Registry extends {
|
|
25
|
+
queries: Record<Query, NamedQueryEntry>;
|
|
26
|
+
fileQueries: object;
|
|
27
|
+
}>(executor: TypedSqlForRegistry<Registry>, params: RegistryParams<Query, Registry>): Promise<QueryResultFor<QueryDefinition<Query, Mode>, Registry>>;
|
|
28
|
+
run<Registry extends {
|
|
29
|
+
queries: Record<Query, PositionalQueryEntry>;
|
|
30
|
+
fileQueries: object;
|
|
31
|
+
}>(executor: TypedSqlForRegistry<Registry>, ...params: RegistryParams<Query, Registry> & readonly unknown[]): Promise<QueryResultFor<QueryDefinition<Query, Mode>, Registry>>;
|
|
32
|
+
};
|
|
33
|
+
type DefinitionQuery<Definition> = Definition extends QueryDefinition<infer Query, QueryExecutionMode> ? Query : never;
|
|
34
|
+
type DefinitionMode<Definition> = Definition extends QueryDefinition<string, infer Mode> ? Mode : never;
|
|
35
|
+
type RegistryQuery<Query extends string, Registry extends {
|
|
36
|
+
queries: object;
|
|
37
|
+
}> = Registry["queries"][Query & keyof Registry["queries"]];
|
|
38
|
+
type RegistryParams<Query extends string, Registry extends {
|
|
39
|
+
queries: object;
|
|
40
|
+
}> = RegistryQuery<Query, Registry>["params" & keyof RegistryQuery<Query, Registry>];
|
|
41
|
+
type RegistryRow<Query extends string, Registry extends {
|
|
42
|
+
queries: object;
|
|
43
|
+
}> = RegistryQuery<Query, Registry>["row" & keyof RegistryQuery<Query, Registry>];
|
|
44
|
+
export type QueryParamsFor<Definition, Registry extends {
|
|
45
|
+
queries: object;
|
|
46
|
+
}> = RegistryParams<DefinitionQuery<Definition>, Registry>;
|
|
47
|
+
export type QueryRowFor<Definition, Registry extends {
|
|
48
|
+
queries: object;
|
|
49
|
+
}> = RegistryRow<DefinitionQuery<Definition>, Registry>;
|
|
50
|
+
export type QueryResultFor<Definition, Registry extends {
|
|
51
|
+
queries: object;
|
|
52
|
+
}> = QueryModeResult<DefinitionMode<Definition>, QueryRowFor<Definition, Registry>>;
|
|
53
|
+
type DefineQueryMethod<Mode extends QueryExecutionMode> = {
|
|
54
|
+
<const Query extends string>(query: Query): QueryDefinition<Query, Mode>;
|
|
55
|
+
<const Query extends string>(name: string, query: Query): QueryDefinition<Query, Mode>;
|
|
56
|
+
};
|
|
57
|
+
export declare const defineQuery: DefineQueryMethod<"many"> & {
|
|
58
|
+
one: DefineQueryMethod<"one">;
|
|
59
|
+
optional: DefineQueryMethod<"optional">;
|
|
60
|
+
execute: DefineQueryMethod<"execute">;
|
|
61
|
+
};
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { queryId } from "./query-id.js";
|
|
2
|
+
export const QUERY_EXECUTOR = Symbol.for("@onreza/sqlx-js.query-executor");
|
|
3
|
+
function definitionMethod(mode) {
|
|
4
|
+
return ((nameOrQuery, maybeQuery) => {
|
|
5
|
+
const query = maybeQuery ?? nameOrQuery;
|
|
6
|
+
const name = maybeQuery === undefined ? undefined : nameOrQuery;
|
|
7
|
+
if (name !== undefined && name.trim() === "") {
|
|
8
|
+
throw new Error("sqlx-js.defineQuery: query name must not be empty");
|
|
9
|
+
}
|
|
10
|
+
const metadata = {
|
|
11
|
+
queryId: queryId(query),
|
|
12
|
+
...(name ? { queryName: name } : {}),
|
|
13
|
+
};
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
query,
|
|
16
|
+
mode,
|
|
17
|
+
queryId: metadata.queryId,
|
|
18
|
+
...(name ? { queryName: name } : {}),
|
|
19
|
+
async run(executor, ...params) {
|
|
20
|
+
const execute = executor[QUERY_EXECUTOR];
|
|
21
|
+
if (execute)
|
|
22
|
+
return await execute(mode, query, params, metadata);
|
|
23
|
+
if (mode === "one")
|
|
24
|
+
return await executor.one(query, ...params);
|
|
25
|
+
if (mode === "optional")
|
|
26
|
+
return await executor.optional(query, ...params);
|
|
27
|
+
if (mode === "execute")
|
|
28
|
+
return await executor.execute(query, ...params);
|
|
29
|
+
return await executor(query, ...params);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export const defineQuery = Object.assign(definitionMethod("many"), {
|
|
35
|
+
one: definitionMethod("one"),
|
|
36
|
+
optional: definitionMethod("optional"),
|
|
37
|
+
execute: definitionMethod("execute"),
|
|
38
|
+
});
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { PgError } from "./pg/wire.js";
|
|
2
|
+
import { QUERY_EXECUTOR, type QueryExecutorMethod } from "./query.js";
|
|
2
3
|
export type OnQueryEvent = {
|
|
4
|
+
queryId: string;
|
|
5
|
+
queryName?: string;
|
|
3
6
|
query: string;
|
|
4
7
|
params: unknown[];
|
|
5
8
|
durationMs: number;
|
|
@@ -21,6 +24,7 @@ export type RuntimeClient = {
|
|
|
21
24
|
onQueryHookError?: OnQueryHookError;
|
|
22
25
|
fileRoot?: string;
|
|
23
26
|
reloadSqlFiles?: boolean;
|
|
27
|
+
sqlFiles?: Readonly<Record<string, string>>;
|
|
24
28
|
};
|
|
25
29
|
type AnyFn = (...args: unknown[]) => Promise<unknown[]>;
|
|
26
30
|
type AnyOneFn = (...args: unknown[]) => Promise<unknown>;
|
|
@@ -28,7 +32,7 @@ type AnyOptionalFn = (...args: unknown[]) => Promise<unknown | null>;
|
|
|
28
32
|
type AnyExecuteFn = (...args: unknown[]) => Promise<ExecuteResult>;
|
|
29
33
|
type IdentifierFn = (...parts: string[]) => string;
|
|
30
34
|
declare const PARAMETER_KIND: unique symbol;
|
|
31
|
-
export type JsonParameter<T =
|
|
35
|
+
export type JsonParameter<T = unknown> = {
|
|
32
36
|
readonly [PARAMETER_KIND]: "json";
|
|
33
37
|
readonly value: T;
|
|
34
38
|
};
|
|
@@ -42,11 +46,17 @@ export type JsonInputObject = {
|
|
|
42
46
|
readonly [key: string]: JsonInputValue | undefined;
|
|
43
47
|
};
|
|
44
48
|
export type JsonInputArray = readonly JsonInputValue[];
|
|
49
|
+
type NonJsonValue = bigint | symbol | Date | Uint8Array | ((...args: never[]) => unknown);
|
|
50
|
+
export type JsonCompatible<T> = T extends JsonPrimitive ? T : T extends NonJsonValue ? never : T extends readonly unknown[] ? T extends JsonInputArray ? T : {
|
|
51
|
+
readonly [K in keyof T]: JsonCompatible<T[K]>;
|
|
52
|
+
} : T extends object ? Extract<keyof T, symbol> extends never ? T extends JsonInputObject ? T : {
|
|
53
|
+
readonly [K in keyof T]: undefined extends T[K] ? JsonCompatible<Exclude<T[K], undefined>> | undefined : JsonCompatible<T[K]>;
|
|
54
|
+
} : never : never;
|
|
45
55
|
export type ExecuteResult = {
|
|
46
56
|
rowCount: number;
|
|
47
57
|
command: string;
|
|
48
58
|
};
|
|
49
|
-
export declare function json<T
|
|
59
|
+
export declare function json<T>(value: T & JsonCompatible<T>): JsonParameter<T>;
|
|
50
60
|
export declare function array<T>(value: readonly (T | null)[]): PgArrayParameter<T>;
|
|
51
61
|
export declare function parameterKind(value: unknown): "json" | "array" | undefined;
|
|
52
62
|
declare function renameRows(rows: unknown[]): unknown[];
|
|
@@ -63,6 +73,19 @@ export declare class TooManyRowsError extends Error {
|
|
|
63
73
|
constructor(actual: number, expected?: "1" | "0 or 1");
|
|
64
74
|
}
|
|
65
75
|
export declare function toPgError(e: unknown): PgError | null;
|
|
76
|
+
export declare const SQLSTATE: {
|
|
77
|
+
readonly notNullViolation: "23502";
|
|
78
|
+
readonly foreignKeyViolation: "23503";
|
|
79
|
+
readonly uniqueViolation: "23505";
|
|
80
|
+
readonly checkViolation: "23514";
|
|
81
|
+
readonly serializationFailure: "40001";
|
|
82
|
+
readonly deadlockDetected: "40P01";
|
|
83
|
+
};
|
|
84
|
+
export type KnownSqlState = (typeof SQLSTATE)[keyof typeof SQLSTATE];
|
|
85
|
+
export declare function isPgError(error: unknown): error is PgError;
|
|
86
|
+
export declare function isPgError<const Code extends string>(error: unknown, code: Code): error is PgError & {
|
|
87
|
+
readonly code: Code;
|
|
88
|
+
};
|
|
66
89
|
export declare const _internal: {
|
|
67
90
|
renameRows: typeof renameRows;
|
|
68
91
|
encodeParam: typeof encodeParam;
|
|
@@ -74,7 +97,7 @@ export declare const _internal: {
|
|
|
74
97
|
parameterKind: typeof parameterKind;
|
|
75
98
|
toPgError: typeof toPgError;
|
|
76
99
|
};
|
|
77
|
-
declare function loadSqlFile(path: string, fileRoot?: string, reload?: boolean): string;
|
|
100
|
+
declare function loadSqlFile(path: string, fileRoot?: string, reload?: boolean, embedded?: Readonly<Record<string, string>>): string;
|
|
78
101
|
export declare function clearSqlFileCache(): void;
|
|
79
102
|
declare function clearIdentifierCache(): void;
|
|
80
103
|
export declare function id(...parts: string[]): string;
|
|
@@ -91,6 +114,7 @@ type SqlCallable = AnyFn & {
|
|
|
91
114
|
id: IdentifierFn;
|
|
92
115
|
json: typeof json;
|
|
93
116
|
array: typeof array;
|
|
117
|
+
[QUERY_EXECUTOR]: QueryExecutorMethod;
|
|
94
118
|
};
|
|
95
119
|
export type TransactionOptions = {
|
|
96
120
|
isolation?: "read uncommitted" | "read committed" | "repeatable read" | "serializable";
|
package/dist/src/runtime.js
CHANGED
|
@@ -3,6 +3,8 @@ import { isAbsolute, relative, resolve } from "node:path";
|
|
|
3
3
|
import { PgClient, parseDatabaseUrl, PgError } from "./pg/wire.js";
|
|
4
4
|
import { applyPending, acquireMigrateLock, releaseMigrateLock, DEFAULT_MIGRATE_LOCK_KEY } from "./migration-core.js";
|
|
5
5
|
import { bindNamedParameters, rewriteNamedParameters } from "./sql-params.js";
|
|
6
|
+
import { queryId } from "./query-id.js";
|
|
7
|
+
import { QUERY_EXECUTOR, } from "./query.js";
|
|
6
8
|
const PARAMETER_KIND = Symbol("sqlx-js.parameter");
|
|
7
9
|
export function json(value) {
|
|
8
10
|
return { [PARAMETER_KIND]: "json", value };
|
|
@@ -176,7 +178,7 @@ export class TooManyRowsError extends Error {
|
|
|
176
178
|
}
|
|
177
179
|
// SQLSTATE is exactly five characters from [0-9A-Z]; lowercase or other shapes
|
|
178
180
|
// are never valid, so transport codes like "EPIPE" must not match on shape alone.
|
|
179
|
-
const
|
|
181
|
+
const SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
|
|
180
182
|
function firstString(...candidates) {
|
|
181
183
|
for (const value of candidates) {
|
|
182
184
|
if (typeof value === "string" && value.length > 0)
|
|
@@ -200,7 +202,7 @@ export function toPgError(e) {
|
|
|
200
202
|
// and system errors (EPIPE, ECONNREFUSED, CONNECTION_ENDED) carry neither, so
|
|
201
203
|
// they pass through untouched instead of masquerading as a PgError.
|
|
202
204
|
const isDatabaseError = o.name === "PostgresError" ||
|
|
203
|
-
(code !== undefined &&
|
|
205
|
+
(code !== undefined && SQLSTATE_PATTERN.test(code) && severity !== undefined);
|
|
204
206
|
if (!isDatabaseError)
|
|
205
207
|
return null;
|
|
206
208
|
const fields = {};
|
|
@@ -231,6 +233,17 @@ export function toPgError(e) {
|
|
|
231
233
|
fields.s = schema;
|
|
232
234
|
return new PgError(fields, { cause: e });
|
|
233
235
|
}
|
|
236
|
+
export const SQLSTATE = {
|
|
237
|
+
notNullViolation: "23502",
|
|
238
|
+
foreignKeyViolation: "23503",
|
|
239
|
+
uniqueViolation: "23505",
|
|
240
|
+
checkViolation: "23514",
|
|
241
|
+
serializationFailure: "40001",
|
|
242
|
+
deadlockDetected: "40P01",
|
|
243
|
+
};
|
|
244
|
+
export function isPgError(error, code) {
|
|
245
|
+
return error instanceof PgError && (code === undefined || error.code === code);
|
|
246
|
+
}
|
|
234
247
|
export const _internal = {
|
|
235
248
|
renameRows,
|
|
236
249
|
encodeParam,
|
|
@@ -242,7 +255,21 @@ export const _internal = {
|
|
|
242
255
|
parameterKind,
|
|
243
256
|
toPgError,
|
|
244
257
|
};
|
|
245
|
-
|
|
258
|
+
const runtimeQueryIds = new Map();
|
|
259
|
+
const MAX_RUNTIME_QUERY_IDS = 1024;
|
|
260
|
+
function observedMetadata(query, metadata) {
|
|
261
|
+
if (metadata)
|
|
262
|
+
return metadata;
|
|
263
|
+
let id = runtimeQueryIds.get(query);
|
|
264
|
+
if (!id) {
|
|
265
|
+
id = queryId(query);
|
|
266
|
+
if (runtimeQueryIds.size >= MAX_RUNTIME_QUERY_IDS)
|
|
267
|
+
runtimeQueryIds.clear();
|
|
268
|
+
runtimeQueryIds.set(query, id);
|
|
269
|
+
}
|
|
270
|
+
return { queryId: id };
|
|
271
|
+
}
|
|
272
|
+
async function runRawQuery(client, query, params, metadata) {
|
|
246
273
|
const observedQuery = query;
|
|
247
274
|
const observedParams = params;
|
|
248
275
|
const bound = bindNamedParameters(rewriteNamedParameters(query), params);
|
|
@@ -260,10 +287,12 @@ async function runRawQuery(client, query, params) {
|
|
|
260
287
|
throw toPgError(e) ?? e;
|
|
261
288
|
}
|
|
262
289
|
}
|
|
290
|
+
const observed = observedMetadata(observedQuery, metadata);
|
|
263
291
|
const start = performance.now();
|
|
264
292
|
try {
|
|
265
293
|
const result = await client.query(query, encoded);
|
|
266
294
|
notifyQuery(client, {
|
|
295
|
+
...observed,
|
|
267
296
|
query: observedQuery,
|
|
268
297
|
params: observedParams,
|
|
269
298
|
durationMs: performance.now() - start,
|
|
@@ -273,7 +302,7 @@ async function runRawQuery(client, query, params) {
|
|
|
273
302
|
}
|
|
274
303
|
catch (e) {
|
|
275
304
|
const error = toPgError(e) ?? e;
|
|
276
|
-
notifyQuery(client, { query: observedQuery, params: observedParams, durationMs: performance.now() - start, error });
|
|
305
|
+
notifyQuery(client, { ...observed, query: observedQuery, params: observedParams, durationMs: performance.now() - start, error });
|
|
277
306
|
throw error;
|
|
278
307
|
}
|
|
279
308
|
}
|
|
@@ -296,26 +325,26 @@ function notifyQueryHookError(client, error, event) {
|
|
|
296
325
|
catch {
|
|
297
326
|
}
|
|
298
327
|
}
|
|
299
|
-
async function runQuery(client, query, params) {
|
|
300
|
-
return renameRows(await runRawQuery(client, query, params));
|
|
328
|
+
async function runQuery(client, query, params, metadata) {
|
|
329
|
+
return renameRows(await runRawQuery(client, query, params, metadata));
|
|
301
330
|
}
|
|
302
|
-
async function runExecute(client, query, params) {
|
|
303
|
-
const result = await runRawQuery(client, query, params);
|
|
331
|
+
async function runExecute(client, query, params, metadata) {
|
|
332
|
+
const result = await runRawQuery(client, query, params, metadata);
|
|
304
333
|
return {
|
|
305
334
|
rowCount: result.count ?? result.length,
|
|
306
335
|
command: result.command ?? "",
|
|
307
336
|
};
|
|
308
337
|
}
|
|
309
|
-
async function runOne(client, query, params) {
|
|
310
|
-
const rows = await runQuery(client, query, params);
|
|
338
|
+
async function runOne(client, query, params, metadata) {
|
|
339
|
+
const rows = await runQuery(client, query, params, metadata);
|
|
311
340
|
if (rows.length === 1)
|
|
312
341
|
return rows[0];
|
|
313
342
|
if (rows.length === 0)
|
|
314
343
|
throw new NoRowsError();
|
|
315
344
|
throw new TooManyRowsError(rows.length, "1");
|
|
316
345
|
}
|
|
317
|
-
async function runOptional(client, query, params) {
|
|
318
|
-
const rows = await runQuery(client, query, params);
|
|
346
|
+
async function runOptional(client, query, params, metadata) {
|
|
347
|
+
const rows = await runQuery(client, query, params, metadata);
|
|
319
348
|
if (rows.length === 0)
|
|
320
349
|
return null;
|
|
321
350
|
if (rows.length === 1)
|
|
@@ -323,7 +352,7 @@ async function runOptional(client, query, params) {
|
|
|
323
352
|
throw new TooManyRowsError(rows.length, "0 or 1");
|
|
324
353
|
}
|
|
325
354
|
const sqlFileCache = new Map();
|
|
326
|
-
function loadSqlFile(path, fileRoot = process.env.SQLX_JS_FILE_ROOT ?? process.cwd(), reload = false) {
|
|
355
|
+
function loadSqlFile(path, fileRoot = process.env.SQLX_JS_FILE_ROOT ?? process.cwd(), reload = false, embedded) {
|
|
327
356
|
const root = resolve(fileRoot);
|
|
328
357
|
if (isAbsolute(path)) {
|
|
329
358
|
throw new Error(`sqlx-js.sql.file: path must be relative to fileRoot: ${path}`);
|
|
@@ -333,6 +362,8 @@ function loadSqlFile(path, fileRoot = process.env.SQLX_JS_FILE_ROOT ?? process.c
|
|
|
333
362
|
if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(rel)) {
|
|
334
363
|
throw new Error(`sqlx-js.sql.file: path escapes fileRoot: ${path}`);
|
|
335
364
|
}
|
|
365
|
+
if (embedded && Object.hasOwn(embedded, path))
|
|
366
|
+
return embedded[path];
|
|
336
367
|
try {
|
|
337
368
|
const cached = sqlFileCache.get(full);
|
|
338
369
|
if (cached && !reload)
|
|
@@ -464,21 +495,30 @@ export function id(...parts) {
|
|
|
464
495
|
}
|
|
465
496
|
return parts.map(quoteIdentifier).join(".");
|
|
466
497
|
}
|
|
498
|
+
function executeDefinedQuery(client, mode, query, params, metadata) {
|
|
499
|
+
if (mode === "one")
|
|
500
|
+
return runOne(client, query, params, metadata);
|
|
501
|
+
if (mode === "optional")
|
|
502
|
+
return runOptional(client, query, params, metadata);
|
|
503
|
+
if (mode === "execute")
|
|
504
|
+
return runExecute(client, query, params, metadata);
|
|
505
|
+
return runQuery(client, query, params, metadata);
|
|
506
|
+
}
|
|
467
507
|
function makeBoundCallable(client) {
|
|
468
508
|
const fn = (async (query, ...params) => {
|
|
469
509
|
return runQuery(client, query, params);
|
|
470
510
|
});
|
|
471
511
|
const file = (async (path, ...params) => {
|
|
472
|
-
return runQuery(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
512
|
+
return runQuery(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
473
513
|
});
|
|
474
514
|
file.one = (async (path, ...params) => {
|
|
475
|
-
return runOne(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
515
|
+
return runOne(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
476
516
|
});
|
|
477
517
|
file.optional = (async (path, ...params) => {
|
|
478
|
-
return runOptional(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
518
|
+
return runOptional(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
479
519
|
});
|
|
480
520
|
file.execute = (async (path, ...params) => {
|
|
481
|
-
return runExecute(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
521
|
+
return runExecute(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
482
522
|
});
|
|
483
523
|
fn.file = file;
|
|
484
524
|
fn.one = (async (query, ...params) => {
|
|
@@ -493,6 +533,9 @@ function makeBoundCallable(client) {
|
|
|
493
533
|
fn.id = id;
|
|
494
534
|
fn.json = json;
|
|
495
535
|
fn.array = array;
|
|
536
|
+
fn[QUERY_EXECUTOR] = (mode, query, params, metadata) => {
|
|
537
|
+
return executeDefinedQuery(client, mode, query, params, metadata);
|
|
538
|
+
};
|
|
496
539
|
return fn;
|
|
497
540
|
}
|
|
498
541
|
function buildSetTransaction(opts) {
|
|
@@ -513,19 +556,19 @@ export function createSqlRuntime(getClient) {
|
|
|
513
556
|
});
|
|
514
557
|
const rootFile = (async (path, ...params) => {
|
|
515
558
|
const client = getClient();
|
|
516
|
-
return runQuery(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
559
|
+
return runQuery(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
517
560
|
});
|
|
518
561
|
rootFile.one = (async (path, ...params) => {
|
|
519
562
|
const client = getClient();
|
|
520
|
-
return runOne(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
563
|
+
return runOne(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
521
564
|
});
|
|
522
565
|
rootFile.optional = (async (path, ...params) => {
|
|
523
566
|
const client = getClient();
|
|
524
|
-
return runOptional(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
567
|
+
return runOptional(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
525
568
|
});
|
|
526
569
|
rootFile.execute = (async (path, ...params) => {
|
|
527
570
|
const client = getClient();
|
|
528
|
-
return runExecute(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles), params);
|
|
571
|
+
return runExecute(client, loadSqlFile(path, client.fileRoot, client.reloadSqlFiles, client.sqlFiles), params);
|
|
529
572
|
});
|
|
530
573
|
root.file = rootFile;
|
|
531
574
|
root.one = (async (query, ...params) => {
|
|
@@ -540,6 +583,9 @@ export function createSqlRuntime(getClient) {
|
|
|
540
583
|
root.id = id;
|
|
541
584
|
root.json = json;
|
|
542
585
|
root.array = array;
|
|
586
|
+
root[QUERY_EXECUTOR] = (mode, query, params, metadata) => {
|
|
587
|
+
return executeDefinedQuery(getClient(), mode, query, params, metadata);
|
|
588
|
+
};
|
|
543
589
|
root.transaction = (async (fnOrOpts, maybeFn) => {
|
|
544
590
|
const c = getClient();
|
|
545
591
|
let opts = {};
|