@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.
Files changed (39) hide show
  1. package/README.md +129 -36
  2. package/ROADMAP.md +3 -4
  3. package/dist/bin/sqlx-js.js +137 -26
  4. package/dist/src/artifacts.d.ts +9 -0
  5. package/dist/src/artifacts.js +26 -0
  6. package/dist/src/cache.d.ts +17 -0
  7. package/dist/src/cache.js +83 -1
  8. package/dist/src/codegen.d.ts +2 -1
  9. package/dist/src/codegen.js +34 -5
  10. package/dist/src/commands/doctor.d.ts +16 -0
  11. package/dist/src/commands/doctor.js +196 -0
  12. package/dist/src/commands/init.d.ts +1 -0
  13. package/dist/src/commands/init.js +36 -9
  14. package/dist/src/commands/migrate.js +7 -22
  15. package/dist/src/commands/pgschema.d.ts +31 -0
  16. package/dist/src/commands/pgschema.js +208 -0
  17. package/dist/src/commands/prepare.d.ts +40 -5
  18. package/dist/src/commands/prepare.js +344 -58
  19. package/dist/src/commands/watch.d.ts +1 -0
  20. package/dist/src/commands/watch.js +23 -9
  21. package/dist/src/config.d.ts +27 -0
  22. package/dist/src/config.js +141 -11
  23. package/dist/src/function-cache.d.ts +18 -0
  24. package/dist/src/function-cache.js +38 -0
  25. package/dist/src/index.d.ts +6 -2
  26. package/dist/src/index.js +2 -1
  27. package/dist/src/pg/functions.d.ts +4 -0
  28. package/dist/src/pg/functions.js +150 -0
  29. package/dist/src/pg/oids.js +2 -0
  30. package/dist/src/pg/schema.d.ts +1 -0
  31. package/dist/src/pg/schema.js +3 -0
  32. package/dist/src/postgres-runtime.d.ts +3 -0
  33. package/dist/src/postgres-runtime.js +66 -29
  34. package/dist/src/runtime.d.ts +36 -3
  35. package/dist/src/runtime.js +91 -22
  36. package/dist/src/scan/scanner.d.ts +9 -2
  37. package/dist/src/scan/scanner.js +79 -28
  38. package/dist/src/typed.d.ts +10 -0
  39. package/package.json +6 -5
@@ -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<unknown[]>;
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";
@@ -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
- if (!Array.isArray(p))
130
- return p;
131
- if (p.length === 0)
132
- return p;
133
- if (!p.every(isPrimitiveArrayElement))
134
- return p;
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 runQuery(client, query, params) {
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 renameRows(await client.query(query, encoded));
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 rows = await client.query(query, encoded);
234
- onQuery({ query, params, durationMs: performance.now() - start, rowCount: rows.length });
235
- return renameRows(rows);
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 full = resolve(process.cwd(), path);
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
- return runQuery(getClient(), loadSqlFile(path), params);
488
+ const client = getClient();
489
+ return runQuery(client, loadSqlFile(path, client.fileRoot), params);
432
490
  });
433
491
  rootFile.one = (async (path, ...params) => {
434
- return runOne(getClient(), loadSqlFile(path), params);
492
+ const client = getClient();
493
+ return runOne(client, loadSqlFile(path, client.fileRoot), params);
435
494
  });
436
495
  rootFile.optional = (async (path, ...params) => {
437
- return runOptional(getClient(), loadSqlFile(path), params);
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 function findSourceFiles(root: string): string[];
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[];
@@ -1,25 +1,67 @@
1
1
  import ts from "typescript";
2
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { dirname, join, relative, resolve } from "node:path";
4
- const EXCLUDE_DIRS = new Set(["node_modules", ".git", ".sqlx-js", "dist", "build", ".next"]);
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
- function walk(dir, out) {
8
- for (const name of readdirSync(dir)) {
9
- if (EXCLUDE_DIRS.has(name))
10
- continue;
11
- const full = join(dir, name);
12
- const st = statSync(full);
13
- if (st.isDirectory())
14
- walk(full, out);
15
- else if (EXT.test(name))
16
- out.push(full);
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 out = [];
21
- walk(root, out);
22
- return out;
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 Error(`sqlx-js: ${fileRel}:${pos.line}:${pos.column} sql() requires a string literal as first argument`);
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 Error(`sqlx-js: ${fileRel}:${pos.line}:${pos.column} sql.file() requires a string literal path`);
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
- const abs = resolve(dirname(absPath), sqlPath);
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 Error(`sqlx-js: ${fileRel}:${pos.line}:${pos.column} sql.file path not found: ${sqlPath}`);
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: relative(root, abs),
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))
@@ -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.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": ">=18"
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": "(test -f dist/bin/sqlx-js.js || bun run build) && ([ ! -d .git ] || lefthook install >/dev/null 2>&1 || true)",
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": "rm -rf dist && tsc -p tsconfig.build.json && bun scripts/fix-dist-imports.ts",
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": {