@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
@@ -1,18 +1,148 @@
1
- import { existsSync } from "node:fs";
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
3
  import { join } from "node:path";
3
- export async function loadConfig(root) {
4
- for (const name of ["sqlx-js.config.ts", "sqlx-js.config.js", "sqlx-js.config.mjs"]) {
4
+ import { pathToFileURL } from "node:url";
5
+ import { parseEnv } from "node:util";
6
+ export function defineConfig(config) {
7
+ return config;
8
+ }
9
+ export function loadRootEnv(root) {
10
+ const path = join(root, ".env");
11
+ if (!existsSync(path))
12
+ return undefined;
13
+ const parsed = parseEnv(readFileSync(path, "utf8"));
14
+ for (const [key, value] of Object.entries(parsed)) {
15
+ if (process.env[key] === undefined)
16
+ process.env[key] = value;
17
+ }
18
+ return path;
19
+ }
20
+ export function configPath(root) {
21
+ for (const name of ["sqlx-js.config.mts", "sqlx-js.config.ts", "sqlx-js.config.mjs", "sqlx-js.config.js"]) {
5
22
  const p = join(root, name);
6
- if (!existsSync(p))
7
- continue;
8
- const mod = await import(p);
9
- const cfg = (mod.default ?? mod);
10
- if (typeof cfg !== "object" || cfg === null) {
11
- throw new Error(`sqlx-js: ${name} must default-export a config object`);
23
+ if (existsSync(p))
24
+ return p;
25
+ }
26
+ return undefined;
27
+ }
28
+ export async function loadConfig(root) {
29
+ const path = configPath(root);
30
+ if (!path)
31
+ return {};
32
+ const url = pathToFileURL(path);
33
+ url.searchParams.set("mtime", String(statSync(path).mtimeMs));
34
+ const mod = await import(url.href);
35
+ if (!("default" in mod)) {
36
+ throw new Error(`sqlx-js: ${path} must default-export a config object`);
37
+ }
38
+ return validateConfig(mod.default, path);
39
+ }
40
+ function validateStringRecord(value, name, path) {
41
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
42
+ throw new Error(`sqlx-js: ${path} ${name} must be an object of string values`);
43
+ }
44
+ for (const [key, item] of Object.entries(value)) {
45
+ if (typeof item !== "string") {
46
+ throw new Error(`sqlx-js: ${path} ${name}.${key} must be a string`);
47
+ }
48
+ }
49
+ }
50
+ function validateStringArray(value, name, path) {
51
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
52
+ throw new Error(`sqlx-js: ${path} ${name} must be an array of strings`);
53
+ }
54
+ }
55
+ function validateConfig(value, path) {
56
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
57
+ throw new Error(`sqlx-js: ${path} must default-export a config object`);
58
+ }
59
+ const config = value;
60
+ if (config.jsonbTypes !== undefined)
61
+ validateStringRecord(config.jsonbTypes, "jsonbTypes", path);
62
+ if (config.customTypes !== undefined)
63
+ validateStringRecord(config.customTypes, "customTypes", path);
64
+ if (config.scan !== undefined) {
65
+ if (!config.scan || typeof config.scan !== "object" || Array.isArray(config.scan)) {
66
+ throw new Error(`sqlx-js: ${path} scan must be an object`);
12
67
  }
13
- return cfg;
68
+ const scan = config.scan;
69
+ if (scan.include !== undefined)
70
+ validateStringArray(scan.include, "scan.include", path);
71
+ if (scan.exclude !== undefined)
72
+ validateStringArray(scan.exclude, "scan.exclude", path);
14
73
  }
15
- return {};
74
+ if (config.schema !== undefined) {
75
+ if (!config.schema || typeof config.schema !== "object" || Array.isArray(config.schema)) {
76
+ throw new Error(`sqlx-js: ${path} schema must be an object`);
77
+ }
78
+ const schema = config.schema;
79
+ if (schema.provider !== undefined && schema.provider !== "builtin" && schema.provider !== "pgschema") {
80
+ throw new Error(`sqlx-js: ${path} schema.provider must be builtin or pgschema`);
81
+ }
82
+ for (const key of ["file", "command"]) {
83
+ if (schema[key] !== undefined && typeof schema[key] !== "string") {
84
+ throw new Error(`sqlx-js: ${path} schema.${key} must be a string`);
85
+ }
86
+ }
87
+ if (schema.schemas !== undefined)
88
+ validateStringArray(schema.schemas, "schema.schemas", path);
89
+ }
90
+ return value;
91
+ }
92
+ export function prepareConfigHash(cfg) {
93
+ const value = stableValue({
94
+ jsonbTypes: cfg.jsonbTypes ?? {},
95
+ customTypes: cfg.customTypes ?? {},
96
+ });
97
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
98
+ }
99
+ export function configHash(cfg) {
100
+ return createHash("sha256").update(JSON.stringify(stableValue(cfg))).digest("hex").slice(0, 16);
101
+ }
102
+ function stableValue(value) {
103
+ if (Array.isArray(value))
104
+ return value.map(stableValue);
105
+ if (!value || typeof value !== "object")
106
+ return value;
107
+ return Object.fromEntries(Object.entries(value)
108
+ .sort(([a], [b]) => a.localeCompare(b))
109
+ .map(([key, item]) => [key, stableValue(item)]));
110
+ }
111
+ export async function loadConfigInfo(root) {
112
+ const path = configPath(root);
113
+ if (!path)
114
+ return { config: {} };
115
+ return { config: await loadConfig(root), path };
116
+ }
117
+ export function assertSupportedRuntime() {
118
+ const bun = process.versions.bun;
119
+ if (bun) {
120
+ if (majorMinorLessThan(bun, 1, 3)) {
121
+ throw new Error(`sqlx-js requires Bun >=1.3, current ${bun}`);
122
+ }
123
+ return;
124
+ }
125
+ const node = process.versions.node;
126
+ if (majorMinorLessThan(node, 24, 0)) {
127
+ throw new Error(`sqlx-js requires Node.js >=24, current ${node}`);
128
+ }
129
+ }
130
+ function majorMinorLessThan(version, minMajor, minMinor) {
131
+ const [major = 0, minor = 0] = version.split(".").map(Number);
132
+ return major < minMajor || (major === minMajor && minor < minMinor);
133
+ }
134
+ export function runtimeVersion() {
135
+ if (process.versions.bun)
136
+ return { runtime: "bun", version: process.versions.bun };
137
+ return { runtime: "node", version: process.versions.node };
138
+ }
139
+ export function nativeTypeScriptEnabled() {
140
+ if (process.versions.bun)
141
+ return true;
142
+ return process.features.typescript;
143
+ }
144
+ export function envFilePath(root) {
145
+ return join(root, ".env");
16
146
  }
17
147
  export function lookupJsonbType(cfg, schema, table, column) {
18
148
  const types = cfg.jsonbTypes;
@@ -0,0 +1,18 @@
1
+ export type FunctionKind = "function" | "procedure" | "aggregate" | "window";
2
+ export type FunctionParamMode = "in" | "out" | "inout" | "variadic" | "table";
3
+ export type FunctionParamEntry = {
4
+ mode: FunctionParamMode;
5
+ tsType: string;
6
+ name?: string;
7
+ };
8
+ export type FunctionEntry = {
9
+ schema: string;
10
+ name: string;
11
+ signature: string;
12
+ kind: FunctionKind;
13
+ params: FunctionParamEntry[];
14
+ returns: string;
15
+ returnsSet: boolean;
16
+ };
17
+ export declare function readFunctionCache(cacheDir: string): FunctionEntry[];
18
+ export declare function writeFunctionCache(cacheDir: string, functions: FunctionEntry[]): void;
@@ -0,0 +1,38 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { randomBytes } from "node:crypto";
4
+ function cachePath(cacheDir) {
5
+ return join(cacheDir, "functions", "functions.json");
6
+ }
7
+ function parseFunctionCache(raw) {
8
+ if (!raw || typeof raw !== "object")
9
+ return [];
10
+ const obj = raw;
11
+ if (obj.version !== 1 || !Array.isArray(obj.functions))
12
+ return [];
13
+ return obj.functions;
14
+ }
15
+ export function readFunctionCache(cacheDir) {
16
+ const path = cachePath(cacheDir);
17
+ if (!existsSync(path))
18
+ return [];
19
+ const text = readFileSync(path, "utf8");
20
+ return parseFunctionCache(JSON.parse(text));
21
+ }
22
+ export function writeFunctionCache(cacheDir, functions) {
23
+ const path = cachePath(cacheDir);
24
+ mkdirSync(dirname(path), { recursive: true });
25
+ const payload = { version: 1, functions };
26
+ const tmp = `${path}.tmp-${randomBytes(4).toString("hex")}`;
27
+ writeFileSync(tmp, JSON.stringify(payload, null, 2));
28
+ try {
29
+ renameSync(tmp, path);
30
+ }
31
+ catch (err) {
32
+ try {
33
+ unlinkSync(tmp);
34
+ }
35
+ catch { }
36
+ throw err;
37
+ }
38
+ }
@@ -4,6 +4,8 @@ export interface KnownQueries {
4
4
  }
5
5
  export interface KnownFileQueries {
6
6
  }
7
+ export interface KnownFunctions {
8
+ }
7
9
  export type JsonPrimitive = string | number | boolean | null;
8
10
  export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
9
11
  export type JsonObject = {
@@ -16,11 +18,13 @@ export type JsonInputObject = {
16
18
  readonly [key: string]: JsonInputValue | undefined;
17
19
  };
18
20
  export type JsonInputArray = readonly JsonInputValue[];
19
- export type { SqlxJsConfig } from "./config.js";
21
+ export { defineConfig } from "./config.js";
22
+ export type { ScanConfig, SqlxJsConfig } from "./config.js";
20
23
  export type { SslMode, ConnConfig } from "./pg/wire.js";
21
24
  export { PgError, ConnectionLostError } from "./pg/wire.js";
22
25
  export { NoRowsError, TooManyRowsError } from "./runtime.js";
23
26
  export type { TransactionOptions, MigrateOptions, OnQueryEvent, OnQueryHook } from "./runtime.js";
27
+ export type { ExecuteResult, JsonParameter, PgArrayParameter } from "./runtime.js";
24
28
  export type { PostgresClient, PostgresOptions, CreateClientOptions } from "./postgres-runtime.js";
25
29
  export type TypedFile = TypedFileFor<KnownFileQueries>;
26
30
  export type TypedSql = TypedSqlFor<KnownQueries, KnownFileQueries>;
@@ -32,4 +36,4 @@ export declare const getClient: typeof rt.getClient;
32
36
  export declare const setClient: typeof rt.setClient;
33
37
  export declare const createClient: typeof rt.createClient;
34
38
  export declare const close: typeof rt.close;
35
- export { migrate, clearSqlFileCache, encodePgArrayLiteral, id } from "./runtime.js";
39
+ export { migrate, clearSqlFileCache, encodePgArrayLiteral, id, json, array } from "./runtime.js";
package/dist/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as rt from "./postgres-runtime.js";
2
+ export { defineConfig } from "./config.js";
2
3
  export { PgError, ConnectionLostError } from "./pg/wire.js";
3
4
  export { NoRowsError, TooManyRowsError } from "./runtime.js";
4
5
  export const sql = rt.sql;
@@ -7,4 +8,4 @@ export const getClient = rt.getClient;
7
8
  export const setClient = rt.setClient;
8
9
  export const createClient = rt.createClient;
9
10
  export const close = rt.close;
10
- export { migrate, clearSqlFileCache, encodePgArrayLiteral, id } from "./runtime.js";
11
+ export { migrate, clearSqlFileCache, encodePgArrayLiteral, id, json, array } from "./runtime.js";
@@ -0,0 +1,4 @@
1
+ import type { FunctionEntry } from "../function-cache.js";
2
+ import { type PgClient } from "./wire.js";
3
+ import { SchemaCache } from "./schema.js";
4
+ export declare function introspectFunctions(client: PgClient, schema: SchemaCache): Promise<FunctionEntry[]>;
@@ -0,0 +1,150 @@
1
+ import { decodeText } from "./wire.js";
2
+ const JSON_OIDS = new Set([114, 3802]);
3
+ const JSON_ARRAY_OIDS = new Set([199, 3807]);
4
+ const JSON_INPUT = 'import("@onreza/sqlx-js").JsonInput';
5
+ export async function introspectFunctions(client, schema) {
6
+ const rows = await loadFunctionRows(client);
7
+ const typeOids = new Set();
8
+ for (const row of rows) {
9
+ typeOids.add(row.returnOid);
10
+ for (const oid of row.inputArgOids)
11
+ typeOids.add(oid);
12
+ for (const oid of row.allArgOids ?? [])
13
+ typeOids.add(oid);
14
+ }
15
+ await schema.loadCustomTypes([...typeOids]);
16
+ return rows.map((row) => toEntry(row, schema)).sort((a, b) => a.signature.localeCompare(b.signature));
17
+ }
18
+ async function loadFunctionRows(client) {
19
+ const result = await client.simpleQueryAll(`
20
+ SELECT
21
+ n.nspname,
22
+ p.proname,
23
+ p.prokind::text,
24
+ pg_get_function_identity_arguments(p.oid),
25
+ to_json(
26
+ CASE
27
+ WHEN p.proargtypes::text = '' THEN ARRAY[]::oid[]
28
+ ELSE string_to_array(p.proargtypes::text, ' ')::oid[]
29
+ END
30
+ )::text,
31
+ to_json(p.proallargtypes)::text,
32
+ to_json(p.proargmodes)::text,
33
+ to_json(p.proargnames)::text,
34
+ p.prorettype::int8,
35
+ p.proretset
36
+ FROM pg_proc p
37
+ JOIN pg_namespace n ON n.oid = p.pronamespace
38
+ WHERE ${userSchemaFilter("n")}
39
+ ORDER BY n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)
40
+ `);
41
+ return result.rows.map((row) => ({
42
+ schema: decodeText(row[0]),
43
+ name: decodeText(row[1]),
44
+ kind: functionKind(decodeText(row[2])),
45
+ identityArguments: decodeText(row[3]) ?? "",
46
+ inputArgOids: parseNumberJsonArray(decodeText(row[4])),
47
+ allArgOids: parseNullableNumberJsonArray(decodeText(row[5] ?? null)),
48
+ argModes: parseNullableStringJsonArray(decodeText(row[6] ?? null)),
49
+ argNames: parseNullableStringJsonArray(decodeText(row[7] ?? null)),
50
+ returnOid: Number(decodeText(row[8])),
51
+ returnsSet: decodeText(row[9]) === "t",
52
+ }));
53
+ }
54
+ function toEntry(row, schema) {
55
+ const allArgOids = row.allArgOids ?? row.inputArgOids;
56
+ const modes = row.argModes ?? allArgOids.map(() => "i");
57
+ const params = allArgOids.map((oid, i) => {
58
+ const mode = paramMode(modes[i]);
59
+ const resultTsType = outputTsType(oid, schema);
60
+ return {
61
+ mode,
62
+ tsType: mode === "out" || mode === "table" ? resultTsType : inputTsType(oid, schema),
63
+ ...(mode === "inout" ? { resultTsType } : {}),
64
+ ...(row.argNames?.[i] ? { name: row.argNames[i] } : {}),
65
+ };
66
+ });
67
+ const output = params.filter((p) => p.mode === "out" || p.mode === "inout" || p.mode === "table");
68
+ return {
69
+ schema: row.schema,
70
+ name: row.name,
71
+ signature: `${row.schema}.${row.name}(${row.identityArguments})`,
72
+ kind: row.kind,
73
+ params: params.map(persistedParam),
74
+ returns: returnTsType(row, output, schema),
75
+ returnsSet: row.returnsSet,
76
+ };
77
+ }
78
+ function persistedParam(param) {
79
+ return {
80
+ mode: param.mode,
81
+ tsType: param.tsType,
82
+ ...(param.name ? { name: param.name } : {}),
83
+ };
84
+ }
85
+ function returnTsType(row, output, schema) {
86
+ if (output.length > 0)
87
+ return outputObject(output);
88
+ if (row.kind === "procedure")
89
+ return "void";
90
+ return nullableReturn(schema.tsType(row.returnOid));
91
+ }
92
+ function outputObject(output) {
93
+ const fields = output.map((p, i) => {
94
+ const name = p.name && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name ?? `column${i + 1}`);
95
+ return `${name}: ${nullableReturn(p.resultTsType ?? p.tsType)}`;
96
+ });
97
+ return `{ ${fields.join("; ")} }`;
98
+ }
99
+ function inputTsType(oid, schema) {
100
+ if (JSON_OIDS.has(oid))
101
+ return JSON_INPUT;
102
+ if (JSON_ARRAY_OIDS.has(oid))
103
+ return `(${JSON_INPUT})[]`;
104
+ return schema.tsType(oid);
105
+ }
106
+ function outputTsType(oid, schema) {
107
+ return schema.tsType(oid);
108
+ }
109
+ function nullableReturn(tsType) {
110
+ if (tsType === "void")
111
+ return tsType;
112
+ return `${tsType} | null`;
113
+ }
114
+ function parseNumberJsonArray(raw) {
115
+ if (!raw)
116
+ return [];
117
+ const parsed = JSON.parse(raw);
118
+ return Array.isArray(parsed) ? parsed.map(Number).filter((n) => Number.isFinite(n)) : [];
119
+ }
120
+ function parseNullableNumberJsonArray(raw) {
121
+ if (raw === null)
122
+ return null;
123
+ return parseNumberJsonArray(raw);
124
+ }
125
+ function parseNullableStringJsonArray(raw) {
126
+ if (raw === null)
127
+ return null;
128
+ const parsed = JSON.parse(raw);
129
+ return Array.isArray(parsed) ? parsed.map((v) => (typeof v === "string" ? v : "")) : [];
130
+ }
131
+ function paramMode(raw) {
132
+ switch (raw) {
133
+ case "o": return "out";
134
+ case "b": return "inout";
135
+ case "v": return "variadic";
136
+ case "t": return "table";
137
+ default: return "in";
138
+ }
139
+ }
140
+ function functionKind(raw) {
141
+ switch (raw) {
142
+ case "p": return "procedure";
143
+ case "a": return "aggregate";
144
+ case "w": return "window";
145
+ default: return "function";
146
+ }
147
+ }
148
+ function userSchemaFilter(alias) {
149
+ return `${alias}.nspname <> 'information_schema' AND ${alias}.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'`;
150
+ }
@@ -42,6 +42,8 @@ const SCALAR = {
42
42
  2950: { ts: "string" },
43
43
  2205: { ts: "string" },
44
44
  2206: { ts: "string" },
45
+ 2249: { ts: "Record<string, unknown>" },
46
+ 2278: { ts: "void" },
45
47
  3220: { ts: "string" },
46
48
  3614: { ts: "string" },
47
49
  3615: { ts: "string" },
@@ -74,4 +74,5 @@ export declare class SchemaCache {
74
74
  loadCustomTypes(typeOids: number[]): Promise<void>;
75
75
  private resolveBaseTs;
76
76
  customType(oid: number): CustomTypeInfo | undefined;
77
+ tsType(oid: number): string;
77
78
  }
@@ -262,6 +262,9 @@ export class SchemaCache {
262
262
  customType(oid) {
263
263
  return this.customTypes.get(oid);
264
264
  }
265
+ tsType(oid) {
266
+ return this.resolveBaseTs(oid) ?? "unknown";
267
+ }
265
268
  }
266
269
  function key(oid, attno) {
267
270
  return `${oid}/${attno}`;
@@ -7,11 +7,14 @@ export type PostgresOptions = postgres.Options<Record<string, postgres.PostgresT
7
7
  export type CreateClientOptions = PostgresOptions & {
8
8
  onQuery?: OnQueryHook;
9
9
  statementTimeoutMs?: number;
10
+ fileRoot?: string;
10
11
  };
11
12
  export declare function createClient(url?: string | undefined, options?: CreateClientOptions): PostgresClient;
12
13
  export declare function getClient(): PostgresClient;
13
14
  export declare function setClient(client: PostgresClient, options?: {
14
15
  onQuery?: OnQueryHook;
16
+ prepare?: boolean;
17
+ fileRoot?: string;
15
18
  }): void;
16
19
  export declare function close(): Promise<void>;
17
20
  export declare const sql: import("./runtime.js").SqlRoot;
@@ -1,28 +1,45 @@
1
1
  import postgres from "postgres";
2
- import { createSqlRuntime, encodeParam, encodePgArrayLiteral, parsePgArrayLiteral } from "./runtime.js";
2
+ import { resolve } from "node:path";
3
+ import { createSqlRuntime, encodePgArrayLiteral, isPrimitiveArrayElement, parameterKind, parsePgArrayLiteral, } from "./runtime.js";
3
4
  import { arrayElementOid, builtinArrayOids } from "./pg/oids.js";
4
5
  const HOOKS = Symbol.for("sqlx-js.hooks");
6
+ function resolvedFileRoot(value) {
7
+ return resolve(value ?? process.env.SQLX_JS_FILE_ROOT ?? process.cwd());
8
+ }
5
9
  class PostgresRuntimeClient {
6
10
  client;
7
11
  onQuery;
8
- constructor(client, onQuery) {
12
+ prepare;
13
+ fileRoot;
14
+ constructor(client, onQuery, prepare = true, fileRoot = resolvedFileRoot()) {
9
15
  this.client = client;
10
16
  this.onQuery = onQuery;
17
+ this.prepare = prepare;
18
+ this.fileRoot = fileRoot;
11
19
  }
12
20
  async query(query, params) {
13
- return await this.client.unsafe(query, params, { prepare: true });
21
+ return await this.client.unsafe(query, params, { prepare: this.prepare });
14
22
  }
15
23
  transformParam(param) {
16
- const elementOid = postgresNativeArrayElementOid(param);
17
- if (elementOid !== undefined)
18
- return this.client.array(param, elementOid);
19
- return encodeParam(param);
24
+ const kind = parameterKind(param);
25
+ if (kind === "json")
26
+ return this.client.json(param.value);
27
+ if (kind === "array") {
28
+ const value = [...param.value];
29
+ if (value.every(isPrimitiveArrayElement))
30
+ return encodePgArrayLiteral(value);
31
+ if (value.every((item) => item === null || parameterKind(item) === "json")) {
32
+ return this.client.array(value, 3807);
33
+ }
34
+ return this.client.array(value);
35
+ }
36
+ return param;
20
37
  }
21
38
  async transaction(fn) {
22
39
  if (!("begin" in this.client))
23
40
  throw new Error("sqlx-js.transaction: nested transactions are not supported");
24
41
  return await this.client.begin(async (tx) => {
25
- return await fn(new PostgresRuntimeClient(tx, this.onQuery));
42
+ return await fn(new PostgresRuntimeClient(tx, this.onQuery, this.prepare, this.fileRoot));
26
43
  });
27
44
  }
28
45
  async close() {
@@ -74,6 +91,7 @@ const STRING_ARRAY_ELEMENT_OIDS = new Set([
74
91
  4451,
75
92
  4536,
76
93
  ]);
94
+ const JSON_ELEMENT_OIDS = new Set([114, 3802]);
77
95
  function parseSimpleArrayElement(oid) {
78
96
  switch (oid) {
79
97
  case 16:
@@ -90,28 +108,21 @@ function parseSimpleArrayElement(oid) {
90
108
  return STRING_ARRAY_ELEMENT_OIDS.has(oid) ? (value) => value : undefined;
91
109
  }
92
110
  }
93
- function postgresNativeArrayElementOid(value) {
94
- if (!Array.isArray(value) || value.length === 0)
95
- return undefined;
96
- let oid;
97
- for (const item of value) {
98
- if (item === null)
99
- continue;
100
- const itemOid = item instanceof Uint8Array ? 17 : undefined;
101
- if (itemOid === undefined)
102
- return undefined;
103
- if (oid !== undefined && oid !== itemOid)
104
- return undefined;
105
- oid = itemOid;
106
- }
107
- return oid;
108
- }
109
111
  function postgresTypes() {
110
112
  const types = { bigint: postgres.BigInt };
111
113
  for (const oid of builtinArrayOids()) {
112
114
  const elementOid = arrayElementOid(oid);
113
115
  if (elementOid === undefined)
114
116
  continue;
117
+ if (JSON_ELEMENT_OIDS.has(elementOid)) {
118
+ types[`array_${oid}`] = {
119
+ to: oid,
120
+ from: [oid],
121
+ serialize: (value) => Array.isArray(value) ? encodePgArrayLiteral(value) : String(value),
122
+ parse: (value) => parsePgArrayLiteral(value, JSON.parse),
123
+ };
124
+ continue;
125
+ }
115
126
  const parseElement = parseSimpleArrayElement(elementOid);
116
127
  if (!parseElement)
117
128
  continue;
@@ -124,10 +135,30 @@ function postgresTypes() {
124
135
  }
125
136
  return types;
126
137
  }
138
+ function typeOids(value) {
139
+ if (value === null || value === undefined)
140
+ return [];
141
+ return Array.isArray(value) ? value : [value];
142
+ }
143
+ function installJsonArrayCodecs(client) {
144
+ const options = client.options;
145
+ options.parsers ??= {};
146
+ options.serializers ??= {};
147
+ const configured = Object.values(options.types ?? {});
148
+ for (const oid of [199, 3807]) {
149
+ const hasParser = configured.some((type) => typeOids(type.from).includes(oid));
150
+ const hasSerializer = configured.some((type) => type.to === oid);
151
+ if (!hasParser)
152
+ options.parsers[oid] = (value) => parsePgArrayLiteral(value, JSON.parse);
153
+ if (!hasSerializer) {
154
+ options.serializers[oid] = (value) => Array.isArray(value) ? encodePgArrayLiteral(value) : String(value);
155
+ }
156
+ }
157
+ }
127
158
  export function createClient(url = process.env.DATABASE_URL, options = {}) {
128
159
  if (!url)
129
160
  throw new Error("sqlx-js: DATABASE_URL is not set");
130
- const { onQuery, statementTimeoutMs, ...pgOptions } = options;
161
+ const { onQuery, statementTimeoutMs, fileRoot, ...pgOptions } = options;
131
162
  const connection = statementTimeoutMs !== undefined
132
163
  ? { ...(pgOptions.connection ?? {}), statement_timeout: statementTimeoutMs }
133
164
  : pgOptions.connection;
@@ -136,12 +167,17 @@ export function createClient(url = process.env.DATABASE_URL, options = {}) {
136
167
  ...(connection ? { connection } : {}),
137
168
  types: { ...postgresTypes(), ...(pgOptions.types ?? {}) },
138
169
  });
139
- if (onQuery)
140
- client[HOOKS] = { onQuery };
170
+ client[HOOKS] = {
171
+ onQuery,
172
+ prepare: pgOptions.prepare ?? true,
173
+ fileRoot: resolvedFileRoot(fileRoot),
174
+ };
141
175
  return client;
142
176
  }
143
177
  function createDefaultClient() {
144
- return new PostgresRuntimeClient(createClient());
178
+ const client = createClient();
179
+ const attached = client[HOOKS];
180
+ return new PostgresRuntimeClient(client, attached.onQuery, attached.prepare, attached.fileRoot);
145
181
  }
146
182
  function getRuntimeClient() {
147
183
  defaultClient ??= createDefaultClient();
@@ -151,8 +187,9 @@ export function getClient() {
151
187
  return getRuntimeClient().client;
152
188
  }
153
189
  export function setClient(client, options) {
190
+ installJsonArrayCodecs(client);
154
191
  const attached = client[HOOKS];
155
- defaultClient = new PostgresRuntimeClient(client, options?.onQuery ?? attached?.onQuery);
192
+ defaultClient = new PostgresRuntimeClient(client, options?.onQuery ?? attached?.onQuery, options?.prepare ?? attached?.prepare ?? client.options?.prepare ?? true, resolvedFileRoot(options?.fileRoot ?? attached?.fileRoot));
156
193
  }
157
194
  export async function close() {
158
195
  if (defaultClient) {