@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.
@@ -8,7 +8,7 @@ import { arrayElementOid, isBuiltinOid, oidToTs } from "../pg/oids.js";
8
8
  import { ScanError, scanProject } from "../scan/scanner.js";
9
9
  import { assertCacheManifest, Cache, fingerprint, effectiveNullable, portableCacheOid, writeCacheManifest, } from "../cache.js";
10
10
  import { emitDts } from "../codegen.js";
11
- import { loadConfig, lookupJsonbType, prepareConfigHash } from "../config.js";
11
+ import { loadConfig, lookupColumnType, lookupJsonbType, prepareConfigHash } from "../config.js";
12
12
  import { functionCacheExists, readFunctionCache, writeFunctionCache } from "../function-cache.js";
13
13
  import { introspectFunctions } from "../pg/functions.js";
14
14
  import { buildParamMap } from "../pg/param-map.js";
@@ -18,7 +18,7 @@ import { containsUnknownType } from "../type-inspection.js";
18
18
  import { originalPosition, rewriteNamedParameters } from "../sql-params.js";
19
19
  const JSON_OIDS = new Set([114, 3802]);
20
20
  const JSON_ARRAY_OIDS = new Set([199, 3807]);
21
- const JSON_INPUT_VALUE = 'import("@onreza/sqlx-js").JsonInputValue';
21
+ const JSON_INPUT_VALUE = "unknown";
22
22
  function jsonParameter(type) {
23
23
  return `import("@onreza/sqlx-js").JsonParameter<${type}>`;
24
24
  }
@@ -48,6 +48,12 @@ function resolveTs(oid, customLookup) {
48
48
  }
49
49
  return oidToTs(oid).ts;
50
50
  }
51
+ function isScalarColumnType(oid, schema) {
52
+ if (JSON_OIDS.has(oid) || JSON_ARRAY_OIDS.has(oid) || arrayElementOid(oid) !== undefined)
53
+ return false;
54
+ const custom = schema.customType(oid);
55
+ return custom?.kind !== "enumArray" && custom?.kind !== "scalarArray" && custom?.kind !== "compositeArray";
56
+ }
51
57
  function resolveColumnTs(f, schema, cfg) {
52
58
  if (f.tableOid !== 0 && f.columnAttr !== 0) {
53
59
  const tbl = schema.tableNameByOid(f.tableOid);
@@ -63,13 +69,25 @@ function resolveColumnTs(f, schema, cfg) {
63
69
  if (decl)
64
70
  return `(${decl})[]`;
65
71
  }
72
+ if (isScalarColumnType(f.typeOid, schema)) {
73
+ const decl = lookupColumnType(cfg, tbl.schema, tbl.name, colName);
74
+ if (decl)
75
+ return decl;
76
+ }
66
77
  }
67
78
  }
68
79
  return resolveTs(f.typeOid, (oid) => schema.customType(oid));
69
80
  }
70
81
  function resolveParamTs(paramIndex, paramOid, paramMap, schema, cfg) {
82
+ const target = paramMap.get(paramIndex);
83
+ if (isScalarColumnType(paramOid, schema) && target) {
84
+ const column = resolveTargetColumn(target, schema);
85
+ const table = resolvedTargetTable(target, schema);
86
+ const decl = column && table ? lookupColumnType(cfg, table.schema, table.name, column) : undefined;
87
+ if (decl)
88
+ return decl;
89
+ }
71
90
  if (JSON_OIDS.has(paramOid)) {
72
- const target = paramMap.get(paramIndex);
73
91
  if (target) {
74
92
  const column = resolveTargetColumn(target, schema);
75
93
  const table = resolvedTargetTable(target, schema);
@@ -80,7 +98,6 @@ function resolveParamTs(paramIndex, paramOid, paramMap, schema, cfg) {
80
98
  return jsonParameter(JSON_INPUT_VALUE);
81
99
  }
82
100
  if (JSON_ARRAY_OIDS.has(paramOid)) {
83
- const target = paramMap.get(paramIndex);
84
101
  if (target) {
85
102
  const column = resolveTargetColumn(target, schema);
86
103
  const table = resolvedTargetTable(target, schema);
@@ -188,7 +205,17 @@ function fatal(phase, error) {
188
205
  return new PrepareFatalError(phase, message, location, { cause: error });
189
206
  }
190
207
  function formatSite(s) {
191
- return `${s.file}:${s.line}:${s.column}`;
208
+ return `${s.file}:${s.line}:${s.column}${s.queryName ? ` [${s.queryName}]` : ""}`;
209
+ }
210
+ function siteDiagnostic(site) {
211
+ return {
212
+ file: site.file,
213
+ line: site.line,
214
+ column: site.column,
215
+ query: site.query,
216
+ queryId: fingerprint(site.query),
217
+ ...(site.queryName ? { queryName: site.queryName } : {}),
218
+ };
192
219
  }
193
220
  function snippet(query, max = 80) {
194
221
  const oneLine = query.replace(/\s+/g, " ").trim();
@@ -224,10 +251,7 @@ function inferenceDiagnostics(entry, site, strict) {
224
251
  severity: strict ? "error" : "warning",
225
252
  phase: "inference",
226
253
  message,
227
- file: site.file,
228
- line: site.line,
229
- column: site.column,
230
- query: site.query,
254
+ ...siteDiagnostic(site),
231
255
  }));
232
256
  }
233
257
  export async function openSession(opts) {
@@ -372,10 +396,7 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
372
396
  severity: "error",
373
397
  phase: "result-shape",
374
398
  message,
375
- file: site.file,
376
- line: site.line,
377
- column: site.column,
378
- query: site.query,
399
+ ...siteDiagnostic(site),
379
400
  });
380
401
  err(` ✗ ${formatSite(site)} — ${message}`);
381
402
  err(` query: ${snippet(site.query)}`);
@@ -392,10 +413,7 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
392
413
  severity: "error",
393
414
  phase: "describe",
394
415
  message: e.message,
395
- file: site.file,
396
- line: site.line,
397
- column: site.column,
398
- query: site.query,
416
+ ...siteDiagnostic(site),
399
417
  ...(e.code ? { code: e.code } : {}),
400
418
  ...(position ? { position } : {}),
401
419
  ...(e.hint ? { hint: e.hint } : {}),
@@ -416,10 +434,7 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
416
434
  severity: "error",
417
435
  phase: "describe",
418
436
  message: e.message,
419
- file: site.file,
420
- line: site.line,
421
- column: site.column,
422
- query: site.query,
437
+ ...siteDiagnostic(site),
423
438
  });
424
439
  err(` ✗ ${formatSite(site)} — describe failed: ${e.message}`);
425
440
  err(` query: ${snippet(site.query)}`);
@@ -457,10 +472,7 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
457
472
  severity: "error",
458
473
  phase: "analyze",
459
474
  message: e.message,
460
- file: site.file,
461
- line: site.line,
462
- column: site.column,
463
- query: site.query,
475
+ ...siteDiagnostic(site),
464
476
  });
465
477
  err(` ✗ ${formatSite(site)} — analyze failed: ${e.message}`);
466
478
  err(` query: ${snippet(site.query)}`);
@@ -476,10 +488,7 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
476
488
  severity: "error",
477
489
  phase: "param-map",
478
490
  message: e.message,
479
- file: site.file,
480
- line: site.line,
481
- column: site.column,
482
- query: site.query,
491
+ ...siteDiagnostic(site),
483
492
  });
484
493
  err(` ✗ ${formatSite(site)} — paramMap failed: ${e.message}`);
485
494
  err(` query: ${snippet(site.query)}`);
@@ -580,12 +589,17 @@ export async function prepareOnce(opts, session, log = console.log, err = consol
580
589
  return { sites: sites.length, entries: entries.length, failures, pruned: 0, functions: 0, diagnostics };
581
590
  }
582
591
  let functions;
583
- if (input.reuseFunctions && functionCacheExists(opts.cacheDir)) {
592
+ if (userCfg.functionCatalog === false) {
593
+ functions = [];
594
+ }
595
+ else if (input.reuseFunctions && functionCacheExists(opts.cacheDir)) {
584
596
  functions = readFunctionCache(opts.cacheDir);
585
597
  }
586
598
  else {
587
599
  try {
588
- functions = await introspectFunctions(client, schema);
600
+ functions = await introspectFunctions(client, schema, {
601
+ includeExtensionOwned: userCfg.functionCatalog?.includeExtensionOwned === true,
602
+ });
589
603
  }
590
604
  catch (error) {
591
605
  throw fatal("introspect", error);
@@ -663,10 +677,7 @@ export async function runPrepare(opts) {
663
677
  severity: "error",
664
678
  phase: "cache",
665
679
  message: "query is not in the offline cache",
666
- file: site.file,
667
- line: site.line,
668
- column: site.column,
669
- query,
680
+ ...siteDiagnostic(site),
670
681
  });
671
682
  if (!opts.json) {
672
683
  console.error(`stale: ${formatSite(site)} — query not in cache`);
@@ -0,0 +1,38 @@
1
+ import type { QueryExecutionMode } from "../query.js";
2
+ export type QueriesPhase = "config" | "scan" | "cache" | "embed";
3
+ export declare class QueriesError extends Error {
4
+ readonly phase: QueriesPhase;
5
+ readonly file?: string | undefined;
6
+ readonly line?: number | undefined;
7
+ readonly column?: number | undefined;
8
+ constructor(phase: QueriesPhase, message: string, file?: string | undefined, line?: number | undefined, column?: number | undefined, options?: {
9
+ cause?: unknown;
10
+ });
11
+ }
12
+ export type QueryInventoryItem = {
13
+ queryId: string;
14
+ queryNames: string[];
15
+ query: string;
16
+ cardinalities: QueryExecutionMode[];
17
+ sqlFilePaths: string[];
18
+ callSites: {
19
+ file: string;
20
+ line: number;
21
+ column: number;
22
+ }[];
23
+ cacheStatus: "current" | "stale" | "missing";
24
+ };
25
+ export type QueryInventory = {
26
+ formatVersion: 1;
27
+ ok: true;
28
+ queries: QueryInventoryItem[];
29
+ orphanedCacheIds: string[];
30
+ };
31
+ export declare function buildQueryInventory(root: string, cacheDir: string): Promise<QueryInventory>;
32
+ export declare function emitEmbeddedSqlModule(path: string, inventory: QueryInventory): void;
33
+ export declare function runQueries(options: {
34
+ root: string;
35
+ cacheDir: string;
36
+ json?: boolean;
37
+ embedPath?: string;
38
+ }): Promise<void>;
@@ -0,0 +1,143 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { dirname, relative } from "node:path";
4
+ import { Cache, CacheManifestStaleError, readCacheManifest } from "../cache.js";
5
+ import { loadConfig, prepareConfigHash } from "../config.js";
6
+ import { queryId } from "../query-id.js";
7
+ import { ScanError, scanProject } from "../scan/scanner.js";
8
+ export class QueriesError extends Error {
9
+ phase;
10
+ file;
11
+ line;
12
+ column;
13
+ constructor(phase, message, file, line, column, options) {
14
+ super(message, options);
15
+ this.phase = phase;
16
+ this.file = file;
17
+ this.line = line;
18
+ this.column = column;
19
+ this.name = "QueriesError";
20
+ }
21
+ }
22
+ function queriesError(phase, error) {
23
+ if (error instanceof QueriesError)
24
+ return error;
25
+ if (error instanceof ScanError) {
26
+ return new QueriesError(phase, error.message, error.file, error.line, error.column, { cause: error });
27
+ }
28
+ return new QueriesError(phase, error instanceof Error ? error.message : String(error), undefined, undefined, undefined, { cause: error });
29
+ }
30
+ export async function buildQueryInventory(root, cacheDir) {
31
+ let config;
32
+ try {
33
+ config = await loadConfig(root);
34
+ }
35
+ catch (error) {
36
+ throw queriesError("config", error);
37
+ }
38
+ let sites;
39
+ try {
40
+ sites = scanProject(root, config.scan);
41
+ }
42
+ catch (error) {
43
+ throw queriesError("scan", error);
44
+ }
45
+ const cache = new Cache(cacheDir);
46
+ let manifestCurrent = false;
47
+ try {
48
+ const manifest = readCacheManifest(cacheDir);
49
+ manifestCurrent = manifest?.configHash === prepareConfigHash(config);
50
+ }
51
+ catch (error) {
52
+ if (!(error instanceof CacheManifestStaleError)) {
53
+ throw queriesError("cache", error);
54
+ }
55
+ manifestCurrent = false;
56
+ }
57
+ let cacheIds;
58
+ try {
59
+ cacheIds = cache.list().map(({ fp }) => fp);
60
+ }
61
+ catch (error) {
62
+ throw queriesError("cache", error);
63
+ }
64
+ const cached = new Set(cacheIds);
65
+ const grouped = new Map();
66
+ for (const site of sites) {
67
+ const id = queryId(site.query);
68
+ const group = grouped.get(id) ?? [];
69
+ group.push(site);
70
+ grouped.set(id, group);
71
+ }
72
+ const queries = [...grouped.entries()].map(([id, group]) => {
73
+ return {
74
+ queryId: id,
75
+ queryNames: [...new Set(group.flatMap((site) => site.queryName ? [site.queryName] : []))].sort(),
76
+ query: group[0].query,
77
+ cardinalities: [...new Set(group.map((site) => site.cardinality ?? "many"))].sort(),
78
+ sqlFilePaths: [...new Set(group.flatMap((site) => site.sqlFilePath ? [site.sqlFilePath] : []))].sort(),
79
+ callSites: group
80
+ .map(({ file, line, column }) => ({ file, line, column }))
81
+ .sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column),
82
+ cacheStatus: !cached.has(id) ? "missing" : manifestCurrent ? "current" : "stale",
83
+ };
84
+ }).sort((a, b) => a.queryId.localeCompare(b.queryId));
85
+ const active = new Set(queries.map((query) => query.queryId));
86
+ const orphanedCacheIds = cacheIds.filter((fp) => !active.has(fp)).sort();
87
+ return { formatVersion: 1, ok: true, queries, orphanedCacheIds };
88
+ }
89
+ export function emitEmbeddedSqlModule(path, inventory) {
90
+ const sqlFiles = Object.fromEntries(inventory.queries
91
+ .flatMap((query) => query.sqlFilePaths.map((file) => [file, query.query]))
92
+ .sort(([a], [b]) => a.localeCompare(b)));
93
+ const content = [
94
+ "// AUTO-GENERATED by sqlx-js. Do not edit.",
95
+ "// Run `sqlx-js queries --embed <path>` to regenerate.",
96
+ "",
97
+ `export const sqlxJsEmbeddedSql = ${JSON.stringify(sqlFiles, null, 2)} as const;`,
98
+ "",
99
+ ].join("\n");
100
+ mkdirSync(dirname(path), { recursive: true });
101
+ const tmp = `${path}.tmp-${randomBytes(4).toString("hex")}`;
102
+ writeFileSync(tmp, content);
103
+ try {
104
+ renameSync(tmp, path);
105
+ }
106
+ catch (error) {
107
+ try {
108
+ unlinkSync(tmp);
109
+ }
110
+ catch { }
111
+ throw error;
112
+ }
113
+ }
114
+ export async function runQueries(options) {
115
+ const inventory = await buildQueryInventory(options.root, options.cacheDir);
116
+ if (options.embedPath) {
117
+ try {
118
+ emitEmbeddedSqlModule(options.embedPath, inventory);
119
+ }
120
+ catch (error) {
121
+ throw queriesError("embed", error);
122
+ }
123
+ }
124
+ if (options.json) {
125
+ console.log(JSON.stringify({
126
+ ...inventory,
127
+ ...(options.embedPath ? { embeddedModule: relative(options.root, options.embedPath).replace(/\\/g, "/") } : {}),
128
+ }, null, 2));
129
+ return;
130
+ }
131
+ for (const query of inventory.queries) {
132
+ const names = query.queryNames.length > 0 ? ` ${query.queryNames.join(",")}` : "";
133
+ console.log(`${query.queryId}${names} ${query.cardinalities.join(",")} ${query.cacheStatus}`);
134
+ for (const site of query.callSites)
135
+ console.log(` ${site.file}:${site.line}:${site.column}`);
136
+ }
137
+ if (inventory.queries.length === 0)
138
+ console.log("no sqlx-js queries found");
139
+ if (inventory.orphanedCacheIds.length > 0)
140
+ console.log(`orphaned cache: ${inventory.orphanedCacheIds.join(", ")}`);
141
+ if (options.embedPath)
142
+ console.log(`embedded SQL module: ${relative(options.root, options.embedPath).replace(/\\/g, "/")}`);
143
+ }
@@ -5,7 +5,11 @@ export type ScanConfig = {
5
5
  };
6
6
  export type SqlxJsConfig = {
7
7
  jsonbTypes?: Record<string, string>;
8
+ columnTypes?: Record<string, string>;
8
9
  customTypes?: Record<string, string>;
10
+ functionCatalog?: false | {
11
+ includeExtensionOwned?: boolean;
12
+ };
9
13
  scan?: ScanConfig;
10
14
  schema?: {
11
15
  provider?: "builtin" | "pgschema";
@@ -32,3 +36,4 @@ export declare function runtimeVersion(): {
32
36
  export declare function nativeTypeScriptEnabled(): boolean | string;
33
37
  export declare function envFilePath(root: string): string;
34
38
  export declare function lookupJsonbType(cfg: SqlxJsConfig, schema: string, table: string, column: string): string | undefined;
39
+ export declare function lookupColumnType(cfg: SqlxJsConfig, schema: string, table: string, column: string): string | undefined;
@@ -65,8 +65,27 @@ function validateConfig(value, path) {
65
65
  const config = value;
66
66
  if (config.jsonbTypes !== undefined)
67
67
  validateStringRecord(config.jsonbTypes, "jsonbTypes", path);
68
+ if (config.columnTypes !== undefined)
69
+ validateStringRecord(config.columnTypes, "columnTypes", path);
68
70
  if (config.customTypes !== undefined)
69
71
  validateStringRecord(config.customTypes, "customTypes", path);
72
+ if (config.jsonbTypes && config.columnTypes) {
73
+ const jsonKeys = Object.keys(config.jsonbTypes);
74
+ const columnKeys = Object.keys(config.columnTypes);
75
+ const conflicts = jsonKeys.filter((jsonKey) => columnKeys.some((columnKey) => jsonKey === columnKey || jsonKey.endsWith(`.${columnKey}`) || columnKey.endsWith(`.${jsonKey}`)));
76
+ if (conflicts.length > 0) {
77
+ throw new Error(`sqlx-js: ${path} maps the same column in jsonbTypes and columnTypes: ${conflicts.join(", ")}`);
78
+ }
79
+ }
80
+ if (config.functionCatalog !== undefined && config.functionCatalog !== false) {
81
+ if (!config.functionCatalog || typeof config.functionCatalog !== "object" || Array.isArray(config.functionCatalog)) {
82
+ throw new Error(`sqlx-js: ${path} functionCatalog must be false or an object`);
83
+ }
84
+ const functionCatalog = config.functionCatalog;
85
+ if (functionCatalog.includeExtensionOwned !== undefined && typeof functionCatalog.includeExtensionOwned !== "boolean") {
86
+ throw new Error(`sqlx-js: ${path} functionCatalog.includeExtensionOwned must be a boolean`);
87
+ }
88
+ }
70
89
  if (config.scan !== undefined) {
71
90
  if (!config.scan || typeof config.scan !== "object" || Array.isArray(config.scan)) {
72
91
  throw new Error(`sqlx-js: ${path} scan must be an object`);
@@ -100,7 +119,11 @@ function validateConfig(value, path) {
100
119
  export function prepareConfigHash(cfg) {
101
120
  const value = stableValue({
102
121
  jsonbTypes: cfg.jsonbTypes ?? {},
122
+ columnTypes: cfg.columnTypes ?? {},
103
123
  customTypes: cfg.customTypes ?? {},
124
+ functionCatalog: cfg.functionCatalog === false
125
+ ? false
126
+ : { includeExtensionOwned: cfg.functionCatalog?.includeExtensionOwned === true },
104
127
  });
105
128
  return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
106
129
  }
@@ -159,3 +182,9 @@ export function lookupJsonbType(cfg, schema, table, column) {
159
182
  return (types[`${schema}.${table}.${column}`] ??
160
183
  types[`${table}.${column}`]);
161
184
  }
185
+ export function lookupColumnType(cfg, schema, table, column) {
186
+ const types = cfg.columnTypes;
187
+ if (!types)
188
+ return undefined;
189
+ return types[`${schema}.${table}.${column}`] ?? types[`${table}.${column}`];
190
+ }
@@ -1,5 +1,6 @@
1
1
  import * as rt from "./postgres-runtime.js";
2
- import type { Typed as TypedFor, TypedFile as TypedFileFor, TypedSql as TypedSqlFor } from "./typed.js";
2
+ import type { TypedFile as TypedFileFor, TypedForRegistry, TypedSqlForRegistry } from "./typed.js";
3
+ import type { QueryParamsFor, QueryResultFor, QueryRowFor } from "./query.js";
3
4
  export interface KnownQueries {
4
5
  }
5
6
  export interface KnownFileQueries {
@@ -22,13 +23,13 @@ export { defineConfig } from "./config.js";
22
23
  export type { ScanConfig, SqlxJsConfig } from "./config.js";
23
24
  export type { SslMode, ConnConfig } from "./pg/wire.js";
24
25
  export { PgError, ConnectionLostError } from "./pg/wire.js";
25
- export { NoRowsError, TooManyRowsError } from "./runtime.js";
26
+ export { NoRowsError, TooManyRowsError, SQLSTATE, isPgError } from "./runtime.js";
26
27
  export type { TransactionOptions, MigrateOptions, OnQueryEvent, OnQueryHook, OnQueryHookError } from "./runtime.js";
27
- export type { ExecuteResult, JsonParameter, PgArrayParameter } from "./runtime.js";
28
+ export type { ExecuteResult, JsonParameter, PgArrayParameter, JsonCompatible, KnownSqlState } from "./runtime.js";
28
29
  export type { PostgresClient, PostgresOptions, CreateClientOptions } from "./postgres-runtime.js";
29
30
  export type TypedFile = TypedFileFor<KnownFileQueries>;
30
- export type TypedSql = TypedSqlFor<KnownQueries, KnownFileQueries>;
31
- export type Typed = TypedFor<KnownQueries, KnownFileQueries, import("./runtime.js").TransactionOptions>;
31
+ export type TypedSql = TypedSqlForRegistry<DefaultQueryRegistry>;
32
+ export type Typed = TypedForRegistry<DefaultQueryRegistry, import("./runtime.js").TransactionOptions>;
32
33
  export type QueryRegistry = {
33
34
  queries: object;
34
35
  fileQueries: object;
@@ -39,11 +40,18 @@ export interface DefaultQueryRegistry {
39
40
  functions: KnownFunctions;
40
41
  }
41
42
  export type SqlClient<Registry extends QueryRegistry = DefaultQueryRegistry> = {
42
- sql: TypedFor<Registry["queries"], Registry["fileQueries"], import("./runtime.js").TransactionOptions>;
43
+ sql: TypedForRegistry<Registry, import("./runtime.js").TransactionOptions>;
43
44
  unsafe: Unsafe;
44
45
  client: import("./postgres-runtime.js").PostgresClient;
45
46
  close: () => Promise<void>;
46
47
  };
48
+ export type SqlExecutor<Registry extends QueryRegistry = DefaultQueryRegistry> = TypedSqlForRegistry<Registry>;
49
+ export type QueryParams<Definition, Registry extends QueryRegistry = DefaultQueryRegistry> = QueryParamsFor<Definition, Registry>;
50
+ export type QueryRow<Definition, Registry extends QueryRegistry = DefaultQueryRegistry> = QueryRowFor<Definition, Registry>;
51
+ export type QueryResult<Definition, Registry extends QueryRegistry = DefaultQueryRegistry> = QueryResultFor<Definition, Registry>;
52
+ export type { QueryDefinition, QueryExecutionMode } from "./query.js";
53
+ export { defineQuery } from "./query.js";
54
+ export { queryId } from "./query-id.js";
47
55
  export declare const sql: Typed;
48
56
  export type Unsafe = (query: string, ...params: unknown[]) => Promise<Record<string, unknown>[]>;
49
57
  export declare const unsafe: Unsafe;
package/dist/src/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as rt from "./postgres-runtime.js";
2
2
  export { defineConfig } from "./config.js";
3
3
  export { PgError, ConnectionLostError } from "./pg/wire.js";
4
- export { NoRowsError, TooManyRowsError } from "./runtime.js";
4
+ export { NoRowsError, TooManyRowsError, SQLSTATE, isPgError } from "./runtime.js";
5
+ export { defineQuery } from "./query.js";
6
+ export { queryId } from "./query-id.js";
5
7
  export const sql = rt.sql;
6
8
  export const unsafe = rt.unsafe;
7
9
  export const getClient = rt.getClient;
@@ -1,4 +1,6 @@
1
1
  import type { FunctionEntry } from "../function-cache.js";
2
2
  import { type PgClient } from "./wire.js";
3
3
  import { SchemaCache } from "./schema.js";
4
- export declare function introspectFunctions(client: PgClient, schema: SchemaCache): Promise<FunctionEntry[]>;
4
+ export declare function introspectFunctions(client: PgClient, schema: SchemaCache, options?: {
5
+ includeExtensionOwned?: boolean;
6
+ }): Promise<FunctionEntry[]>;
@@ -2,8 +2,8 @@ import { decodeText } from "./wire.js";
2
2
  const JSON_OIDS = new Set([114, 3802]);
3
3
  const JSON_ARRAY_OIDS = new Set([199, 3807]);
4
4
  const JSON_INPUT = 'import("@onreza/sqlx-js").JsonInput';
5
- export async function introspectFunctions(client, schema) {
6
- const rows = await loadFunctionRows(client);
5
+ export async function introspectFunctions(client, schema, options = {}) {
6
+ const rows = await loadFunctionRows(client, options.includeExtensionOwned === true);
7
7
  const typeOids = new Set();
8
8
  for (const row of rows) {
9
9
  typeOids.add(row.returnOid);
@@ -15,7 +15,7 @@ export async function introspectFunctions(client, schema) {
15
15
  await schema.loadCustomTypes([...typeOids]);
16
16
  return rows.map((row) => toEntry(row, schema)).sort((a, b) => a.signature.localeCompare(b.signature));
17
17
  }
18
- async function loadFunctionRows(client) {
18
+ async function loadFunctionRows(client, includeExtensionOwned) {
19
19
  const result = await client.simpleQueryAll(`
20
20
  SELECT
21
21
  n.nspname,
@@ -36,6 +36,14 @@ async function loadFunctionRows(client) {
36
36
  FROM pg_proc p
37
37
  JOIN pg_namespace n ON n.oid = p.pronamespace
38
38
  WHERE ${userSchemaFilter("n")}
39
+ ${includeExtensionOwned ? "" : `AND NOT EXISTS (
40
+ SELECT 1
41
+ FROM pg_depend d
42
+ WHERE d.classid = 'pg_proc'::regclass
43
+ AND d.objid = p.oid
44
+ AND d.refclassid = 'pg_extension'::regclass
45
+ AND d.deptype = 'e'
46
+ )`}
39
47
  ORDER BY n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)
40
48
  `);
41
49
  return result.rows.map((row) => ({
@@ -10,6 +10,7 @@ export type CreateClientOptions = PostgresOptions & {
10
10
  statementTimeoutMs?: number;
11
11
  fileRoot?: string;
12
12
  reloadSqlFiles?: boolean;
13
+ sqlFiles?: Readonly<Record<string, string>>;
13
14
  };
14
15
  export declare function createClient(url?: string | undefined, options?: CreateClientOptions): PostgresClient;
15
16
  export declare function getClient(): PostgresClient;
@@ -19,6 +20,7 @@ export declare function setClient(client: PostgresClient, options?: {
19
20
  prepare?: boolean;
20
21
  fileRoot?: string;
21
22
  reloadSqlFiles?: boolean;
23
+ sqlFiles?: Readonly<Record<string, string>>;
22
24
  }): void;
23
25
  export declare function close(): Promise<void>;
24
26
  export declare function createSqlClient(url?: string | undefined, options?: CreateClientOptions): {
@@ -13,13 +13,15 @@ class PostgresRuntimeClient {
13
13
  prepare;
14
14
  fileRoot;
15
15
  reloadSqlFiles;
16
- constructor(client, onQuery, onQueryHookError, prepare = true, fileRoot = resolvedFileRoot(), reloadSqlFiles = false) {
16
+ sqlFiles;
17
+ constructor(client, onQuery, onQueryHookError, prepare = true, fileRoot = resolvedFileRoot(), reloadSqlFiles = false, sqlFiles) {
17
18
  this.client = client;
18
19
  this.onQuery = onQuery;
19
20
  this.onQueryHookError = onQueryHookError;
20
21
  this.prepare = prepare;
21
22
  this.fileRoot = fileRoot;
22
23
  this.reloadSqlFiles = reloadSqlFiles;
24
+ this.sqlFiles = sqlFiles;
23
25
  }
24
26
  async query(query, params) {
25
27
  return await this.client.unsafe(query, params, { prepare: this.prepare });
@@ -43,7 +45,7 @@ class PostgresRuntimeClient {
43
45
  if (!("begin" in this.client))
44
46
  throw new Error("sqlx-js.transaction: nested transactions are not supported");
45
47
  return await this.client.begin(async (tx) => {
46
- return await fn(new PostgresRuntimeClient(tx, this.onQuery, this.onQueryHookError, this.prepare, this.fileRoot, this.reloadSqlFiles));
48
+ return await fn(new PostgresRuntimeClient(tx, this.onQuery, this.onQueryHookError, this.prepare, this.fileRoot, this.reloadSqlFiles, this.sqlFiles));
47
49
  });
48
50
  }
49
51
  async close() {
@@ -162,7 +164,7 @@ function installJsonArrayCodecs(client) {
162
164
  export function createClient(url = process.env.DATABASE_URL, options = {}) {
163
165
  if (!url)
164
166
  throw new Error("sqlx-js: DATABASE_URL is not set");
165
- const { onQuery, onQueryHookError, statementTimeoutMs, fileRoot, reloadSqlFiles, ...pgOptions } = options;
167
+ const { onQuery, onQueryHookError, statementTimeoutMs, fileRoot, reloadSqlFiles, sqlFiles, ...pgOptions } = options;
166
168
  const connection = statementTimeoutMs !== undefined
167
169
  ? { ...(pgOptions.connection ?? {}), statement_timeout: statementTimeoutMs }
168
170
  : pgOptions.connection;
@@ -177,13 +179,14 @@ export function createClient(url = process.env.DATABASE_URL, options = {}) {
177
179
  prepare: pgOptions.prepare ?? true,
178
180
  fileRoot: resolvedFileRoot(fileRoot),
179
181
  reloadSqlFiles: reloadSqlFiles ?? false,
182
+ sqlFiles,
180
183
  };
181
184
  return client;
182
185
  }
183
186
  function createDefaultClient() {
184
187
  const client = createClient();
185
188
  const attached = client[HOOKS];
186
- return new PostgresRuntimeClient(client, attached.onQuery, attached.onQueryHookError, attached.prepare, attached.fileRoot, attached.reloadSqlFiles);
189
+ return new PostgresRuntimeClient(client, attached.onQuery, attached.onQueryHookError, attached.prepare, attached.fileRoot, attached.reloadSqlFiles, attached.sqlFiles);
187
190
  }
188
191
  function getRuntimeClient() {
189
192
  defaultClient ??= createDefaultClient();
@@ -195,7 +198,7 @@ export function getClient() {
195
198
  export function setClient(client, options) {
196
199
  installJsonArrayCodecs(client);
197
200
  const attached = client[HOOKS];
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);
201
+ 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, options?.sqlFiles ?? attached?.sqlFiles);
199
202
  }
200
203
  export async function close() {
201
204
  if (defaultClient) {
@@ -206,7 +209,7 @@ export async function close() {
206
209
  export function createSqlClient(url = process.env.DATABASE_URL, options = {}) {
207
210
  const client = createClient(url, options);
208
211
  const attached = client[HOOKS];
209
- const runtimeClient = new PostgresRuntimeClient(client, attached.onQuery, attached.onQueryHookError, attached.prepare, attached.fileRoot, attached.reloadSqlFiles);
212
+ const runtimeClient = new PostgresRuntimeClient(client, attached.onQuery, attached.onQueryHookError, attached.prepare, attached.fileRoot, attached.reloadSqlFiles, attached.sqlFiles);
210
213
  const runtime = createSqlRuntime(() => runtimeClient);
211
214
  return {
212
215
  ...runtime,
@@ -0,0 +1 @@
1
+ export declare function queryId(query: string): string;