@onreza/sqlx-js 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -36
- package/ROADMAP.md +3 -4
- package/dist/bin/sqlx-js.js +137 -26
- package/dist/src/artifacts.d.ts +9 -0
- package/dist/src/artifacts.js +26 -0
- package/dist/src/cache.d.ts +17 -0
- package/dist/src/cache.js +83 -1
- package/dist/src/codegen.d.ts +2 -1
- package/dist/src/codegen.js +34 -5
- package/dist/src/commands/doctor.d.ts +16 -0
- package/dist/src/commands/doctor.js +196 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +36 -9
- package/dist/src/commands/migrate.js +7 -22
- package/dist/src/commands/pgschema.d.ts +31 -0
- package/dist/src/commands/pgschema.js +208 -0
- package/dist/src/commands/prepare.d.ts +40 -5
- package/dist/src/commands/prepare.js +344 -58
- package/dist/src/commands/watch.d.ts +1 -0
- package/dist/src/commands/watch.js +23 -9
- package/dist/src/config.d.ts +27 -0
- package/dist/src/config.js +141 -11
- package/dist/src/function-cache.d.ts +18 -0
- package/dist/src/function-cache.js +38 -0
- package/dist/src/index.d.ts +6 -2
- package/dist/src/index.js +2 -1
- package/dist/src/pg/functions.d.ts +4 -0
- package/dist/src/pg/functions.js +150 -0
- package/dist/src/pg/oids.js +2 -0
- package/dist/src/pg/schema.d.ts +1 -0
- package/dist/src/pg/schema.js +3 -0
- package/dist/src/postgres-runtime.d.ts +3 -0
- package/dist/src/postgres-runtime.js +66 -29
- package/dist/src/runtime.d.ts +36 -3
- package/dist/src/runtime.js +91 -22
- package/dist/src/scan/scanner.d.ts +9 -2
- package/dist/src/scan/scanner.js +79 -28
- package/dist/src/typed.d.ts +10 -0
- package/package.json +6 -5
package/dist/src/runtime.d.ts
CHANGED
|
@@ -7,19 +7,47 @@ export type OnQueryEvent = {
|
|
|
7
7
|
error?: unknown;
|
|
8
8
|
};
|
|
9
9
|
export type OnQueryHook = (event: OnQueryEvent) => void;
|
|
10
|
+
export type RuntimeQueryResult = unknown[] & {
|
|
11
|
+
count?: number | null;
|
|
12
|
+
command?: string | null;
|
|
13
|
+
};
|
|
10
14
|
export type RuntimeClient = {
|
|
11
|
-
query: (query: string, params: unknown[]) => Promise<
|
|
15
|
+
query: (query: string, params: unknown[]) => Promise<RuntimeQueryResult>;
|
|
12
16
|
transformParam?: (param: unknown) => unknown;
|
|
13
17
|
transaction: <R>(fn: (client: RuntimeClient) => Promise<R>) => Promise<R>;
|
|
14
18
|
close: () => Promise<void>;
|
|
15
19
|
onQuery?: OnQueryHook;
|
|
20
|
+
fileRoot?: string;
|
|
16
21
|
};
|
|
17
22
|
type AnyFn = (...args: unknown[]) => Promise<unknown[]>;
|
|
18
23
|
type AnyOneFn = (...args: unknown[]) => Promise<unknown>;
|
|
19
24
|
type AnyOptionalFn = (...args: unknown[]) => Promise<unknown | null>;
|
|
25
|
+
type AnyExecuteFn = (...args: unknown[]) => Promise<ExecuteResult>;
|
|
20
26
|
type IdentifierFn = (...parts: string[]) => string;
|
|
27
|
+
declare const PARAMETER_KIND: unique symbol;
|
|
28
|
+
export type JsonParameter<T = JsonInputValue> = {
|
|
29
|
+
readonly [PARAMETER_KIND]: "json";
|
|
30
|
+
readonly value: T;
|
|
31
|
+
};
|
|
32
|
+
export type PgArrayParameter<T = unknown> = {
|
|
33
|
+
readonly [PARAMETER_KIND]: "array";
|
|
34
|
+
readonly value: readonly (T | null)[];
|
|
35
|
+
};
|
|
36
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
37
|
+
export type JsonInputValue = JsonPrimitive | JsonInputObject | JsonInputArray;
|
|
38
|
+
export type JsonInputObject = {
|
|
39
|
+
readonly [key: string]: JsonInputValue | undefined;
|
|
40
|
+
};
|
|
41
|
+
export type JsonInputArray = readonly JsonInputValue[];
|
|
42
|
+
export type ExecuteResult = {
|
|
43
|
+
rowCount: number;
|
|
44
|
+
command: string;
|
|
45
|
+
};
|
|
46
|
+
export declare function json<T extends JsonInputValue>(value: T): JsonParameter<T>;
|
|
47
|
+
export declare function array<T>(value: readonly (T | null)[]): PgArrayParameter<T>;
|
|
48
|
+
export declare function parameterKind(value: unknown): "json" | "array" | undefined;
|
|
21
49
|
declare function renameRows(rows: unknown[]): unknown[];
|
|
22
|
-
declare function isPrimitiveArrayElement(v: unknown): boolean;
|
|
50
|
+
export declare function isPrimitiveArrayElement(v: unknown): boolean;
|
|
23
51
|
export declare function encodePgArrayLiteral(arr: unknown[]): string;
|
|
24
52
|
type PgArrayValue<T> = T | null | PgArrayValue<T>[];
|
|
25
53
|
export declare function parsePgArrayLiteral<T = string>(input: string, parseElement?: (value: string) => T): PgArrayValue<T>[];
|
|
@@ -40,21 +68,26 @@ export declare const _internal: {
|
|
|
40
68
|
loadSqlFile: typeof loadSqlFile;
|
|
41
69
|
buildSetTransaction: typeof buildSetTransaction;
|
|
42
70
|
clearIdentifierCache: typeof clearIdentifierCache;
|
|
71
|
+
parameterKind: typeof parameterKind;
|
|
43
72
|
toPgError: typeof toPgError;
|
|
44
73
|
};
|
|
45
|
-
declare function loadSqlFile(path: string): string;
|
|
74
|
+
declare function loadSqlFile(path: string, fileRoot?: string): string;
|
|
46
75
|
export declare function clearSqlFileCache(): void;
|
|
47
76
|
declare function clearIdentifierCache(): void;
|
|
48
77
|
export declare function id(...parts: string[]): string;
|
|
49
78
|
type FileCallable = AnyFn & {
|
|
50
79
|
one: AnyOneFn;
|
|
51
80
|
optional: AnyOptionalFn;
|
|
81
|
+
execute: AnyExecuteFn;
|
|
52
82
|
};
|
|
53
83
|
type SqlCallable = AnyFn & {
|
|
54
84
|
file: FileCallable;
|
|
55
85
|
one: AnyOneFn;
|
|
56
86
|
optional: AnyOptionalFn;
|
|
87
|
+
execute: AnyExecuteFn;
|
|
57
88
|
id: IdentifierFn;
|
|
89
|
+
json: typeof json;
|
|
90
|
+
array: typeof array;
|
|
58
91
|
};
|
|
59
92
|
export type TransactionOptions = {
|
|
60
93
|
isolation?: "read uncommitted" | "read committed" | "repeatable read" | "serializable";
|
package/dist/src/runtime.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
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 "./commands/migrate.js";
|
|
5
|
+
const PARAMETER_KIND = Symbol("sqlx-js.parameter");
|
|
6
|
+
export function json(value) {
|
|
7
|
+
return { [PARAMETER_KIND]: "json", value };
|
|
8
|
+
}
|
|
9
|
+
export function array(value) {
|
|
10
|
+
return { [PARAMETER_KIND]: "array", value };
|
|
11
|
+
}
|
|
12
|
+
export function parameterKind(value) {
|
|
13
|
+
if (!value || typeof value !== "object")
|
|
14
|
+
return undefined;
|
|
15
|
+
return value[PARAMETER_KIND];
|
|
16
|
+
}
|
|
5
17
|
const SUFFIX = /[!?]$/;
|
|
6
18
|
function renameRows(rows) {
|
|
7
19
|
if (rows.length === 0)
|
|
@@ -28,9 +40,11 @@ function renameRows(rows) {
|
|
|
28
40
|
}
|
|
29
41
|
return out;
|
|
30
42
|
}
|
|
31
|
-
function isPrimitiveArrayElement(v) {
|
|
43
|
+
export function isPrimitiveArrayElement(v) {
|
|
32
44
|
if (v === null || v === undefined)
|
|
33
45
|
return true;
|
|
46
|
+
if (v instanceof Date || v instanceof Uint8Array)
|
|
47
|
+
return true;
|
|
34
48
|
const t = typeof v;
|
|
35
49
|
return t === "string" || t === "number" || t === "bigint" || t === "boolean";
|
|
36
50
|
}
|
|
@@ -44,6 +58,18 @@ export function encodePgArrayLiteral(arr) {
|
|
|
44
58
|
parts.push("NULL");
|
|
45
59
|
continue;
|
|
46
60
|
}
|
|
61
|
+
if (parameterKind(v) === "json") {
|
|
62
|
+
parts.push(quoteArrayElement(JSON.stringify(v.value)));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (v instanceof Date) {
|
|
66
|
+
parts.push(quoteArrayElement(v.toISOString()));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (v instanceof Uint8Array) {
|
|
70
|
+
parts.push(quoteArrayElement(`\\x${Buffer.from(v).toString("hex")}`));
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
47
73
|
if (typeof v === "bigint") {
|
|
48
74
|
parts.push(v.toString());
|
|
49
75
|
continue;
|
|
@@ -126,13 +152,12 @@ export function parsePgArrayLiteral(input, parseElement = (value) => value) {
|
|
|
126
152
|
return parsed;
|
|
127
153
|
}
|
|
128
154
|
export function encodeParam(p) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return encodePgArrayLiteral(p);
|
|
155
|
+
const kind = parameterKind(p);
|
|
156
|
+
if (kind === "json")
|
|
157
|
+
return JSON.stringify(p.value);
|
|
158
|
+
if (kind === "array")
|
|
159
|
+
return encodePgArrayLiteral([...p.value]);
|
|
160
|
+
return p;
|
|
136
161
|
}
|
|
137
162
|
export class NoRowsError extends Error {
|
|
138
163
|
constructor(message = "expected exactly 1 row, got 0") {
|
|
@@ -213,16 +238,17 @@ export const _internal = {
|
|
|
213
238
|
loadSqlFile,
|
|
214
239
|
buildSetTransaction,
|
|
215
240
|
clearIdentifierCache,
|
|
241
|
+
parameterKind,
|
|
216
242
|
toPgError,
|
|
217
243
|
};
|
|
218
|
-
async function
|
|
244
|
+
async function runRawQuery(client, query, params) {
|
|
219
245
|
const encoded = params.length === 0
|
|
220
246
|
? params
|
|
221
247
|
: params.map((p) => client.transformParam ? client.transformParam(p) : encodeParam(p));
|
|
222
248
|
const onQuery = client.onQuery;
|
|
223
249
|
if (!onQuery) {
|
|
224
250
|
try {
|
|
225
|
-
return
|
|
251
|
+
return await client.query(query, encoded);
|
|
226
252
|
}
|
|
227
253
|
catch (e) {
|
|
228
254
|
throw toPgError(e) ?? e;
|
|
@@ -230,9 +256,14 @@ async function runQuery(client, query, params) {
|
|
|
230
256
|
}
|
|
231
257
|
const start = performance.now();
|
|
232
258
|
try {
|
|
233
|
-
const
|
|
234
|
-
onQuery({
|
|
235
|
-
|
|
259
|
+
const result = await client.query(query, encoded);
|
|
260
|
+
onQuery({
|
|
261
|
+
query,
|
|
262
|
+
params,
|
|
263
|
+
durationMs: performance.now() - start,
|
|
264
|
+
rowCount: result.count ?? result.length,
|
|
265
|
+
});
|
|
266
|
+
return result;
|
|
236
267
|
}
|
|
237
268
|
catch (e) {
|
|
238
269
|
const error = toPgError(e) ?? e;
|
|
@@ -240,6 +271,16 @@ async function runQuery(client, query, params) {
|
|
|
240
271
|
throw error;
|
|
241
272
|
}
|
|
242
273
|
}
|
|
274
|
+
async function runQuery(client, query, params) {
|
|
275
|
+
return renameRows(await runRawQuery(client, query, params));
|
|
276
|
+
}
|
|
277
|
+
async function runExecute(client, query, params) {
|
|
278
|
+
const result = await runRawQuery(client, query, params);
|
|
279
|
+
return {
|
|
280
|
+
rowCount: result.count ?? result.length,
|
|
281
|
+
command: result.command ?? "",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
243
284
|
async function runOne(client, query, params) {
|
|
244
285
|
const rows = await runQuery(client, query, params);
|
|
245
286
|
if (rows.length === 1)
|
|
@@ -257,8 +298,16 @@ async function runOptional(client, query, params) {
|
|
|
257
298
|
throw new TooManyRowsError(rows.length, "0 or 1");
|
|
258
299
|
}
|
|
259
300
|
const sqlFileCache = new Map();
|
|
260
|
-
function loadSqlFile(path) {
|
|
261
|
-
const
|
|
301
|
+
function loadSqlFile(path, fileRoot = process.env.SQLX_JS_FILE_ROOT ?? process.cwd()) {
|
|
302
|
+
const root = resolve(fileRoot);
|
|
303
|
+
if (isAbsolute(path)) {
|
|
304
|
+
throw new Error(`sqlx-js.sql.file: path must be relative to fileRoot: ${path}`);
|
|
305
|
+
}
|
|
306
|
+
const full = resolve(root, path);
|
|
307
|
+
const rel = relative(root, full);
|
|
308
|
+
if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(rel)) {
|
|
309
|
+
throw new Error(`sqlx-js.sql.file: path escapes fileRoot: ${path}`);
|
|
310
|
+
}
|
|
262
311
|
try {
|
|
263
312
|
const st = statSync(full);
|
|
264
313
|
const cached = sqlFileCache.get(full);
|
|
@@ -393,13 +442,16 @@ function makeBoundCallable(client) {
|
|
|
393
442
|
return runQuery(client, query, params);
|
|
394
443
|
});
|
|
395
444
|
const file = (async (path, ...params) => {
|
|
396
|
-
return runQuery(client, loadSqlFile(path), params);
|
|
445
|
+
return runQuery(client, loadSqlFile(path, client.fileRoot), params);
|
|
397
446
|
});
|
|
398
447
|
file.one = (async (path, ...params) => {
|
|
399
|
-
return runOne(client, loadSqlFile(path), params);
|
|
448
|
+
return runOne(client, loadSqlFile(path, client.fileRoot), params);
|
|
400
449
|
});
|
|
401
450
|
file.optional = (async (path, ...params) => {
|
|
402
|
-
return runOptional(client, loadSqlFile(path), params);
|
|
451
|
+
return runOptional(client, loadSqlFile(path, client.fileRoot), params);
|
|
452
|
+
});
|
|
453
|
+
file.execute = (async (path, ...params) => {
|
|
454
|
+
return runExecute(client, loadSqlFile(path, client.fileRoot), params);
|
|
403
455
|
});
|
|
404
456
|
fn.file = file;
|
|
405
457
|
fn.one = (async (query, ...params) => {
|
|
@@ -408,7 +460,12 @@ function makeBoundCallable(client) {
|
|
|
408
460
|
fn.optional = (async (query, ...params) => {
|
|
409
461
|
return runOptional(client, query, params);
|
|
410
462
|
});
|
|
463
|
+
fn.execute = (async (query, ...params) => {
|
|
464
|
+
return runExecute(client, query, params);
|
|
465
|
+
});
|
|
411
466
|
fn.id = id;
|
|
467
|
+
fn.json = json;
|
|
468
|
+
fn.array = array;
|
|
412
469
|
return fn;
|
|
413
470
|
}
|
|
414
471
|
function buildSetTransaction(opts) {
|
|
@@ -428,13 +485,20 @@ export function createSqlRuntime(getClient) {
|
|
|
428
485
|
return runQuery(getClient(), query, params);
|
|
429
486
|
});
|
|
430
487
|
const rootFile = (async (path, ...params) => {
|
|
431
|
-
|
|
488
|
+
const client = getClient();
|
|
489
|
+
return runQuery(client, loadSqlFile(path, client.fileRoot), params);
|
|
432
490
|
});
|
|
433
491
|
rootFile.one = (async (path, ...params) => {
|
|
434
|
-
|
|
492
|
+
const client = getClient();
|
|
493
|
+
return runOne(client, loadSqlFile(path, client.fileRoot), params);
|
|
435
494
|
});
|
|
436
495
|
rootFile.optional = (async (path, ...params) => {
|
|
437
|
-
|
|
496
|
+
const client = getClient();
|
|
497
|
+
return runOptional(client, loadSqlFile(path, client.fileRoot), params);
|
|
498
|
+
});
|
|
499
|
+
rootFile.execute = (async (path, ...params) => {
|
|
500
|
+
const client = getClient();
|
|
501
|
+
return runExecute(client, loadSqlFile(path, client.fileRoot), params);
|
|
438
502
|
});
|
|
439
503
|
root.file = rootFile;
|
|
440
504
|
root.one = (async (query, ...params) => {
|
|
@@ -443,7 +507,12 @@ export function createSqlRuntime(getClient) {
|
|
|
443
507
|
root.optional = (async (query, ...params) => {
|
|
444
508
|
return runOptional(getClient(), query, params);
|
|
445
509
|
});
|
|
510
|
+
root.execute = (async (query, ...params) => {
|
|
511
|
+
return runExecute(getClient(), query, params);
|
|
512
|
+
});
|
|
446
513
|
root.id = id;
|
|
514
|
+
root.json = json;
|
|
515
|
+
root.array = array;
|
|
447
516
|
root.transaction = (async (fnOrOpts, maybeFn) => {
|
|
448
517
|
const c = getClient();
|
|
449
518
|
let opts = {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScanConfig } from "../config.js";
|
|
1
2
|
export type QueryCallSite = {
|
|
2
3
|
file: string;
|
|
3
4
|
line: number;
|
|
@@ -7,6 +8,12 @@ export type QueryCallSite = {
|
|
|
7
8
|
kind: "inline" | "file";
|
|
8
9
|
sqlFilePath?: string;
|
|
9
10
|
};
|
|
10
|
-
export declare
|
|
11
|
+
export declare class ScanError extends Error {
|
|
12
|
+
readonly file: string;
|
|
13
|
+
readonly line: number;
|
|
14
|
+
readonly column: number;
|
|
15
|
+
constructor(file: string, line: number, column: number, message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare function findSourceFiles(root: string, scan?: ScanConfig): string[];
|
|
11
18
|
export declare function scanFile(absPath: string, root: string): QueryCallSite[];
|
|
12
|
-
export declare function scanProject(root: string): QueryCallSite[];
|
|
19
|
+
export declare function scanProject(root: string, scan?: ScanConfig): QueryCallSite[];
|
package/dist/src/scan/scanner.js
CHANGED
|
@@ -1,25 +1,67 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { existsSync, readFileSync
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
export class ScanError extends Error {
|
|
5
|
+
file;
|
|
6
|
+
line;
|
|
7
|
+
column;
|
|
8
|
+
constructor(file, line, column, message) {
|
|
9
|
+
super(`sqlx-js: ${file}:${line}:${column} — ${message}`);
|
|
10
|
+
this.file = file;
|
|
11
|
+
this.line = line;
|
|
12
|
+
this.column = column;
|
|
13
|
+
this.name = "ScanError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const DEFAULT_EXCLUDES = [
|
|
17
|
+
"**/node_modules/**",
|
|
18
|
+
"**/.git/**",
|
|
19
|
+
"**/.sqlx-js/**",
|
|
20
|
+
"**/dist/**",
|
|
21
|
+
"**/build/**",
|
|
22
|
+
"**/.next/**",
|
|
23
|
+
];
|
|
5
24
|
const SQLX_MODULES = new Set(["@onreza/sqlx-js"]);
|
|
6
25
|
const EXT = /\.(ts|tsx|mts|cts)$/;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
const TS_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts"];
|
|
27
|
+
function formatConfigError(error) {
|
|
28
|
+
return ts.flattenDiagnosticMessageText(error.messageText, "\n");
|
|
29
|
+
}
|
|
30
|
+
function collectTsconfigFiles(configPath, out, visited) {
|
|
31
|
+
const resolved = resolve(configPath);
|
|
32
|
+
if (visited.has(resolved))
|
|
33
|
+
return;
|
|
34
|
+
visited.add(resolved);
|
|
35
|
+
const read = ts.readConfigFile(resolved, ts.sys.readFile);
|
|
36
|
+
if (read.error)
|
|
37
|
+
throw new Error(`sqlx-js scan: ${resolved}: ${formatConfigError(read.error)}`);
|
|
38
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, resolve(resolved, ".."), undefined, resolved);
|
|
39
|
+
if (parsed.errors.length > 0) {
|
|
40
|
+
throw new Error(`sqlx-js scan: ${resolved}: ${parsed.errors.map(formatConfigError).join("; ")}`);
|
|
41
|
+
}
|
|
42
|
+
for (const file of parsed.fileNames) {
|
|
43
|
+
if (EXT.test(file))
|
|
44
|
+
out.add(resolve(file));
|
|
45
|
+
}
|
|
46
|
+
for (const reference of parsed.projectReferences ?? []) {
|
|
47
|
+
collectTsconfigFiles(ts.resolveProjectReferencePath(reference), out, visited);
|
|
17
48
|
}
|
|
18
49
|
}
|
|
19
|
-
export function findSourceFiles(root) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
export function findSourceFiles(root, scan = {}) {
|
|
51
|
+
const excludes = [...DEFAULT_EXCLUDES, ...(scan.exclude ?? [])];
|
|
52
|
+
if (scan.include !== undefined) {
|
|
53
|
+
if (scan.include.length === 0)
|
|
54
|
+
return [];
|
|
55
|
+
return ts.sys.readDirectory(root, TS_EXTENSIONS, excludes, scan.include).map((file) => resolve(file)).sort();
|
|
56
|
+
}
|
|
57
|
+
const configPath = join(root, "tsconfig.json");
|
|
58
|
+
if (!ts.sys.fileExists(configPath)) {
|
|
59
|
+
return ts.sys.readDirectory(root, TS_EXTENSIONS, excludes, ["**/*"]).map((file) => resolve(file)).sort();
|
|
60
|
+
}
|
|
61
|
+
const configured = new Set();
|
|
62
|
+
collectTsconfigFiles(configPath, configured, new Set());
|
|
63
|
+
const allowed = new Set(ts.sys.readDirectory(root, TS_EXTENSIONS, excludes, ["**/*"]).map((file) => resolve(file)));
|
|
64
|
+
return [...configured].filter((file) => allowed.has(file)).sort();
|
|
23
65
|
}
|
|
24
66
|
function classifyCallee(callee, scope) {
|
|
25
67
|
if (ts.isIdentifier(callee)) {
|
|
@@ -45,7 +87,7 @@ function classifyCallee(callee, scope) {
|
|
|
45
87
|
return { kind: "transaction" };
|
|
46
88
|
if (methodName === "file")
|
|
47
89
|
return { kind: "file" };
|
|
48
|
-
if (methodName === "one" || methodName === "optional")
|
|
90
|
+
if (methodName === "one" || methodName === "optional" || methodName === "execute")
|
|
49
91
|
return { kind: "inline" };
|
|
50
92
|
return null;
|
|
51
93
|
}
|
|
@@ -57,7 +99,7 @@ function classifyCallee(callee, scope) {
|
|
|
57
99
|
if (ts.isIdentifier(mid.expression) && scope.namespaces.has(mid.expression.text)) {
|
|
58
100
|
if (mid.name.text !== "sql")
|
|
59
101
|
return null;
|
|
60
|
-
if (methodName === "one" || methodName === "optional")
|
|
102
|
+
if (methodName === "one" || methodName === "optional" || methodName === "execute")
|
|
61
103
|
return { kind: "inline" };
|
|
62
104
|
if (methodName === "file")
|
|
63
105
|
return { kind: "file" };
|
|
@@ -72,7 +114,7 @@ function classifyCallee(callee, scope) {
|
|
|
72
114
|
return null;
|
|
73
115
|
if (mid.name.text !== "file")
|
|
74
116
|
return null;
|
|
75
|
-
if (methodName === "one" || methodName === "optional")
|
|
117
|
+
if (methodName === "one" || methodName === "optional" || methodName === "execute")
|
|
76
118
|
return { kind: "file" };
|
|
77
119
|
return null;
|
|
78
120
|
}
|
|
@@ -83,7 +125,7 @@ function classifyCallee(callee, scope) {
|
|
|
83
125
|
scope.namespaces.has(mid.expression.expression.text) &&
|
|
84
126
|
mid.expression.name.text === "sql" &&
|
|
85
127
|
mid.name.text === "file" &&
|
|
86
|
-
(methodName === "one" || methodName === "optional")) {
|
|
128
|
+
(methodName === "one" || methodName === "optional" || methodName === "execute")) {
|
|
87
129
|
return { kind: "file" };
|
|
88
130
|
}
|
|
89
131
|
}
|
|
@@ -130,7 +172,7 @@ export function scanFile(absPath, root) {
|
|
|
130
172
|
const recordInline = (first, args) => {
|
|
131
173
|
if (!ts.isStringLiteralLike(first)) {
|
|
132
174
|
const pos = here(first);
|
|
133
|
-
throw new
|
|
175
|
+
throw new ScanError(fileRel, pos.line, pos.column, "sql() requires a string literal as first argument");
|
|
134
176
|
}
|
|
135
177
|
const pos = here(first);
|
|
136
178
|
out.push({
|
|
@@ -146,13 +188,22 @@ export function scanFile(absPath, root) {
|
|
|
146
188
|
const recordFile = (first, args, callee) => {
|
|
147
189
|
if (!ts.isStringLiteralLike(first)) {
|
|
148
190
|
const pos = first ? here(first) : here(callee);
|
|
149
|
-
throw new
|
|
191
|
+
throw new ScanError(fileRel, pos.line, pos.column, "sql.file() requires a string literal path");
|
|
150
192
|
}
|
|
151
193
|
const sqlPath = first.text;
|
|
152
|
-
|
|
194
|
+
if (isAbsolute(sqlPath)) {
|
|
195
|
+
const pos = here(first);
|
|
196
|
+
throw new ScanError(fileRel, pos.line, pos.column, `sql.file path must be relative to --root: ${sqlPath}`);
|
|
197
|
+
}
|
|
198
|
+
const abs = resolve(root, sqlPath);
|
|
199
|
+
const rel = relative(root, abs);
|
|
200
|
+
if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(rel)) {
|
|
201
|
+
const pos = here(first);
|
|
202
|
+
throw new ScanError(fileRel, pos.line, pos.column, `sql.file path escapes --root: ${sqlPath}`);
|
|
203
|
+
}
|
|
153
204
|
if (!existsSync(abs)) {
|
|
154
205
|
const pos = here(first);
|
|
155
|
-
throw new
|
|
206
|
+
throw new ScanError(fileRel, pos.line, pos.column, `sql.file path not found: ${sqlPath}`);
|
|
156
207
|
}
|
|
157
208
|
const query = readFileSync(abs, "utf8");
|
|
158
209
|
const pos = here(first);
|
|
@@ -163,7 +214,7 @@ export function scanFile(absPath, root) {
|
|
|
163
214
|
query,
|
|
164
215
|
paramCount: args.length - 1,
|
|
165
216
|
kind: "file",
|
|
166
|
-
sqlFilePath:
|
|
217
|
+
sqlFilePath: sqlPath,
|
|
167
218
|
});
|
|
168
219
|
return true;
|
|
169
220
|
};
|
|
@@ -272,8 +323,8 @@ export function scanFile(absPath, root) {
|
|
|
272
323
|
visit(source, { sqlAliases: importedAliases, namespaces: importedNamespaces });
|
|
273
324
|
return out;
|
|
274
325
|
}
|
|
275
|
-
export function scanProject(root) {
|
|
276
|
-
const files = findSourceFiles(root);
|
|
326
|
+
export function scanProject(root, scan = {}) {
|
|
327
|
+
const files = findSourceFiles(root, scan);
|
|
277
328
|
const out = [];
|
|
278
329
|
for (const f of files) {
|
|
279
330
|
for (const site of scanFile(f, root))
|
package/dist/src/typed.d.ts
CHANGED
|
@@ -4,17 +4,27 @@ type ParamsOf<T> = T extends {
|
|
|
4
4
|
type RowOf<T> = T extends {
|
|
5
5
|
row: infer R;
|
|
6
6
|
} ? R : never;
|
|
7
|
+
type ExecuteResult = import("./runtime.js").ExecuteResult;
|
|
8
|
+
type JsonInputValue = import("./runtime.js").JsonInputValue;
|
|
9
|
+
type JsonParameter<T> = import("./runtime.js").JsonParameter<T>;
|
|
10
|
+
type PgArrayParameter<T> = import("./runtime.js").PgArrayParameter<T>;
|
|
11
|
+
type JsonFn = <T extends JsonInputValue>(value: T) => JsonParameter<T>;
|
|
12
|
+
type ArrayFn = <T>(value: readonly (T | null)[]) => PgArrayParameter<T>;
|
|
7
13
|
export type TypedFile<TFileQueries> = {
|
|
8
14
|
<P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>): Promise<RowOf<TFileQueries[P]>[]>;
|
|
9
15
|
one: <P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>) => Promise<RowOf<TFileQueries[P]>>;
|
|
10
16
|
optional: <P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>) => Promise<RowOf<TFileQueries[P]> | null>;
|
|
17
|
+
execute: <P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>) => Promise<ExecuteResult>;
|
|
11
18
|
};
|
|
12
19
|
export type TypedSql<TQueries, TFileQueries> = {
|
|
13
20
|
<Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>): Promise<RowOf<TQueries[Q]>[]>;
|
|
14
21
|
one: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<RowOf<TQueries[Q]>>;
|
|
15
22
|
optional: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<RowOf<TQueries[Q]> | null>;
|
|
23
|
+
execute: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<ExecuteResult>;
|
|
16
24
|
file: TypedFile<TFileQueries>;
|
|
17
25
|
id: (...parts: string[]) => string;
|
|
26
|
+
json: JsonFn;
|
|
27
|
+
array: ArrayFn;
|
|
18
28
|
};
|
|
19
29
|
export type Typed<TQueries, TFileQueries, TTransactionOptions> = TypedSql<TQueries, TFileQueries> & {
|
|
20
30
|
transaction: {
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onreza/sqlx-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Compile-time-checked raw SQL for TypeScript + PostgreSQL. Inspired by sqlx.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=24",
|
|
9
|
+
"bun": ">=1.3"
|
|
9
10
|
},
|
|
10
11
|
"sideEffects": false,
|
|
11
12
|
"main": "./dist/src/index.js",
|
|
@@ -49,13 +50,13 @@
|
|
|
49
50
|
"migrations"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
|
-
"prepare": "
|
|
53
|
-
"test": "bun test tests",
|
|
53
|
+
"prepare": "node scripts/prepare-package.mjs",
|
|
54
|
+
"test": "bun test tests --timeout 120000",
|
|
54
55
|
"test:typecheck": "tsc --noEmit",
|
|
55
56
|
"test:example": "bunx tsc -p example --noEmit",
|
|
56
57
|
"sqlx:prepare": "bun bin/sqlx-js.ts prepare",
|
|
57
58
|
"sqlx:migrate": "bun bin/sqlx-js.ts migrate",
|
|
58
|
-
"build": "
|
|
59
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json && bun scripts/fix-dist-imports.ts",
|
|
59
60
|
"prepack": "bun run build"
|
|
60
61
|
},
|
|
61
62
|
"peerDependencies": {
|