@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.
- package/LICENSE +21 -0
- package/README.md +496 -0
- package/ROADMAP.md +28 -0
- package/dist/bin/sqlx-js.d.ts +2 -0
- package/dist/bin/sqlx-js.js +180 -0
- package/dist/src/bun-runtime.d.ts +7 -0
- package/dist/src/bun-runtime.js +45 -0
- package/dist/src/bun.d.ts +22 -0
- package/dist/src/bun.js +9 -0
- package/dist/src/cache.d.ts +36 -0
- package/dist/src/cache.js +104 -0
- package/dist/src/codegen.d.ts +2 -0
- package/dist/src/codegen.js +74 -0
- package/dist/src/commands/migrate.d.ts +63 -0
- package/dist/src/commands/migrate.js +283 -0
- package/dist/src/commands/prepare.d.ts +23 -0
- package/dist/src/commands/prepare.js +314 -0
- package/dist/src/commands/schema.d.ts +14 -0
- package/dist/src/commands/schema.js +72 -0
- package/dist/src/commands/watch.d.ts +21 -0
- package/dist/src/commands/watch.js +94 -0
- package/dist/src/config.d.ts +6 -0
- package/dist/src/config.js +23 -0
- package/dist/src/index.d.ts +23 -0
- package/dist/src/index.js +10 -0
- package/dist/src/pg/analyze.d.ts +13 -0
- package/dist/src/pg/analyze.js +479 -0
- package/dist/src/pg/extensions.d.ts +2 -0
- package/dist/src/pg/extensions.js +15 -0
- package/dist/src/pg/narrow.d.ts +3 -0
- package/dist/src/pg/narrow.js +146 -0
- package/dist/src/pg/oids.d.ts +10 -0
- package/dist/src/pg/oids.js +137 -0
- package/dist/src/pg/param-map.d.ts +12 -0
- package/dist/src/pg/param-map.js +180 -0
- package/dist/src/pg/schema.d.ts +61 -0
- package/dist/src/pg/schema.js +225 -0
- package/dist/src/pg/wire.d.ts +134 -0
- package/dist/src/pg/wire.js +705 -0
- package/dist/src/postgres-runtime.d.ts +11 -0
- package/dist/src/postgres-runtime.js +150 -0
- package/dist/src/runtime.d.ts +69 -0
- package/dist/src/runtime.js +446 -0
- package/dist/src/scan/scanner.d.ts +12 -0
- package/dist/src/scan/scanner.js +283 -0
- package/dist/src/schema-snapshot.d.ts +104 -0
- package/dist/src/schema-snapshot.js +473 -0
- package/dist/src/typed.d.ts +25 -0
- package/dist/src/typed.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { decodeText, parseDatabaseUrl, PgClient } from "./pg/wire.js";
|
|
4
|
+
function rows(result) {
|
|
5
|
+
return result.rows.map((r) => {
|
|
6
|
+
const out = {};
|
|
7
|
+
for (let i = 0; i < result.fields.length; i++) {
|
|
8
|
+
out[result.fields[i].name] = decodeText(r[i] ?? null);
|
|
9
|
+
}
|
|
10
|
+
return out;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function parseJsonArray(value) {
|
|
14
|
+
if (!value)
|
|
15
|
+
return [];
|
|
16
|
+
const parsed = JSON.parse(value);
|
|
17
|
+
return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string") : [];
|
|
18
|
+
}
|
|
19
|
+
function bool(value) {
|
|
20
|
+
return value === "t";
|
|
21
|
+
}
|
|
22
|
+
function num(value) {
|
|
23
|
+
return Number(value ?? 0);
|
|
24
|
+
}
|
|
25
|
+
function relationKind(raw) {
|
|
26
|
+
switch (raw) {
|
|
27
|
+
case "r": return "table";
|
|
28
|
+
case "p": return "partitioned_table";
|
|
29
|
+
case "v": return "view";
|
|
30
|
+
case "m": return "materialized_view";
|
|
31
|
+
case "f": return "foreign_table";
|
|
32
|
+
default: return "table";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function constraintKind(raw) {
|
|
36
|
+
switch (raw) {
|
|
37
|
+
case "p": return "primary_key";
|
|
38
|
+
case "f": return "foreign_key";
|
|
39
|
+
case "u": return "unique";
|
|
40
|
+
case "x": return "exclude";
|
|
41
|
+
default: return "check";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function functionKind(raw) {
|
|
45
|
+
switch (raw) {
|
|
46
|
+
case "p": return "procedure";
|
|
47
|
+
case "a": return "aggregate";
|
|
48
|
+
case "w": return "window";
|
|
49
|
+
default: return "function";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function volatility(raw) {
|
|
53
|
+
switch (raw) {
|
|
54
|
+
case "i": return "immutable";
|
|
55
|
+
case "s": return "stable";
|
|
56
|
+
default: return "volatile";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function action(raw) {
|
|
60
|
+
switch (raw) {
|
|
61
|
+
case "r": return "restrict";
|
|
62
|
+
case "c": return "cascade";
|
|
63
|
+
case "n": return "set null";
|
|
64
|
+
case "d": return "set default";
|
|
65
|
+
default: return "no action";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function byRelationKey(items) {
|
|
69
|
+
const out = new Map();
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
const key = `${item.schema}.${item.table}`;
|
|
72
|
+
const arr = out.get(key) ?? [];
|
|
73
|
+
arr.push(item);
|
|
74
|
+
out.set(key, arr);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function relationKey(schema, name) {
|
|
79
|
+
return `${schema}.${name}`;
|
|
80
|
+
}
|
|
81
|
+
function systemSchemaFilter(alias = "n") {
|
|
82
|
+
return `${alias}.nspname <> 'information_schema' AND ${alias}.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'`;
|
|
83
|
+
}
|
|
84
|
+
export async function introspectDatabase(databaseUrl) {
|
|
85
|
+
const client = new PgClient(parseDatabaseUrl(databaseUrl));
|
|
86
|
+
await client.connect();
|
|
87
|
+
try {
|
|
88
|
+
return await introspectConnected(client);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
await client.end();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export async function introspectConnected(client) {
|
|
95
|
+
const relRows = rows(await client.simpleQuery(`
|
|
96
|
+
SELECT
|
|
97
|
+
n.nspname AS schema,
|
|
98
|
+
c.relname AS name,
|
|
99
|
+
c.relkind::text AS kind,
|
|
100
|
+
CASE WHEN c.relkind IN ('v', 'm') THEN pg_get_viewdef(c.oid, true) ELSE NULL END AS definition
|
|
101
|
+
FROM pg_class c
|
|
102
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
103
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
|
104
|
+
AND ${systemSchemaFilter("n")}
|
|
105
|
+
ORDER BY n.nspname, c.relname
|
|
106
|
+
`));
|
|
107
|
+
const colRows = rows(await client.simpleQuery(`
|
|
108
|
+
SELECT
|
|
109
|
+
n.nspname AS schema,
|
|
110
|
+
c.relname AS table,
|
|
111
|
+
a.attnum::int4 AS ordinal,
|
|
112
|
+
a.attname AS name,
|
|
113
|
+
format_type(a.atttypid, a.atttypmod) AS type,
|
|
114
|
+
a.atttypid::int8 AS type_oid,
|
|
115
|
+
a.attnotnull AS not_null,
|
|
116
|
+
CASE WHEN a.attgenerated = '' THEN pg_get_expr(ad.adbin, ad.adrelid) ELSE NULL END AS default_expr,
|
|
117
|
+
a.attidentity AS identity,
|
|
118
|
+
a.attgenerated AS generated,
|
|
119
|
+
CASE WHEN a.attgenerated <> '' THEN pg_get_expr(ad.adbin, ad.adrelid) ELSE NULL END AS generated_expr
|
|
120
|
+
FROM pg_class c
|
|
121
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
122
|
+
JOIN pg_attribute a ON a.attrelid = c.oid
|
|
123
|
+
LEFT JOIN pg_attrdef ad ON ad.adrelid = c.oid AND ad.adnum = a.attnum
|
|
124
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
|
125
|
+
AND ${systemSchemaFilter("n")}
|
|
126
|
+
AND a.attnum > 0
|
|
127
|
+
AND NOT a.attisdropped
|
|
128
|
+
ORDER BY n.nspname, c.relname, a.attnum
|
|
129
|
+
`));
|
|
130
|
+
const constraintRows = rows(await client.simpleQuery(`
|
|
131
|
+
SELECT
|
|
132
|
+
n.nspname AS schema,
|
|
133
|
+
c.relname AS table,
|
|
134
|
+
con.conname AS name,
|
|
135
|
+
con.contype::text AS kind,
|
|
136
|
+
COALESCE((
|
|
137
|
+
SELECT json_agg(a.attname ORDER BY u.ord)::text
|
|
138
|
+
FROM unnest(con.conkey) WITH ORDINALITY AS u(attnum, ord)
|
|
139
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = u.attnum
|
|
140
|
+
), '[]') AS columns,
|
|
141
|
+
pg_get_constraintdef(con.oid, true) AS definition,
|
|
142
|
+
CASE WHEN con.contype = 'c' THEN pg_get_expr(con.conbin, con.conrelid) ELSE NULL END AS expression,
|
|
143
|
+
rn.nspname AS ref_schema,
|
|
144
|
+
rc.relname AS ref_table,
|
|
145
|
+
COALESCE((
|
|
146
|
+
SELECT json_agg(a.attname ORDER BY u.ord)::text
|
|
147
|
+
FROM unnest(con.confkey) WITH ORDINALITY AS u(attnum, ord)
|
|
148
|
+
JOIN pg_attribute a ON a.attrelid = con.confrelid AND a.attnum = u.attnum
|
|
149
|
+
), '[]') AS ref_columns,
|
|
150
|
+
con.confupdtype::text AS on_update,
|
|
151
|
+
con.confdeltype::text AS on_delete,
|
|
152
|
+
con.condeferrable AS deferrable,
|
|
153
|
+
con.condeferred AS initially_deferred
|
|
154
|
+
FROM pg_constraint con
|
|
155
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
156
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
157
|
+
LEFT JOIN pg_class rc ON rc.oid = con.confrelid
|
|
158
|
+
LEFT JOIN pg_namespace rn ON rn.oid = rc.relnamespace
|
|
159
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
|
160
|
+
AND ${systemSchemaFilter("n")}
|
|
161
|
+
ORDER BY n.nspname, c.relname, con.conname
|
|
162
|
+
`));
|
|
163
|
+
const indexRows = rows(await client.simpleQuery(`
|
|
164
|
+
SELECT
|
|
165
|
+
n.nspname AS schema,
|
|
166
|
+
tbl.relname AS table,
|
|
167
|
+
idx.relname AS name,
|
|
168
|
+
i.indisunique AS unique,
|
|
169
|
+
i.indisprimary AS primary,
|
|
170
|
+
am.amname AS method,
|
|
171
|
+
COALESCE((
|
|
172
|
+
SELECT json_agg(a.attname ORDER BY u.ord)::text
|
|
173
|
+
FROM unnest(string_to_array(i.indkey::text, ' ')) WITH ORDINALITY AS u(attnum, ord)
|
|
174
|
+
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = u.attnum::int2
|
|
175
|
+
WHERE u.attnum <> '0'
|
|
176
|
+
), '[]') AS columns,
|
|
177
|
+
pg_get_indexdef(idx.oid) AS definition,
|
|
178
|
+
pg_get_expr(i.indpred, i.indrelid) AS predicate
|
|
179
|
+
FROM pg_index i
|
|
180
|
+
JOIN pg_class idx ON idx.oid = i.indexrelid
|
|
181
|
+
JOIN pg_class tbl ON tbl.oid = i.indrelid
|
|
182
|
+
JOIN pg_namespace n ON n.oid = tbl.relnamespace
|
|
183
|
+
JOIN pg_am am ON am.oid = idx.relam
|
|
184
|
+
WHERE tbl.relkind IN ('r', 'p', 'm')
|
|
185
|
+
AND ${systemSchemaFilter("n")}
|
|
186
|
+
ORDER BY n.nspname, tbl.relname, idx.relname
|
|
187
|
+
`));
|
|
188
|
+
const enumRows = rows(await client.simpleQuery(`
|
|
189
|
+
SELECT n.nspname AS schema, t.typname AS name, e.enumlabel AS value
|
|
190
|
+
FROM pg_type t
|
|
191
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
192
|
+
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
193
|
+
WHERE ${systemSchemaFilter("n")}
|
|
194
|
+
ORDER BY n.nspname, t.typname, e.enumsortorder
|
|
195
|
+
`));
|
|
196
|
+
const domainRows = rows(await client.simpleQuery(`
|
|
197
|
+
SELECT
|
|
198
|
+
n.nspname AS schema,
|
|
199
|
+
t.typname AS name,
|
|
200
|
+
format_type(t.typbasetype, t.typtypmod) AS base_type,
|
|
201
|
+
t.typnotnull AS not_null,
|
|
202
|
+
pg_get_expr(t.typdefaultbin, 0) AS default_expr,
|
|
203
|
+
COALESCE((
|
|
204
|
+
SELECT json_agg(pg_get_constraintdef(c.oid, true) ORDER BY c.conname)::text
|
|
205
|
+
FROM pg_constraint c
|
|
206
|
+
WHERE c.contypid = t.oid
|
|
207
|
+
), '[]') AS checks
|
|
208
|
+
FROM pg_type t
|
|
209
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
210
|
+
WHERE t.typtype = 'd'
|
|
211
|
+
AND ${systemSchemaFilter("n")}
|
|
212
|
+
ORDER BY n.nspname, t.typname
|
|
213
|
+
`));
|
|
214
|
+
const compositeRows = rows(await client.simpleQuery(`
|
|
215
|
+
SELECT
|
|
216
|
+
n.nspname AS schema,
|
|
217
|
+
t.typname AS name,
|
|
218
|
+
COALESCE((
|
|
219
|
+
SELECT json_agg(json_build_object('name', a.attname, 'type', format_type(a.atttypid, a.atttypmod)) ORDER BY a.attnum)::text
|
|
220
|
+
FROM pg_attribute a
|
|
221
|
+
WHERE a.attrelid = c.oid
|
|
222
|
+
AND a.attnum > 0
|
|
223
|
+
AND NOT a.attisdropped
|
|
224
|
+
), '[]') AS fields
|
|
225
|
+
FROM pg_type t
|
|
226
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
227
|
+
JOIN pg_class c ON c.oid = t.typrelid AND c.relkind = 'c'
|
|
228
|
+
WHERE t.typtype = 'c'
|
|
229
|
+
AND ${systemSchemaFilter("n")}
|
|
230
|
+
ORDER BY n.nspname, t.typname
|
|
231
|
+
`));
|
|
232
|
+
const functionRows = rows(await client.simpleQuery(`
|
|
233
|
+
SELECT
|
|
234
|
+
n.nspname AS schema,
|
|
235
|
+
p.proname AS name,
|
|
236
|
+
p.prokind::text AS kind,
|
|
237
|
+
pg_get_function_identity_arguments(p.oid) AS identity_arguments,
|
|
238
|
+
pg_get_function_arguments(p.oid) AS arguments,
|
|
239
|
+
pg_get_function_result(p.oid) AS return_type,
|
|
240
|
+
p.proretset AS returns_set,
|
|
241
|
+
p.provolatile::text AS volatility,
|
|
242
|
+
p.proisstrict AS strict,
|
|
243
|
+
p.prosecdef AS security_definer,
|
|
244
|
+
l.lanname AS language
|
|
245
|
+
FROM pg_proc p
|
|
246
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
247
|
+
JOIN pg_language l ON l.oid = p.prolang
|
|
248
|
+
WHERE ${systemSchemaFilter("n")}
|
|
249
|
+
ORDER BY n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)
|
|
250
|
+
`));
|
|
251
|
+
const columnsByRel = byRelationKey(colRows.map((r) => ({
|
|
252
|
+
schema: r.schema,
|
|
253
|
+
table: r.table,
|
|
254
|
+
column: {
|
|
255
|
+
name: r.name,
|
|
256
|
+
ordinal: num(r.ordinal),
|
|
257
|
+
type: r.type,
|
|
258
|
+
typeOid: num(r.type_oid),
|
|
259
|
+
nullable: !bool(r.not_null),
|
|
260
|
+
writable: r.generated !== "s" && r.identity !== "a",
|
|
261
|
+
...(r.default_expr ? { default: r.default_expr } : {}),
|
|
262
|
+
...(r.identity === "a" ? { identity: "always" } : {}),
|
|
263
|
+
...(r.identity === "d" ? { identity: "by_default" } : {}),
|
|
264
|
+
...(r.generated === "s" ? { generated: "stored" } : {}),
|
|
265
|
+
...(r.generated_expr ? { generatedExpression: r.generated_expr } : {}),
|
|
266
|
+
},
|
|
267
|
+
})));
|
|
268
|
+
const constraintsByRel = byRelationKey(constraintRows.map((r) => {
|
|
269
|
+
const item = {
|
|
270
|
+
schema: r.schema,
|
|
271
|
+
table: r.table,
|
|
272
|
+
constraint: {
|
|
273
|
+
name: r.name,
|
|
274
|
+
kind: constraintKind(r.kind),
|
|
275
|
+
columns: parseJsonArray(r.columns),
|
|
276
|
+
definition: r.definition,
|
|
277
|
+
...(r.expression ? { expression: r.expression } : {}),
|
|
278
|
+
...(bool(r.deferrable) ? { deferrable: true } : {}),
|
|
279
|
+
...(bool(r.initially_deferred) ? { initiallyDeferred: true } : {}),
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
if (r.ref_schema && r.ref_table) {
|
|
283
|
+
item.constraint.references = {
|
|
284
|
+
schema: r.ref_schema,
|
|
285
|
+
table: r.ref_table,
|
|
286
|
+
columns: parseJsonArray(r.ref_columns),
|
|
287
|
+
onUpdate: action(r.on_update),
|
|
288
|
+
onDelete: action(r.on_delete),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return item;
|
|
292
|
+
}));
|
|
293
|
+
const indexesByRel = byRelationKey(indexRows.map((r) => ({
|
|
294
|
+
schema: r.schema,
|
|
295
|
+
table: r.table,
|
|
296
|
+
index: {
|
|
297
|
+
name: r.name,
|
|
298
|
+
unique: bool(r.unique),
|
|
299
|
+
primary: bool(r.primary),
|
|
300
|
+
method: r.method,
|
|
301
|
+
columns: parseJsonArray(r.columns),
|
|
302
|
+
definition: r.definition,
|
|
303
|
+
...(r.predicate ? { predicate: r.predicate } : {}),
|
|
304
|
+
},
|
|
305
|
+
})));
|
|
306
|
+
const relations = relRows.map((r) => {
|
|
307
|
+
const key = relationKey(r.schema, r.name);
|
|
308
|
+
return {
|
|
309
|
+
schema: r.schema,
|
|
310
|
+
name: r.name,
|
|
311
|
+
kind: relationKind(r.kind),
|
|
312
|
+
columns: (columnsByRel.get(key) ?? []).map((c) => c.column),
|
|
313
|
+
constraints: (constraintsByRel.get(key) ?? []).map((c) => c.constraint),
|
|
314
|
+
indexes: (indexesByRel.get(key) ?? []).map((i) => i.index),
|
|
315
|
+
...(r.definition ? { definition: r.definition } : {}),
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
const enumMap = new Map();
|
|
319
|
+
for (const r of enumRows) {
|
|
320
|
+
const key = relationKey(r.schema, r.name);
|
|
321
|
+
const existing = enumMap.get(key);
|
|
322
|
+
if (existing)
|
|
323
|
+
existing.values.push(r.value);
|
|
324
|
+
else
|
|
325
|
+
enumMap.set(key, { kind: "enum", schema: r.schema, name: r.name, values: [r.value] });
|
|
326
|
+
}
|
|
327
|
+
const domains = domainRows.map((r) => ({
|
|
328
|
+
kind: "domain",
|
|
329
|
+
schema: r.schema,
|
|
330
|
+
name: r.name,
|
|
331
|
+
baseType: r.base_type,
|
|
332
|
+
notNull: bool(r.not_null),
|
|
333
|
+
...(r.default_expr ? { default: r.default_expr } : {}),
|
|
334
|
+
checks: parseJsonArray(r.checks),
|
|
335
|
+
}));
|
|
336
|
+
const composites = compositeRows.map((r) => ({
|
|
337
|
+
kind: "composite",
|
|
338
|
+
schema: r.schema,
|
|
339
|
+
name: r.name,
|
|
340
|
+
fields: JSON.parse(r.fields ?? "[]").flatMap((f) => {
|
|
341
|
+
if (!f || typeof f !== "object")
|
|
342
|
+
return [];
|
|
343
|
+
const obj = f;
|
|
344
|
+
return typeof obj.name === "string" && typeof obj.type === "string"
|
|
345
|
+
? [{ name: obj.name, type: obj.type }]
|
|
346
|
+
: [];
|
|
347
|
+
}),
|
|
348
|
+
}));
|
|
349
|
+
const functions = functionRows.map((r) => ({
|
|
350
|
+
schema: r.schema,
|
|
351
|
+
name: r.name,
|
|
352
|
+
kind: functionKind(r.kind),
|
|
353
|
+
identityArguments: r.identity_arguments ?? "",
|
|
354
|
+
arguments: r.arguments ?? "",
|
|
355
|
+
returnType: r.return_type ?? "void",
|
|
356
|
+
returnsSet: bool(r.returns_set),
|
|
357
|
+
volatility: volatility(r.volatility),
|
|
358
|
+
strict: bool(r.strict),
|
|
359
|
+
securityDefiner: bool(r.security_definer),
|
|
360
|
+
language: r.language,
|
|
361
|
+
}));
|
|
362
|
+
const schemas = [...new Set([
|
|
363
|
+
...relations.map((r) => r.schema),
|
|
364
|
+
...Array.from(enumMap.values()).map((t) => t.schema),
|
|
365
|
+
...domains.map((t) => t.schema),
|
|
366
|
+
...composites.map((t) => t.schema),
|
|
367
|
+
...functions.map((f) => f.schema),
|
|
368
|
+
])].sort();
|
|
369
|
+
return {
|
|
370
|
+
version: 1,
|
|
371
|
+
schemas,
|
|
372
|
+
relations,
|
|
373
|
+
types: [...Array.from(enumMap.values()), ...domains, ...composites],
|
|
374
|
+
functions,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
export function stableSchemaJson(snapshot) {
|
|
378
|
+
return JSON.stringify(snapshot, null, 2) + "\n";
|
|
379
|
+
}
|
|
380
|
+
export function writeSchemaSnapshot(path, snapshot) {
|
|
381
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
382
|
+
writeFileSync(path, stableSchemaJson(snapshot));
|
|
383
|
+
}
|
|
384
|
+
export function readSchemaSnapshot(path) {
|
|
385
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
386
|
+
}
|
|
387
|
+
export function schemaSnapshotEqual(a, b) {
|
|
388
|
+
return stableSchemaJson(a) === stableSchemaJson(b);
|
|
389
|
+
}
|
|
390
|
+
export function renderSchemaManifest(snapshot) {
|
|
391
|
+
const lines = [];
|
|
392
|
+
lines.push("# sqlx-js schema manifest");
|
|
393
|
+
lines.push("");
|
|
394
|
+
lines.push("Generated from PostgreSQL introspection. Do not edit by hand.");
|
|
395
|
+
lines.push("");
|
|
396
|
+
lines.push(`Schemas: ${snapshot.schemas.length === 0 ? "(none)" : snapshot.schemas.join(", ")}`);
|
|
397
|
+
lines.push("");
|
|
398
|
+
lines.push("## Relations");
|
|
399
|
+
if (snapshot.relations.length === 0)
|
|
400
|
+
lines.push("");
|
|
401
|
+
if (snapshot.relations.length === 0)
|
|
402
|
+
lines.push("(none)");
|
|
403
|
+
for (const rel of snapshot.relations) {
|
|
404
|
+
lines.push("");
|
|
405
|
+
lines.push(`### ${rel.schema}.${rel.name} (${rel.kind})`);
|
|
406
|
+
if (rel.definition)
|
|
407
|
+
lines.push(`Definition: ${rel.definition.replace(/\s+/g, " ").trim()}`);
|
|
408
|
+
lines.push("");
|
|
409
|
+
lines.push("| Column | Type | Nullable | Writable | Default / Generated |");
|
|
410
|
+
lines.push("|--------|------|----------|----------|---------------------|");
|
|
411
|
+
for (const col of rel.columns) {
|
|
412
|
+
const defaultInfo = col.generatedExpression ?? col.default ?? col.identity ?? "";
|
|
413
|
+
lines.push(`| ${col.name} | ${col.type} | ${col.nullable ? "yes" : "no"} | ${col.writable ? "yes" : "no"} | ${defaultInfo} |`);
|
|
414
|
+
}
|
|
415
|
+
if (rel.constraints.length > 0) {
|
|
416
|
+
lines.push("");
|
|
417
|
+
lines.push("Constraints:");
|
|
418
|
+
for (const c of rel.constraints) {
|
|
419
|
+
const cols = c.columns.length > 0 ? ` [${c.columns.join(", ")}]` : "";
|
|
420
|
+
const ref = c.references ? ` -> ${c.references.schema}.${c.references.table}(${c.references.columns.join(", ")})` : "";
|
|
421
|
+
lines.push(`- ${c.name}: ${c.kind}${cols}${ref}; ${c.definition}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (rel.indexes.length > 0) {
|
|
425
|
+
lines.push("");
|
|
426
|
+
lines.push("Indexes:");
|
|
427
|
+
for (const idx of rel.indexes) {
|
|
428
|
+
const flags = [idx.primary ? "primary" : "", idx.unique ? "unique" : ""].filter(Boolean).join(", ");
|
|
429
|
+
const cols = idx.columns.length > 0 ? ` [${idx.columns.join(", ")}]` : "";
|
|
430
|
+
lines.push(`- ${idx.name}: ${idx.method}${cols}${flags ? ` (${flags})` : ""}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
lines.push("");
|
|
435
|
+
lines.push("## Types");
|
|
436
|
+
if (snapshot.types.length === 0)
|
|
437
|
+
lines.push("(none)");
|
|
438
|
+
for (const t of snapshot.types) {
|
|
439
|
+
if (t.kind === "enum") {
|
|
440
|
+
lines.push(`- enum ${t.schema}.${t.name}: ${t.values.map((v) => JSON.stringify(v)).join(" | ")}`);
|
|
441
|
+
}
|
|
442
|
+
else if (t.kind === "domain") {
|
|
443
|
+
const checks = t.checks.length > 0 ? `; checks: ${t.checks.join("; ")}` : "";
|
|
444
|
+
lines.push(`- domain ${t.schema}.${t.name}: ${t.baseType}${t.notNull ? " not null" : ""}${checks}`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
lines.push(`- composite ${t.schema}.${t.name}: ${t.fields.map((f) => `${f.name} ${f.type}`).join(", ")}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
lines.push("");
|
|
451
|
+
lines.push("## Functions");
|
|
452
|
+
if (snapshot.functions.length === 0)
|
|
453
|
+
lines.push("(none)");
|
|
454
|
+
for (const fn of snapshot.functions) {
|
|
455
|
+
const attrs = [
|
|
456
|
+
fn.kind,
|
|
457
|
+
fn.volatility,
|
|
458
|
+
fn.strict ? "strict" : "",
|
|
459
|
+
fn.securityDefiner ? "security definer" : "",
|
|
460
|
+
fn.returnsSet ? "returns set" : "",
|
|
461
|
+
].filter(Boolean).join(", ");
|
|
462
|
+
lines.push(`- ${fn.schema}.${fn.name}(${fn.identityArguments}) -> ${fn.returnType} [${attrs}]`);
|
|
463
|
+
}
|
|
464
|
+
lines.push("");
|
|
465
|
+
return lines.join("\n");
|
|
466
|
+
}
|
|
467
|
+
export function writeSchemaManifest(path, snapshot) {
|
|
468
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
469
|
+
writeFileSync(path, renderSchemaManifest(snapshot));
|
|
470
|
+
}
|
|
471
|
+
export function schemaSnapshotExists(path) {
|
|
472
|
+
return existsSync(path);
|
|
473
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type ParamsOf<T> = T extends {
|
|
2
|
+
params: infer P extends readonly unknown[];
|
|
3
|
+
} ? P : never[];
|
|
4
|
+
type RowOf<T> = T extends {
|
|
5
|
+
row: infer R;
|
|
6
|
+
} ? R : never;
|
|
7
|
+
export type TypedFile<TFileQueries> = {
|
|
8
|
+
<P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>): Promise<RowOf<TFileQueries[P]>[]>;
|
|
9
|
+
one: <P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>) => Promise<RowOf<TFileQueries[P]>>;
|
|
10
|
+
optional: <P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>) => Promise<RowOf<TFileQueries[P]> | null>;
|
|
11
|
+
};
|
|
12
|
+
export type TypedSql<TQueries, TFileQueries> = {
|
|
13
|
+
<Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>): Promise<RowOf<TQueries[Q]>[]>;
|
|
14
|
+
one: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<RowOf<TQueries[Q]>>;
|
|
15
|
+
optional: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<RowOf<TQueries[Q]> | null>;
|
|
16
|
+
file: TypedFile<TFileQueries>;
|
|
17
|
+
id: (...parts: string[]) => string;
|
|
18
|
+
};
|
|
19
|
+
export type Typed<TQueries, TFileQueries, TTransactionOptions> = TypedSql<TQueries, TFileQueries> & {
|
|
20
|
+
transaction: {
|
|
21
|
+
<R>(fn: (tx: TypedSql<TQueries, TFileQueries>) => Promise<R>): Promise<R>;
|
|
22
|
+
<R>(opts: TTransactionOptions, fn: (tx: TypedSql<TQueries, TFileQueries>) => Promise<R>): Promise<R>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onreza/sqlx-js",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Compile-time-checked raw SQL for TypeScript + PostgreSQL. Inspired by sqlx.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/src/index.js",
|
|
9
|
+
"types": "./dist/src/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/src/index.d.ts",
|
|
13
|
+
"import": "./dist/src/index.js",
|
|
14
|
+
"default": "./dist/src/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./bun": {
|
|
17
|
+
"types": "./dist/src/bun.d.ts",
|
|
18
|
+
"bun": "./dist/src/bun.js",
|
|
19
|
+
"import": "./dist/src/bun.js",
|
|
20
|
+
"default": "./dist/src/bun.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"sqlx-js": "./dist/bin/sqlx-js.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"ROADMAP.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/ONREZA/sqlx-js.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/ONREZA/sqlx-js#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/ONREZA/sqlx-js/issues"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"bun",
|
|
45
|
+
"node",
|
|
46
|
+
"postgres",
|
|
47
|
+
"postgresql",
|
|
48
|
+
"sql",
|
|
49
|
+
"sqlx",
|
|
50
|
+
"typescript",
|
|
51
|
+
"type-safe",
|
|
52
|
+
"migrations"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"prepare": "[ -d .git ] && lefthook install >/dev/null 2>&1 || true",
|
|
56
|
+
"test": "bun test tests",
|
|
57
|
+
"test:typecheck": "tsc --noEmit",
|
|
58
|
+
"test:example": "bunx tsc -p example --noEmit",
|
|
59
|
+
"sqlx:prepare": "bun bin/sqlx-js.ts prepare",
|
|
60
|
+
"sqlx:migrate": "bun bin/sqlx-js.ts migrate",
|
|
61
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && bun scripts/fix-dist-imports.ts",
|
|
62
|
+
"prepack": "bun run build"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"typescript": ">=5.4"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"libpg-query": "^17.7.3",
|
|
69
|
+
"postgres": "^3.4.9"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@testcontainers/postgresql": "^11.14.0",
|
|
73
|
+
"@types/bun": "latest",
|
|
74
|
+
"lefthook": "^2.1.6",
|
|
75
|
+
"testcontainers": "^11.14.0",
|
|
76
|
+
"typescript": "^5.6.0"
|
|
77
|
+
}
|
|
78
|
+
}
|