@onreza/sqlx-js 0.0.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +496 -0
  3. package/ROADMAP.md +28 -0
  4. package/dist/bin/sqlx-js.d.ts +2 -0
  5. package/dist/bin/sqlx-js.js +180 -0
  6. package/dist/src/bun-runtime.d.ts +7 -0
  7. package/dist/src/bun-runtime.js +45 -0
  8. package/dist/src/bun.d.ts +22 -0
  9. package/dist/src/bun.js +9 -0
  10. package/dist/src/cache.d.ts +36 -0
  11. package/dist/src/cache.js +104 -0
  12. package/dist/src/codegen.d.ts +2 -0
  13. package/dist/src/codegen.js +74 -0
  14. package/dist/src/commands/migrate.d.ts +63 -0
  15. package/dist/src/commands/migrate.js +283 -0
  16. package/dist/src/commands/prepare.d.ts +23 -0
  17. package/dist/src/commands/prepare.js +314 -0
  18. package/dist/src/commands/schema.d.ts +14 -0
  19. package/dist/src/commands/schema.js +72 -0
  20. package/dist/src/commands/watch.d.ts +21 -0
  21. package/dist/src/commands/watch.js +94 -0
  22. package/dist/src/config.d.ts +6 -0
  23. package/dist/src/config.js +23 -0
  24. package/dist/src/index.d.ts +23 -0
  25. package/dist/src/index.js +10 -0
  26. package/dist/src/pg/analyze.d.ts +13 -0
  27. package/dist/src/pg/analyze.js +479 -0
  28. package/dist/src/pg/extensions.d.ts +2 -0
  29. package/dist/src/pg/extensions.js +15 -0
  30. package/dist/src/pg/narrow.d.ts +3 -0
  31. package/dist/src/pg/narrow.js +146 -0
  32. package/dist/src/pg/oids.d.ts +10 -0
  33. package/dist/src/pg/oids.js +137 -0
  34. package/dist/src/pg/param-map.d.ts +12 -0
  35. package/dist/src/pg/param-map.js +180 -0
  36. package/dist/src/pg/schema.d.ts +61 -0
  37. package/dist/src/pg/schema.js +225 -0
  38. package/dist/src/pg/wire.d.ts +134 -0
  39. package/dist/src/pg/wire.js +705 -0
  40. package/dist/src/postgres-runtime.d.ts +11 -0
  41. package/dist/src/postgres-runtime.js +150 -0
  42. package/dist/src/runtime.d.ts +69 -0
  43. package/dist/src/runtime.js +446 -0
  44. package/dist/src/scan/scanner.d.ts +12 -0
  45. package/dist/src/scan/scanner.js +283 -0
  46. package/dist/src/schema-snapshot.d.ts +104 -0
  47. package/dist/src/schema-snapshot.js +473 -0
  48. package/dist/src/typed.d.ts +25 -0
  49. package/dist/src/typed.js +1 -0
  50. package/package.json +78 -0
@@ -0,0 +1,11 @@
1
+ import postgres from "postgres";
2
+ export type PostgresClient = postgres.Sql<{
3
+ bigint: bigint;
4
+ }>;
5
+ export type PostgresOptions = postgres.Options<Record<string, postgres.PostgresType>>;
6
+ export declare function createClient(url?: string | undefined, options?: PostgresOptions): PostgresClient;
7
+ export declare function getClient(): PostgresClient;
8
+ export declare function setClient(client: PostgresClient): void;
9
+ export declare function close(): Promise<void>;
10
+ export declare const sql: import("./runtime.js").SqlRoot;
11
+ export declare const unsafe: (query: string, ...params: unknown[]) => Promise<Record<string, unknown>[]>;
@@ -0,0 +1,150 @@
1
+ import postgres from "postgres";
2
+ import { createSqlRuntime, encodeParam, encodePgArrayLiteral, parsePgArrayLiteral } from "./runtime.js";
3
+ import { arrayElementOid, builtinArrayOids } from "./pg/oids.js";
4
+ class PostgresRuntimeClient {
5
+ client;
6
+ constructor(client) {
7
+ this.client = client;
8
+ }
9
+ async query(query, params) {
10
+ return await this.client.unsafe(query, params, { prepare: true });
11
+ }
12
+ transformParam(param) {
13
+ const elementOid = postgresNativeArrayElementOid(param);
14
+ if (elementOid !== undefined)
15
+ return this.client.array(param, elementOid);
16
+ return encodeParam(param);
17
+ }
18
+ async transaction(fn) {
19
+ if (!("begin" in this.client))
20
+ throw new Error("sqlx-js.transaction: nested transactions are not supported");
21
+ return await this.client.begin(async (tx) => {
22
+ return await fn(new PostgresRuntimeClient(tx));
23
+ });
24
+ }
25
+ async close() {
26
+ if ("end" in this.client)
27
+ await this.client.end();
28
+ }
29
+ }
30
+ let defaultClient = null;
31
+ const STRING_ARRAY_ELEMENT_OIDS = new Set([
32
+ 18,
33
+ 19,
34
+ 25,
35
+ 27,
36
+ 28,
37
+ 29,
38
+ 142,
39
+ 600,
40
+ 601,
41
+ 602,
42
+ 603,
43
+ 604,
44
+ 628,
45
+ 650,
46
+ 718,
47
+ 774,
48
+ 790,
49
+ 829,
50
+ 869,
51
+ 1042,
52
+ 1043,
53
+ 1083,
54
+ 1186,
55
+ 1266,
56
+ 1560,
57
+ 1562,
58
+ 1700,
59
+ 2950,
60
+ 2205,
61
+ 2206,
62
+ 3220,
63
+ 3614,
64
+ 3615,
65
+ 3904,
66
+ 3906,
67
+ 3908,
68
+ 3910,
69
+ 3912,
70
+ 3926,
71
+ 4451,
72
+ 4536,
73
+ ]);
74
+ function parseSimpleArrayElement(oid) {
75
+ switch (oid) {
76
+ case 16:
77
+ return (value) => value === "t";
78
+ case 20:
79
+ return (value) => BigInt(value);
80
+ case 21:
81
+ case 23:
82
+ case 26:
83
+ case 700:
84
+ case 701:
85
+ return (value) => Number(value);
86
+ default:
87
+ return STRING_ARRAY_ELEMENT_OIDS.has(oid) ? (value) => value : undefined;
88
+ }
89
+ }
90
+ function postgresNativeArrayElementOid(value) {
91
+ if (!Array.isArray(value) || value.length === 0)
92
+ return undefined;
93
+ let oid;
94
+ for (const item of value) {
95
+ if (item === null)
96
+ continue;
97
+ const itemOid = item instanceof Uint8Array ? 17 : undefined;
98
+ if (itemOid === undefined)
99
+ return undefined;
100
+ if (oid !== undefined && oid !== itemOid)
101
+ return undefined;
102
+ oid = itemOid;
103
+ }
104
+ return oid;
105
+ }
106
+ function postgresTypes() {
107
+ const types = { bigint: postgres.BigInt };
108
+ for (const oid of builtinArrayOids()) {
109
+ const elementOid = arrayElementOid(oid);
110
+ if (elementOid === undefined)
111
+ continue;
112
+ const parseElement = parseSimpleArrayElement(elementOid);
113
+ if (!parseElement)
114
+ continue;
115
+ types[`array_${oid}`] = {
116
+ to: oid,
117
+ from: [oid],
118
+ serialize: (value) => Array.isArray(value) ? encodePgArrayLiteral(value) : String(value),
119
+ parse: (value) => parsePgArrayLiteral(value, parseElement),
120
+ };
121
+ }
122
+ return types;
123
+ }
124
+ export function createClient(url = process.env.DATABASE_URL, options = {}) {
125
+ if (!url)
126
+ throw new Error("sqlx-js: DATABASE_URL is not set");
127
+ return postgres(url, { ...options, types: { ...postgresTypes(), ...(options.types ?? {}) } });
128
+ }
129
+ function createDefaultClient() {
130
+ return new PostgresRuntimeClient(createClient());
131
+ }
132
+ function getRuntimeClient() {
133
+ defaultClient ??= createDefaultClient();
134
+ return defaultClient;
135
+ }
136
+ export function getClient() {
137
+ return getRuntimeClient().client;
138
+ }
139
+ export function setClient(client) {
140
+ defaultClient = new PostgresRuntimeClient(client);
141
+ }
142
+ export async function close() {
143
+ if (defaultClient) {
144
+ await defaultClient.close();
145
+ defaultClient = null;
146
+ }
147
+ }
148
+ const runtime = createSqlRuntime(getRuntimeClient);
149
+ export const sql = runtime.sql;
150
+ export const unsafe = runtime.unsafe;
@@ -0,0 +1,69 @@
1
+ export type RuntimeClient = {
2
+ query: (query: string, params: unknown[]) => Promise<unknown[]>;
3
+ transformParam?: (param: unknown) => unknown;
4
+ transaction: <R>(fn: (client: RuntimeClient) => Promise<R>) => Promise<R>;
5
+ close: () => Promise<void>;
6
+ };
7
+ type AnyFn = (...args: unknown[]) => Promise<unknown[]>;
8
+ type AnyOneFn = (...args: unknown[]) => Promise<unknown>;
9
+ type AnyOptionalFn = (...args: unknown[]) => Promise<unknown | null>;
10
+ type IdentifierFn = (...parts: string[]) => string;
11
+ declare function renameRows(rows: unknown[]): unknown[];
12
+ declare function isPrimitiveArrayElement(v: unknown): boolean;
13
+ export declare function encodePgArrayLiteral(arr: unknown[]): string;
14
+ type PgArrayValue<T> = T | null | PgArrayValue<T>[];
15
+ export declare function parsePgArrayLiteral<T = string>(input: string, parseElement?: (value: string) => T): PgArrayValue<T>[];
16
+ export declare function encodeParam(p: unknown): unknown;
17
+ export declare class NoRowsError extends Error {
18
+ constructor(message?: string);
19
+ }
20
+ export declare class TooManyRowsError extends Error {
21
+ actual: number;
22
+ constructor(actual: number, expected?: "1" | "0 or 1");
23
+ }
24
+ export declare const _internal: {
25
+ renameRows: typeof renameRows;
26
+ encodeParam: typeof encodeParam;
27
+ isPrimitiveArrayElement: typeof isPrimitiveArrayElement;
28
+ parsePgArrayLiteral: typeof parsePgArrayLiteral;
29
+ loadSqlFile: typeof loadSqlFile;
30
+ buildSetTransaction: typeof buildSetTransaction;
31
+ clearIdentifierCache: typeof clearIdentifierCache;
32
+ };
33
+ declare function loadSqlFile(path: string): string;
34
+ export declare function clearSqlFileCache(): void;
35
+ declare function clearIdentifierCache(): void;
36
+ export declare function id(...parts: string[]): string;
37
+ type FileCallable = AnyFn & {
38
+ one: AnyOneFn;
39
+ optional: AnyOptionalFn;
40
+ };
41
+ type SqlCallable = AnyFn & {
42
+ file: FileCallable;
43
+ one: AnyOneFn;
44
+ optional: AnyOptionalFn;
45
+ id: IdentifierFn;
46
+ };
47
+ export type TransactionOptions = {
48
+ isolation?: "read uncommitted" | "read committed" | "repeatable read" | "serializable";
49
+ readOnly?: boolean;
50
+ deferrable?: boolean;
51
+ };
52
+ export type SqlRoot = SqlCallable & {
53
+ transaction: <R>(fnOrOpts: TransactionOptions | ((tx: SqlCallable) => Promise<R>), fn?: (tx: SqlCallable) => Promise<R>) => Promise<R>;
54
+ };
55
+ declare function buildSetTransaction(opts: TransactionOptions): string;
56
+ export type RuntimeApi = {
57
+ sql: SqlRoot;
58
+ unsafe: (query: string, ...params: unknown[]) => Promise<Record<string, unknown>[]>;
59
+ };
60
+ export declare function createSqlRuntime(getClient: () => RuntimeClient): RuntimeApi;
61
+ export type MigrateOptions = {
62
+ dir?: string;
63
+ databaseUrl?: string;
64
+ log?: (msg: string) => void;
65
+ lockKey?: number | bigint;
66
+ lockTimeoutMs?: number;
67
+ };
68
+ export declare function migrate(opts?: MigrateOptions): Promise<void>;
69
+ export {};
@@ -0,0 +1,446 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { PgClient, parseDatabaseUrl } from "./pg/wire.js";
4
+ import { applyPending, acquireMigrateLock, releaseMigrateLock, DEFAULT_MIGRATE_LOCK_KEY } from "./commands/migrate.js";
5
+ const SUFFIX = /[!?]$/;
6
+ function renameRows(rows) {
7
+ if (rows.length === 0)
8
+ return rows;
9
+ const first = rows[0];
10
+ if (first === null || typeof first !== "object")
11
+ return rows;
12
+ const rename = new Map();
13
+ for (const k of Object.keys(first)) {
14
+ if (SUFFIX.test(k))
15
+ rename.set(k, k.slice(0, -1));
16
+ }
17
+ if (rename.size === 0)
18
+ return rows;
19
+ const out = new Array(rows.length);
20
+ for (let i = 0; i < rows.length; i++) {
21
+ const r = rows[i];
22
+ const copy = {};
23
+ for (const k in r) {
24
+ const dst = rename.get(k);
25
+ copy[dst ?? k] = r[k];
26
+ }
27
+ out[i] = copy;
28
+ }
29
+ return out;
30
+ }
31
+ function isPrimitiveArrayElement(v) {
32
+ if (v === null || v === undefined)
33
+ return true;
34
+ const t = typeof v;
35
+ return t === "string" || t === "number" || t === "bigint" || t === "boolean";
36
+ }
37
+ function quoteArrayElement(raw) {
38
+ return '"' + raw.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
39
+ }
40
+ export function encodePgArrayLiteral(arr) {
41
+ const parts = [];
42
+ for (const v of arr) {
43
+ if (v === null || v === undefined) {
44
+ parts.push("NULL");
45
+ continue;
46
+ }
47
+ if (typeof v === "bigint") {
48
+ parts.push(v.toString());
49
+ continue;
50
+ }
51
+ if (typeof v === "number") {
52
+ parts.push(Number.isFinite(v) ? String(v) : quoteArrayElement(String(v)));
53
+ continue;
54
+ }
55
+ if (typeof v === "boolean") {
56
+ parts.push(v ? "t" : "f");
57
+ continue;
58
+ }
59
+ const s = String(v);
60
+ if (s === "" || /[\\"{},\s]/.test(s) || s.toLowerCase() === "null") {
61
+ parts.push(quoteArrayElement(s));
62
+ }
63
+ else {
64
+ parts.push(s);
65
+ }
66
+ }
67
+ return "{" + parts.join(",") + "}";
68
+ }
69
+ export function parsePgArrayLiteral(input, parseElement = (value) => value) {
70
+ let i = 0;
71
+ const parseQuoted = () => {
72
+ i++;
73
+ let out = "";
74
+ while (i < input.length) {
75
+ const ch = input[i++];
76
+ if (ch === '"')
77
+ return parseElement(out);
78
+ if (ch === "\\") {
79
+ if (i < input.length)
80
+ out += input[i++];
81
+ }
82
+ else {
83
+ out += ch;
84
+ }
85
+ }
86
+ throw new Error("sqlx-js: malformed PostgreSQL array literal");
87
+ };
88
+ const parseUnquoted = () => {
89
+ const start = i;
90
+ while (i < input.length && input[i] !== "," && input[i] !== "}")
91
+ i++;
92
+ const raw = input.slice(start, i);
93
+ return raw === "NULL" ? null : parseElement(raw);
94
+ };
95
+ const parseArray = () => {
96
+ if (input[i] !== "{")
97
+ throw new Error("sqlx-js: malformed PostgreSQL array literal");
98
+ i++;
99
+ const out = [];
100
+ while (i < input.length) {
101
+ if (input[i] === "}") {
102
+ i++;
103
+ return out;
104
+ }
105
+ const value = input[i] === "{"
106
+ ? parseArray()
107
+ : input[i] === '"'
108
+ ? parseQuoted()
109
+ : parseUnquoted();
110
+ out.push(value);
111
+ if (input[i] === ",") {
112
+ i++;
113
+ continue;
114
+ }
115
+ if (input[i] === "}")
116
+ continue;
117
+ if (i >= input.length)
118
+ break;
119
+ throw new Error("sqlx-js: malformed PostgreSQL array literal");
120
+ }
121
+ throw new Error("sqlx-js: malformed PostgreSQL array literal");
122
+ };
123
+ const parsed = parseArray();
124
+ if (i !== input.length)
125
+ throw new Error("sqlx-js: malformed PostgreSQL array literal");
126
+ return parsed;
127
+ }
128
+ 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);
136
+ }
137
+ export class NoRowsError extends Error {
138
+ constructor(message = "expected exactly 1 row, got 0") {
139
+ super(message);
140
+ this.name = "NoRowsError";
141
+ }
142
+ }
143
+ export class TooManyRowsError extends Error {
144
+ actual;
145
+ constructor(actual, expected = "1") {
146
+ super(`expected ${expected} row${expected === "1" ? "" : "s"}, got ${actual}`);
147
+ this.name = "TooManyRowsError";
148
+ this.actual = actual;
149
+ }
150
+ }
151
+ export const _internal = {
152
+ renameRows,
153
+ encodeParam,
154
+ isPrimitiveArrayElement,
155
+ parsePgArrayLiteral,
156
+ loadSqlFile,
157
+ buildSetTransaction,
158
+ clearIdentifierCache,
159
+ };
160
+ async function runQuery(client, query, params) {
161
+ const encoded = params.length === 0
162
+ ? params
163
+ : params.map((p) => client.transformParam ? client.transformParam(p) : encodeParam(p));
164
+ const rows = await client.query(query, encoded);
165
+ return renameRows(rows);
166
+ }
167
+ async function runOne(client, query, params) {
168
+ const rows = await runQuery(client, query, params);
169
+ if (rows.length === 1)
170
+ return rows[0];
171
+ if (rows.length === 0)
172
+ throw new NoRowsError();
173
+ throw new TooManyRowsError(rows.length, "1");
174
+ }
175
+ async function runOptional(client, query, params) {
176
+ const rows = await runQuery(client, query, params);
177
+ if (rows.length === 0)
178
+ return null;
179
+ if (rows.length === 1)
180
+ return rows[0];
181
+ throw new TooManyRowsError(rows.length, "0 or 1");
182
+ }
183
+ const sqlFileCache = new Map();
184
+ function loadSqlFile(path) {
185
+ const full = resolve(process.cwd(), path);
186
+ try {
187
+ const st = statSync(full);
188
+ const cached = sqlFileCache.get(full);
189
+ if (cached && cached.mtimeMs === st.mtimeMs && cached.size === st.size) {
190
+ return cached.content;
191
+ }
192
+ const content = readFileSync(full, "utf8");
193
+ sqlFileCache.set(full, { mtimeMs: st.mtimeMs, size: st.size, content });
194
+ return content;
195
+ }
196
+ catch (err) {
197
+ throw new Error(`sqlx-js.sql.file: cannot read ${path}: ${err.message}`);
198
+ }
199
+ }
200
+ export function clearSqlFileCache() {
201
+ sqlFileCache.clear();
202
+ }
203
+ let identifierCache = null;
204
+ function clearIdentifierCache() {
205
+ identifierCache = null;
206
+ }
207
+ function identifierSnapshotPath() {
208
+ return process.env.SQLX_JS_SCHEMA_PATH
209
+ ? resolve(process.cwd(), process.env.SQLX_JS_SCHEMA_PATH)
210
+ : resolve(process.cwd(), ".sqlx-js/schema/schema.json");
211
+ }
212
+ function addPath(whitelist, parts) {
213
+ for (const part of parts)
214
+ whitelist.names.add(part);
215
+ whitelist.paths.add(parts.join("\0"));
216
+ }
217
+ function asRecord(value) {
218
+ return value && typeof value === "object" ? value : null;
219
+ }
220
+ function arrayProp(obj, key) {
221
+ const value = obj?.[key];
222
+ return Array.isArray(value) ? value : [];
223
+ }
224
+ function stringProp(obj, key) {
225
+ const value = obj?.[key];
226
+ return typeof value === "string" ? value : undefined;
227
+ }
228
+ function buildIdentifierWhitelist(snapshot) {
229
+ const whitelist = { names: new Set(), paths: new Set() };
230
+ const root = asRecord(snapshot);
231
+ for (const schema of arrayProp(root, "schemas")) {
232
+ if (typeof schema === "string")
233
+ whitelist.names.add(schema);
234
+ }
235
+ for (const relRaw of arrayProp(root, "relations")) {
236
+ const rel = asRecord(relRaw);
237
+ const schema = stringProp(rel, "schema");
238
+ const name = stringProp(rel, "name");
239
+ if (!schema || !name)
240
+ continue;
241
+ addPath(whitelist, [schema, name]);
242
+ for (const colRaw of arrayProp(rel, "columns")) {
243
+ const colName = stringProp(asRecord(colRaw), "name");
244
+ if (!colName)
245
+ continue;
246
+ whitelist.names.add(colName);
247
+ addPath(whitelist, [name, colName]);
248
+ addPath(whitelist, [schema, name, colName]);
249
+ }
250
+ for (const idxRaw of arrayProp(rel, "indexes")) {
251
+ const idxName = stringProp(asRecord(idxRaw), "name");
252
+ if (idxName)
253
+ addPath(whitelist, [schema, idxName]);
254
+ }
255
+ for (const constraintRaw of arrayProp(rel, "constraints")) {
256
+ const constraintName = stringProp(asRecord(constraintRaw), "name");
257
+ if (constraintName) {
258
+ addPath(whitelist, [schema, constraintName]);
259
+ addPath(whitelist, [name, constraintName]);
260
+ addPath(whitelist, [schema, name, constraintName]);
261
+ }
262
+ }
263
+ }
264
+ for (const typeRaw of arrayProp(root, "types")) {
265
+ const t = asRecord(typeRaw);
266
+ const schema = stringProp(t, "schema");
267
+ const name = stringProp(t, "name");
268
+ if (schema && name)
269
+ addPath(whitelist, [schema, name]);
270
+ }
271
+ for (const fnRaw of arrayProp(root, "functions")) {
272
+ const fn = asRecord(fnRaw);
273
+ const schema = stringProp(fn, "schema");
274
+ const name = stringProp(fn, "name");
275
+ if (schema && name)
276
+ addPath(whitelist, [schema, name]);
277
+ }
278
+ return whitelist;
279
+ }
280
+ function loadIdentifierWhitelist() {
281
+ const path = identifierSnapshotPath();
282
+ if (!existsSync(path)) {
283
+ throw new Error(`sqlx-js.id: schema snapshot not found at ${path}. Run \`sqlx-js schema dump\`.`);
284
+ }
285
+ const st = statSync(path);
286
+ if (identifierCache && identifierCache.path === path && identifierCache.mtimeMs === st.mtimeMs && identifierCache.size === st.size) {
287
+ return identifierCache.whitelist;
288
+ }
289
+ const snapshot = JSON.parse(readFileSync(path, "utf8"));
290
+ const whitelist = buildIdentifierWhitelist(snapshot);
291
+ identifierCache = { path, mtimeMs: st.mtimeMs, size: st.size, whitelist };
292
+ return whitelist;
293
+ }
294
+ function quoteIdentifier(part) {
295
+ if (part.length === 0)
296
+ throw new Error("sqlx-js.id: identifier segment must not be empty");
297
+ if (part.includes("\0"))
298
+ throw new Error("sqlx-js.id: identifier segment must not contain NUL");
299
+ return `"${part.replace(/"/g, '""')}"`;
300
+ }
301
+ export function id(...parts) {
302
+ if (parts.length === 0)
303
+ throw new Error("sqlx-js.id: at least one identifier segment is required");
304
+ if (parts.length > 3)
305
+ throw new Error("sqlx-js.id: expected 1 to 3 identifier segments");
306
+ const whitelist = loadIdentifierWhitelist();
307
+ const ok = parts.length === 1
308
+ ? whitelist.names.has(parts[0])
309
+ : whitelist.paths.has(parts.join("\0"));
310
+ if (!ok) {
311
+ throw new Error(`sqlx-js.id: identifier is not present in schema snapshot: ${parts.join(".")}`);
312
+ }
313
+ return parts.map(quoteIdentifier).join(".");
314
+ }
315
+ function makeBoundCallable(client) {
316
+ const fn = (async (query, ...params) => {
317
+ return runQuery(client, query, params);
318
+ });
319
+ const file = (async (path, ...params) => {
320
+ return runQuery(client, loadSqlFile(path), params);
321
+ });
322
+ file.one = (async (path, ...params) => {
323
+ return runOne(client, loadSqlFile(path), params);
324
+ });
325
+ file.optional = (async (path, ...params) => {
326
+ return runOptional(client, loadSqlFile(path), params);
327
+ });
328
+ fn.file = file;
329
+ fn.one = (async (query, ...params) => {
330
+ return runOne(client, query, params);
331
+ });
332
+ fn.optional = (async (query, ...params) => {
333
+ return runOptional(client, query, params);
334
+ });
335
+ fn.id = id;
336
+ return fn;
337
+ }
338
+ function buildSetTransaction(opts) {
339
+ const parts = [];
340
+ if (opts.isolation)
341
+ parts.push(`ISOLATION LEVEL ${opts.isolation.toUpperCase()}`);
342
+ if (opts.readOnly !== undefined)
343
+ parts.push(opts.readOnly ? "READ ONLY" : "READ WRITE");
344
+ if (opts.deferrable !== undefined)
345
+ parts.push(opts.deferrable ? "DEFERRABLE" : "NOT DEFERRABLE");
346
+ if (parts.length === 0)
347
+ return "";
348
+ return `SET TRANSACTION ${parts.join(" ")}`;
349
+ }
350
+ export function createSqlRuntime(getClient) {
351
+ const root = (async (query, ...params) => {
352
+ return runQuery(getClient(), query, params);
353
+ });
354
+ const rootFile = (async (path, ...params) => {
355
+ return runQuery(getClient(), loadSqlFile(path), params);
356
+ });
357
+ rootFile.one = (async (path, ...params) => {
358
+ return runOne(getClient(), loadSqlFile(path), params);
359
+ });
360
+ rootFile.optional = (async (path, ...params) => {
361
+ return runOptional(getClient(), loadSqlFile(path), params);
362
+ });
363
+ root.file = rootFile;
364
+ root.one = (async (query, ...params) => {
365
+ return runOne(getClient(), query, params);
366
+ });
367
+ root.optional = (async (query, ...params) => {
368
+ return runOptional(getClient(), query, params);
369
+ });
370
+ root.id = id;
371
+ root.transaction = (async (fnOrOpts, maybeFn) => {
372
+ const c = getClient();
373
+ let opts = {};
374
+ let cb;
375
+ if (typeof fnOrOpts === "function") {
376
+ cb = fnOrOpts;
377
+ }
378
+ else {
379
+ opts = fnOrOpts;
380
+ if (!maybeFn)
381
+ throw new Error("sqlx-js.transaction: callback is required");
382
+ cb = maybeFn;
383
+ }
384
+ const setTx = buildSetTransaction(opts);
385
+ return await c.transaction(async (txClient) => {
386
+ if (setTx)
387
+ await txClient.query(setTx, []);
388
+ const tx = makeBoundCallable(txClient);
389
+ return await cb(tx);
390
+ });
391
+ });
392
+ const unsafe = (async (query, ...params) => {
393
+ return (await runQuery(getClient(), query, params));
394
+ });
395
+ return { sql: root, unsafe };
396
+ }
397
+ function normalizeLockKey(lockKey) {
398
+ if (typeof lockKey === "bigint")
399
+ return lockKey;
400
+ if (!Number.isSafeInteger(lockKey)) {
401
+ throw new Error(`sqlx-js.migrate: lockKey must be a safe integer or bigint, got ${lockKey}`);
402
+ }
403
+ return BigInt(lockKey);
404
+ }
405
+ export async function migrate(opts = {}) {
406
+ const url = opts.databaseUrl ?? process.env.DATABASE_URL;
407
+ if (!url)
408
+ throw new Error("sqlx-js.migrate: DATABASE_URL is required");
409
+ const dir = opts.dir ?? "migrations";
410
+ const log = opts.log ?? ((m) => console.log(`[sqlx-js] ${m}`));
411
+ const lockKey = normalizeLockKey(opts.lockKey ?? DEFAULT_MIGRATE_LOCK_KEY);
412
+ const cfg = parseDatabaseUrl(url);
413
+ const client = new PgClient(cfg);
414
+ await client.connect();
415
+ let locked = false;
416
+ try {
417
+ await acquireMigrateLock(client, lockKey, opts.lockTimeoutMs);
418
+ locked = true;
419
+ let appliedAny = false;
420
+ const result = await applyPending(client, dir, (e) => {
421
+ if (e.kind === "applied") {
422
+ log(`migrate: applied ${String(e.version).padStart(4, "0")}_${e.name}`);
423
+ appliedAny = true;
424
+ }
425
+ else if (e.kind === "tampered") {
426
+ throw new Error(`sqlx-js.migrate: ${e.version}_${e.name} hash mismatch (applied ${e.applied.slice(0, 16)}… vs current ${e.current.slice(0, 16)}…)`);
427
+ }
428
+ else {
429
+ throw new Error(`sqlx-js.migrate: ${e.version}_${e.name} failed — ${e.error}`);
430
+ }
431
+ });
432
+ if (!appliedAny)
433
+ log(`migrate: up-to-date (${result.applied + result.failed + result.tampered === 0 ? "no pending" : ""})`);
434
+ }
435
+ finally {
436
+ if (locked) {
437
+ try {
438
+ await releaseMigrateLock(client, lockKey);
439
+ }
440
+ catch (e) {
441
+ log(`migrate: failed to release advisory lock: ${e.message}`);
442
+ }
443
+ }
444
+ await client.end();
445
+ }
446
+ }
@@ -0,0 +1,12 @@
1
+ export type QueryCallSite = {
2
+ file: string;
3
+ line: number;
4
+ column: number;
5
+ query: string;
6
+ paramCount: number;
7
+ kind: "inline" | "file";
8
+ sqlFilePath?: string;
9
+ };
10
+ export declare function findSourceFiles(root: string): string[];
11
+ export declare function scanFile(absPath: string, root: string): QueryCallSite[];
12
+ export declare function scanProject(root: string): QueryCallSite[];