@onreza/sqlx-js 0.4.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 +85 -34
- package/ROADMAP.md +1 -3
- package/dist/bin/sqlx-js.js +91 -20
- 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.js +14 -2
- package/dist/src/commands/doctor.d.ts +16 -0
- package/dist/src/commands/doctor.js +196 -0
- package/dist/src/commands/init.js +6 -10
- package/dist/src/commands/migrate.js +7 -22
- package/dist/src/commands/pgschema.d.ts +6 -0
- package/dist/src/commands/pgschema.js +30 -0
- package/dist/src/commands/prepare.d.ts +40 -6
- package/dist/src/commands/prepare.js +341 -63
- package/dist/src/commands/watch.d.ts +1 -0
- package/dist/src/commands/watch.js +23 -9
- package/dist/src/config.d.ts +21 -0
- package/dist/src/config.js +141 -11
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +2 -1
- 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
|
@@ -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": {
|