@supabase/lite 0.0.1 → 0.2.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/README.md +311 -0
- package/dist/cli/index.js +361 -29421
- package/dist/cli/lib.js +78 -6273
- package/dist/db/browser/index.js +50 -15485
- package/dist/db/bun/index.js +50 -15473
- package/dist/db/fallback.js +49 -15369
- package/dist/db/node/index.js +50 -15466
- package/dist/db/postgres/PostgresConnection.js +18 -365
- package/dist/db/postgres/pglite/PgliteConnection.js +20 -642
- package/dist/db/workerd/index.js +49 -15591
- package/dist/index.js +289 -32242
- package/dist/vite/index.js +3 -1910
- package/package.json +5 -3
- package/dist/Connection-BJclIu8v.d.ts +0 -529
- package/dist/Connection-BsiQMo4A.d.ts +0 -562
- package/dist/db/postgres/BasePostgresConnection-COHRCvc-.d.ts +0 -24
- package/dist/db/postgres/BasePostgresConnection-Di94o0ON.d.ts +0 -24
- package/dist/db/postgres/index.js +0 -802
- package/dist/db/sqlite/index.js +0 -14724
- package/dist/index-4cbfQyFv.d.ts +0 -763
|
@@ -1,802 +0,0 @@
|
|
|
1
|
-
import postgres from 'postgres';
|
|
2
|
-
import { PostgresJSDialect } from 'kysely-postgres-js';
|
|
3
|
-
import { Kysely, sql, PostgresQueryCompiler, PostgresIntrospector, PostgresAdapter, CompiledQuery } from 'kysely';
|
|
4
|
-
import { Connection, invariant, RelationNotFoundError, DataLossError } from 'lite-supa';
|
|
5
|
-
import { PGlite } from '@electric-sql/pglite';
|
|
6
|
-
import { mkdir } from 'node:fs';
|
|
7
|
-
import { isn } from '@electric-sql/pglite/contrib/isn';
|
|
8
|
-
import { ltree } from '@electric-sql/pglite/contrib/ltree';
|
|
9
|
-
import { file_fdw } from '@electric-sql/pglite/contrib/file_fdw';
|
|
10
|
-
import { pgcrypto } from '@electric-sql/pglite/contrib/pgcrypto';
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
/**
|
|
14
|
-
* Adding this to avoid warnings from node:sqlite being experimental
|
|
15
|
-
*/
|
|
16
|
-
const { emitWarning } = process;
|
|
17
|
-
process.emitWarning = (warning, ...args) => {
|
|
18
|
-
if (warning.includes("SQLite is an experimental feature")) return;
|
|
19
|
-
return emitWarning(warning, ...args);
|
|
20
|
-
};
|
|
21
|
-
} catch {}
|
|
22
|
-
var BasePostgresConnection = class extends Connection {
|
|
23
|
-
dialect = "postgres";
|
|
24
|
-
constructor(options) {
|
|
25
|
-
super({
|
|
26
|
-
...options,
|
|
27
|
-
// @todo: this should be handled by the auth schema
|
|
28
|
-
baseSchema: `
|
|
29
|
-
${options.baseSchema ?? ""}
|
|
30
|
-
|
|
31
|
-
CREATE SCHEMA IF NOT EXISTS auth;
|
|
32
|
-
|
|
33
|
-
-- Function to get current user ID (for RLS policies)
|
|
34
|
-
CREATE OR REPLACE FUNCTION auth.uid() RETURNS UUID AS $$
|
|
35
|
-
SELECT NULLIF(current_setting('request.jwt.claim.sub', true), '')::uuid;
|
|
36
|
-
$$ LANGUAGE SQL STABLE;
|
|
37
|
-
|
|
38
|
-
-- Function to get current user role (for RLS policies)
|
|
39
|
-
CREATE OR REPLACE FUNCTION auth.role() RETURNS TEXT AS $$
|
|
40
|
-
SELECT NULLIF(current_setting('request.jwt.claim.role', true), '');
|
|
41
|
-
$$ LANGUAGE SQL STABLE;
|
|
42
|
-
|
|
43
|
-
-- Function to get current user email (for RLS policies)
|
|
44
|
-
CREATE OR REPLACE FUNCTION auth.email() RETURNS TEXT AS $$
|
|
45
|
-
SELECT NULLIF(current_setting('request.jwt.claim.email', true), '');
|
|
46
|
-
$$ LANGUAGE SQL STABLE;
|
|
47
|
-
|
|
48
|
-
-- Function to get JWT claims (for RLS policies)
|
|
49
|
-
CREATE OR REPLACE FUNCTION auth.jwt() RETURNS JSONB AS $$
|
|
50
|
-
SELECT COALESCE(
|
|
51
|
-
NULLIF(current_setting('request.jwt.claims', true), ''),
|
|
52
|
-
'{}'
|
|
53
|
-
)::jsonb;
|
|
54
|
-
$$ LANGUAGE SQL STABLE;
|
|
55
|
-
`
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
async introspect(options) {
|
|
59
|
-
invariant(typeof options === "object" || options === void 0, "options must be an object");
|
|
60
|
-
const useCache = options?.useCache ?? false;
|
|
61
|
-
if (typeof this.introspection === "object" && useCache === true) return this.introspection;
|
|
62
|
-
try {
|
|
63
|
-
const tblResult = await sql`
|
|
64
|
-
SELECT
|
|
65
|
-
tbls.table_name AS "name",
|
|
66
|
-
tbls.table_schema AS "schema",
|
|
67
|
-
tbls.table_type AS "type",
|
|
68
|
-
COALESCE(s.n_live_tup, 0) AS "rows"
|
|
69
|
-
FROM information_schema.tables tbls
|
|
70
|
-
LEFT JOIN pg_stat_user_tables s
|
|
71
|
-
ON tbls.table_schema = s.schemaname AND tbls.table_name = s.relname
|
|
72
|
-
WHERE
|
|
73
|
-
tbls.table_schema NOT IN ('information_schema', 'pg_catalog')
|
|
74
|
-
AND tbls.table_type IN ('BASE TABLE', 'FOREIGN TABLE')
|
|
75
|
-
ORDER BY tbls.table_schema, tbls.table_name
|
|
76
|
-
`.execute(this.kysely);
|
|
77
|
-
const tables = tblResult.rows.map((r) => ({
|
|
78
|
-
name: r.name,
|
|
79
|
-
schema: r.schema,
|
|
80
|
-
type: "table",
|
|
81
|
-
rows: Number(r.rows),
|
|
82
|
-
sql: "",
|
|
83
|
-
engine: "",
|
|
84
|
-
collation: ""
|
|
85
|
-
}));
|
|
86
|
-
const colResult = await sql`
|
|
87
|
-
SELECT
|
|
88
|
-
cols.table_name AS "table",
|
|
89
|
-
cols.table_schema AS "schema",
|
|
90
|
-
cols.column_name AS "name",
|
|
91
|
-
cols.ordinal_position AS "ordinal_position",
|
|
92
|
-
CASE
|
|
93
|
-
WHEN cols.column_default IS NOT NULL AND cols.column_default LIKE 'nextval(%' THEN
|
|
94
|
-
CASE
|
|
95
|
-
WHEN LOWER(cols.data_type) = 'smallint' THEN 'smallserial'
|
|
96
|
-
WHEN LOWER(cols.data_type) = 'integer' THEN 'serial'
|
|
97
|
-
WHEN LOWER(cols.data_type) = 'bigint' THEN 'bigserial'
|
|
98
|
-
ELSE LOWER(cols.data_type)
|
|
99
|
-
END
|
|
100
|
-
WHEN cols.data_type = 'ARRAY' THEN format_type(pt.typelem, NULL)
|
|
101
|
-
WHEN LOWER(cols.data_type) = 'user-defined' THEN format_type(pt.oid, NULL)
|
|
102
|
-
ELSE LOWER(cols.data_type)
|
|
103
|
-
END AS "type",
|
|
104
|
-
CASE WHEN cols.is_nullable = 'YES' THEN true ELSE false END AS "nullable",
|
|
105
|
-
CASE
|
|
106
|
-
WHEN cols.column_default IS NOT NULL AND cols.column_default LIKE 'nextval(%'
|
|
107
|
-
THEN NULL
|
|
108
|
-
ELSE cols.column_default
|
|
109
|
-
END AS "default_value",
|
|
110
|
-
cols.character_maximum_length::text AS "character_maximum_length", CASE
|
|
111
|
-
WHEN cols.data_type IN ('numeric', 'decimal')
|
|
112
|
-
THEN
|
|
113
|
-
json_build_object(
|
|
114
|
-
'precision',
|
|
115
|
-
cols.numeric_precision,
|
|
116
|
-
'scale',
|
|
117
|
-
cols.numeric_scale)
|
|
118
|
-
ELSE NULL
|
|
119
|
-
END AS "precision",
|
|
120
|
-
CASE
|
|
121
|
-
WHEN cols.is_identity = 'YES' THEN true
|
|
122
|
-
WHEN cols.column_default IS NOT NULL AND cols.column_default LIKE 'nextval(%'
|
|
123
|
-
THEN true
|
|
124
|
-
ELSE false
|
|
125
|
-
END AS "is_identity",
|
|
126
|
-
COALESCE(cols.collation_name, '') AS "collation"
|
|
127
|
-
FROM information_schema.columns cols
|
|
128
|
-
LEFT JOIN pg_catalog.pg_class c ON c.relname = cols.table_name
|
|
129
|
-
JOIN pg_catalog.pg_namespace n
|
|
130
|
-
ON n.oid = c.relnamespace AND n.nspname = cols.table_schema
|
|
131
|
-
LEFT JOIN pg_catalog.pg_attribute attr
|
|
132
|
-
ON attr.attrelid = c.oid AND attr.attname = cols.column_name
|
|
133
|
-
LEFT JOIN pg_catalog.pg_type pt ON pt.oid = attr.atttypid
|
|
134
|
-
WHERE cols.table_schema NOT IN ('information_schema', 'pg_catalog')
|
|
135
|
-
ORDER BY cols.table_schema, cols.table_name, cols.ordinal_position
|
|
136
|
-
`.execute(this.kysely);
|
|
137
|
-
const columns = colResult.rows.map((r) => ({
|
|
138
|
-
table: r.table,
|
|
139
|
-
schema: r.schema,
|
|
140
|
-
name: r.name,
|
|
141
|
-
type: r.type,
|
|
142
|
-
nullable: r.nullable,
|
|
143
|
-
default_value: r.default_value,
|
|
144
|
-
is_primary_key: false,
|
|
145
|
-
// filled below
|
|
146
|
-
ordinal_position: Number(r.ordinal_position),
|
|
147
|
-
character_maximum_length: r.character_maximum_length ?? null,
|
|
148
|
-
precision: r.precision ?? null,
|
|
149
|
-
is_identity: r.is_identity,
|
|
150
|
-
collation: r.collation
|
|
151
|
-
}));
|
|
152
|
-
const fkResult = await sql`
|
|
153
|
-
SELECT
|
|
154
|
-
ns.nspname AS "schema",
|
|
155
|
-
cl.relname AS "table",
|
|
156
|
-
att.attname AS "column",
|
|
157
|
-
nr.nspname AS "ref_schema",
|
|
158
|
-
ref_cl.relname AS "ref_table",
|
|
159
|
-
ref_att.attname AS "ref_column",
|
|
160
|
-
con.conname AS "foreign_key_name",
|
|
161
|
-
pg_get_constraintdef(con.oid) AS "fk_def"
|
|
162
|
-
FROM pg_constraint con
|
|
163
|
-
JOIN pg_class cl ON cl.oid = con.conrelid
|
|
164
|
-
JOIN pg_namespace ns ON ns.oid = cl.relnamespace
|
|
165
|
-
JOIN pg_class ref_cl ON ref_cl.oid = con.confrelid
|
|
166
|
-
JOIN pg_namespace nr ON nr.oid = ref_cl.relnamespace
|
|
167
|
-
CROSS JOIN LATERAL unnest(con.conkey, con.confkey) WITH ORDINALITY AS cols(conkey_num, confkey_num, ord)
|
|
168
|
-
JOIN pg_attribute att
|
|
169
|
-
ON att.attrelid = con.conrelid AND att.attnum = cols.conkey_num
|
|
170
|
-
JOIN pg_attribute ref_att ON ref_att.attrelid = con.confrelid AND ref_att.attnum = cols.confkey_num
|
|
171
|
-
WHERE
|
|
172
|
-
con.contype = 'f'
|
|
173
|
-
AND ns.nspname NOT IN (
|
|
174
|
-
'information_schema'
|
|
175
|
-
, 'pg_catalog')
|
|
176
|
-
ORDER BY ns.nspname, cl.relname, con.conname, cols.ord
|
|
177
|
-
`.execute(this.kysely);
|
|
178
|
-
const fks = fkResult.rows.map((r) => ({
|
|
179
|
-
table: r.table,
|
|
180
|
-
column: r.column,
|
|
181
|
-
schema: r.schema,
|
|
182
|
-
ref_table: r.ref_table,
|
|
183
|
-
ref_column: r.ref_column,
|
|
184
|
-
foreign_key_name: r.foreign_key_name,
|
|
185
|
-
fk_def: r.fk_def,
|
|
186
|
-
on_update: "",
|
|
187
|
-
on_delete: ""
|
|
188
|
-
}));
|
|
189
|
-
const pkResult = await sql`
|
|
190
|
-
SELECT
|
|
191
|
-
ns.nspname AS "schema",
|
|
192
|
-
cl.relname AS "table",
|
|
193
|
-
array_agg(att.attname ORDER BY cols.ord) AS "columns"
|
|
194
|
-
FROM pg_constraint con
|
|
195
|
-
JOIN pg_class cl ON cl.oid = con.conrelid
|
|
196
|
-
JOIN pg_namespace ns ON ns.oid = cl.relnamespace
|
|
197
|
-
CROSS JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS cols(attnum, ord)
|
|
198
|
-
JOIN pg_attribute att
|
|
199
|
-
ON att.attrelid = con.conrelid AND att.attnum = cols.attnum
|
|
200
|
-
WHERE
|
|
201
|
-
con.contype = 'p'
|
|
202
|
-
AND ns.nspname NOT IN (
|
|
203
|
-
'information_schema'
|
|
204
|
-
, 'pg_catalog')
|
|
205
|
-
GROUP BY ns.nspname, cl.relname, con.oid
|
|
206
|
-
`.execute(this.kysely);
|
|
207
|
-
const pks = pkResult.rows.map((r) => ({
|
|
208
|
-
table: r.table,
|
|
209
|
-
columns: r.columns,
|
|
210
|
-
schema: r.schema,
|
|
211
|
-
field_count: r.columns.length
|
|
212
|
-
}));
|
|
213
|
-
const pkSet = new Set(
|
|
214
|
-
pks.flatMap((pk) => pk.columns.map((col) => `${pk.schema}.${pk.table}.${col}`))
|
|
215
|
-
);
|
|
216
|
-
for (const col of columns) {
|
|
217
|
-
col.is_primary_key = pkSet.has(`${col.schema}.${col.table}.${col.name}`);
|
|
218
|
-
}
|
|
219
|
-
const idxResult = await sql`
|
|
220
|
-
SELECT
|
|
221
|
-
tnsp.nspname AS "schema",
|
|
222
|
-
cl.relname AS "table",
|
|
223
|
-
ic.relname AS "name",
|
|
224
|
-
idx.indisunique AS "unique",
|
|
225
|
-
array_agg(att.attname ORDER BY a.ord) AS "columns"
|
|
226
|
-
FROM pg_index idx
|
|
227
|
-
JOIN pg_class cl ON cl.oid = idx.indrelid
|
|
228
|
-
JOIN pg_namespace tnsp ON cl.relnamespace = tnsp.oid
|
|
229
|
-
JOIN pg_class ic ON ic.oid = idx.indexrelid
|
|
230
|
-
CROSS JOIN LATERAL unnest(idx.indkey) WITH ORDINALITY AS a(attnum, ord)
|
|
231
|
-
JOIN pg_attribute att
|
|
232
|
-
ON att.attrelid = cl.oid AND att.attnum = a.attnum
|
|
233
|
-
WHERE
|
|
234
|
-
idx.indisunique = true
|
|
235
|
-
AND NOT idx.indisprimary
|
|
236
|
-
AND tnsp.nspname NOT IN (
|
|
237
|
-
'information_schema'
|
|
238
|
-
, 'pg_catalog')
|
|
239
|
-
GROUP BY tnsp.nspname, cl.relname, ic.relname, idx.indisunique
|
|
240
|
-
`.execute(this.kysely);
|
|
241
|
-
const indexes = idxResult.rows.map((r) => ({
|
|
242
|
-
table: r.table,
|
|
243
|
-
name: r.name,
|
|
244
|
-
unique: r.unique,
|
|
245
|
-
columns: r.columns,
|
|
246
|
-
schema: r.schema
|
|
247
|
-
}));
|
|
248
|
-
const viewResult = await sql`
|
|
249
|
-
SELECT
|
|
250
|
-
views.schemaname AS "schema",
|
|
251
|
-
views.viewname AS "name",
|
|
252
|
-
views.definition AS "sql"
|
|
253
|
-
FROM pg_views views
|
|
254
|
-
WHERE views.schemaname NOT IN ('information_schema', 'pg_catalog')
|
|
255
|
-
ORDER BY views.schemaname, views.viewname
|
|
256
|
-
`.execute(this.kysely);
|
|
257
|
-
const views = viewResult.rows.map((r) => ({
|
|
258
|
-
name: r.name,
|
|
259
|
-
schema: r.schema,
|
|
260
|
-
sql: r.sql ?? ""
|
|
261
|
-
}));
|
|
262
|
-
const chkResult = await sql`
|
|
263
|
-
SELECT
|
|
264
|
-
n.nspname AS "schema",
|
|
265
|
-
cl.relname AS "table",
|
|
266
|
-
substring(pg_get_constraintdef(c.oid) FROM 'CHECK \\((.*)\\)') AS "expression"
|
|
267
|
-
FROM pg_constraint c
|
|
268
|
-
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
269
|
-
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
270
|
-
WHERE
|
|
271
|
-
c.contype = 'c'
|
|
272
|
-
AND n.nspname NOT IN ('information_schema', 'pg_catalog')
|
|
273
|
-
ORDER BY n.nspname, cl.relname
|
|
274
|
-
`.execute(this.kysely);
|
|
275
|
-
const checkConstraints = chkResult.rows.map((r) => ({
|
|
276
|
-
schema: r.schema,
|
|
277
|
-
table: r.table,
|
|
278
|
-
expression: r.expression ?? ""
|
|
279
|
-
}));
|
|
280
|
-
const enumResult = await sql`
|
|
281
|
-
SELECT
|
|
282
|
-
n.nspname AS "schema",
|
|
283
|
-
t.typname AS "type",
|
|
284
|
-
'enum' AS "kind",
|
|
285
|
-
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS "values"
|
|
286
|
-
FROM pg_type t
|
|
287
|
-
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
288
|
-
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
289
|
-
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
290
|
-
GROUP BY n.nspname, t.typname
|
|
291
|
-
`.execute(this.kysely);
|
|
292
|
-
const compositeResult = await sql`
|
|
293
|
-
SELECT
|
|
294
|
-
n.nspname AS "schema",
|
|
295
|
-
t.typname AS "type",
|
|
296
|
-
'composite' AS "kind",
|
|
297
|
-
json_agg(json_build_object(
|
|
298
|
-
'name',
|
|
299
|
-
a.attname,
|
|
300
|
-
'type',
|
|
301
|
-
format_type(a.atttypid, a.atttypmod)) ORDER BY a.attnum
|
|
302
|
-
) AS "fields"
|
|
303
|
-
FROM pg_type t
|
|
304
|
-
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
305
|
-
JOIN pg_class c ON c.oid = t.typrelid
|
|
306
|
-
JOIN pg_attribute a ON a.attrelid = c.oid
|
|
307
|
-
WHERE
|
|
308
|
-
t.typtype = 'c'
|
|
309
|
-
AND c.relkind = 'c'
|
|
310
|
-
AND a.attnum > 0
|
|
311
|
-
AND NOT a.attisdropped
|
|
312
|
-
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
313
|
-
GROUP BY n.nspname, t.typname
|
|
314
|
-
`.execute(this.kysely);
|
|
315
|
-
const customTypes = [
|
|
316
|
-
...enumResult.rows.map((r) => ({
|
|
317
|
-
schema: r.schema,
|
|
318
|
-
type: r.type,
|
|
319
|
-
kind: "enum",
|
|
320
|
-
values: r.values
|
|
321
|
-
})),
|
|
322
|
-
...compositeResult.rows.map((r) => ({
|
|
323
|
-
schema: r.schema,
|
|
324
|
-
type: r.type,
|
|
325
|
-
kind: "composite",
|
|
326
|
-
fields: r.fields
|
|
327
|
-
}))
|
|
328
|
-
];
|
|
329
|
-
const dbResult = await sql`SELECT current_database() AS "name"`.execute(this.kysely);
|
|
330
|
-
const dbName = dbResult.rows[0]?.name ?? "postgres";
|
|
331
|
-
this.introspection = {
|
|
332
|
-
tables,
|
|
333
|
-
columns,
|
|
334
|
-
indexes,
|
|
335
|
-
foreign_keys: fks,
|
|
336
|
-
primary_keys: pks,
|
|
337
|
-
views,
|
|
338
|
-
triggers: [],
|
|
339
|
-
check_constraints: checkConstraints,
|
|
340
|
-
custom_types: customTypes,
|
|
341
|
-
database_name: dbName,
|
|
342
|
-
version: ""
|
|
343
|
-
};
|
|
344
|
-
return this.introspection;
|
|
345
|
-
} catch (e) {
|
|
346
|
-
console.error("Introspection failed:", e);
|
|
347
|
-
return {
|
|
348
|
-
tables: [],
|
|
349
|
-
columns: [],
|
|
350
|
-
indexes: [],
|
|
351
|
-
foreign_keys: [],
|
|
352
|
-
primary_keys: [],
|
|
353
|
-
views: [],
|
|
354
|
-
triggers: [],
|
|
355
|
-
check_constraints: [],
|
|
356
|
-
custom_types: [],
|
|
357
|
-
database_name: "postgres",
|
|
358
|
-
version: ""
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
rlsState = "unknown";
|
|
363
|
-
/**
|
|
364
|
-
* Detect if any table has RLS enabled, and if so ensure anon/authenticated
|
|
365
|
-
* roles exist with default privileges. Runs once per connection.
|
|
366
|
-
*/
|
|
367
|
-
async ensureRlsContext() {
|
|
368
|
-
if (this.rlsState !== "unknown") return this.rlsState === "active";
|
|
369
|
-
const result = await sql`
|
|
370
|
-
SELECT count(*) ::text as count FROM pg_class WHERE relrowsecurity = true
|
|
371
|
-
`.execute(this.kysely);
|
|
372
|
-
const hasRls = Number(result.rows[0]?.count ?? 0) > 0;
|
|
373
|
-
if (!hasRls) {
|
|
374
|
-
this.rlsState = "inactive";
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
this.rlsState = "active";
|
|
378
|
-
await sql`
|
|
379
|
-
DO $$ BEGIN
|
|
380
|
-
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon') THEN
|
|
381
|
-
CREATE ROLE anon NOLOGIN;
|
|
382
|
-
END IF;
|
|
383
|
-
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticated') THEN
|
|
384
|
-
CREATE ROLE authenticated NOLOGIN;
|
|
385
|
-
END IF;
|
|
386
|
-
END $$
|
|
387
|
-
`.execute(this.kysely);
|
|
388
|
-
const schemas = await sql`
|
|
389
|
-
SELECT DISTINCT n.nspname FROM pg_class c
|
|
390
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
391
|
-
WHERE c.relrowsecurity = true
|
|
392
|
-
`.execute(this.kysely);
|
|
393
|
-
for (const { nspname } of schemas.rows) {
|
|
394
|
-
await sql`GRANT USAGE ON SCHEMA ${sql.ref(nspname)} TO anon, authenticated`.execute(
|
|
395
|
-
this.kysely
|
|
396
|
-
);
|
|
397
|
-
await sql.raw(`GRANT ALL ON ALL TABLES IN SCHEMA "${nspname}" TO anon, authenticated`).execute(this.kysely);
|
|
398
|
-
await sql.raw(`GRANT ALL ON ALL SEQUENCES IN SCHEMA "${nspname}" TO anon, authenticated`).execute(this.kysely);
|
|
399
|
-
await sql.raw(
|
|
400
|
-
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${nspname}" GRANT ALL ON TABLES TO anon, authenticated`
|
|
401
|
-
).execute(this.kysely);
|
|
402
|
-
await sql.raw(
|
|
403
|
-
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${nspname}" GRANT ALL ON SEQUENCES TO anon, authenticated`
|
|
404
|
-
).execute(this.kysely);
|
|
405
|
-
}
|
|
406
|
-
return true;
|
|
407
|
-
}
|
|
408
|
-
async withContext(vars, fn) {
|
|
409
|
-
if (!vars) return fn(this.kysely);
|
|
410
|
-
const rlsActive = await this.ensureRlsContext();
|
|
411
|
-
if (!rlsActive) return fn(this.kysely);
|
|
412
|
-
return this.kysely.transaction().execute(async (trx) => {
|
|
413
|
-
const auth = vars.auth;
|
|
414
|
-
const role = auth?.role || "anon";
|
|
415
|
-
const uid = String(auth?.uid ?? "");
|
|
416
|
-
const jwt = auth?.jwt;
|
|
417
|
-
await sql`SELECT set_config('role', ${role}, true)`.execute(trx);
|
|
418
|
-
await sql`SELECT set_config('request.jwt.claim.sub', ${uid}, true)`.execute(trx);
|
|
419
|
-
await sql`SELECT set_config('request.jwt.claim.role', ${role}, true)`.execute(trx);
|
|
420
|
-
if (jwt && typeof jwt === "object") {
|
|
421
|
-
await sql`SELECT set_config('request.jwt.claims', ${JSON.stringify(jwt)}, true)`.execute(
|
|
422
|
-
trx
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
return fn(trx);
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
async onPostgrestAST(ast) {
|
|
429
|
-
if (!ast.from) return ast;
|
|
430
|
-
const schema = ast.schema ?? "public";
|
|
431
|
-
const relation = ast.from;
|
|
432
|
-
const introspection = await this.introspect({ useCache: true });
|
|
433
|
-
if (!introspection.tables.some((t) => t.name === relation && t.schema === schema) && !introspection.views.some((v) => v.name === relation && v.schema === schema)) {
|
|
434
|
-
throw new RelationNotFoundError(schema, relation);
|
|
435
|
-
}
|
|
436
|
-
return ast;
|
|
437
|
-
}
|
|
438
|
-
async transaction(statements, opts) {
|
|
439
|
-
await this.exec("BEGIN");
|
|
440
|
-
try {
|
|
441
|
-
for (const statement of statements) {
|
|
442
|
-
await this.exec(statement);
|
|
443
|
-
}
|
|
444
|
-
await this.exec("COMMIT");
|
|
445
|
-
} catch (error) {
|
|
446
|
-
await this.exec("ROLLBACK");
|
|
447
|
-
throw error;
|
|
448
|
-
} finally {
|
|
449
|
-
if (opts?.intent === "migration") {
|
|
450
|
-
this.introspection = void 0;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
// src/db/postgres/PostgresConnection.ts
|
|
457
|
-
var PostgresConnection = class extends BasePostgresConnection {
|
|
458
|
-
driver;
|
|
459
|
-
dialect = "postgres";
|
|
460
|
-
constructor(config) {
|
|
461
|
-
super(config);
|
|
462
|
-
this.driver = postgres(config.url ?? "", {
|
|
463
|
-
types: {
|
|
464
|
-
// Parse bigint/int8 (OID 20) as JS number instead of string
|
|
465
|
-
bigint: {
|
|
466
|
-
to: 20,
|
|
467
|
-
from: [20],
|
|
468
|
-
serialize: (x) => x.toString(),
|
|
469
|
-
parse: (x) => {
|
|
470
|
-
const n = Number(x);
|
|
471
|
-
if (Number.isSafeInteger(n)) return n;
|
|
472
|
-
return BigInt(x);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
const dialect = new PostgresJSDialect({ postgres: this.driver });
|
|
478
|
-
this.kysely = new Kysely({ dialect });
|
|
479
|
-
}
|
|
480
|
-
async close() {
|
|
481
|
-
await this.driver.end();
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
function createPostgresConnection(config) {
|
|
485
|
-
return new PostgresConnection(config);
|
|
486
|
-
}
|
|
487
|
-
function ensureDataDirExist(dataDir) {
|
|
488
|
-
let path;
|
|
489
|
-
if (typeof dataDir === "string") {
|
|
490
|
-
path = dataDir;
|
|
491
|
-
}
|
|
492
|
-
if (typeof dataDir === "object" && dataDir.dataDir) {
|
|
493
|
-
path = dataDir.dataDir;
|
|
494
|
-
}
|
|
495
|
-
if (path) {
|
|
496
|
-
mkdir(path, { recursive: true }, (err) => {
|
|
497
|
-
if (err) {
|
|
498
|
-
throw err;
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
var PGliteDriver = class {
|
|
504
|
-
#client;
|
|
505
|
-
constructor(client) {
|
|
506
|
-
this.#client = client;
|
|
507
|
-
}
|
|
508
|
-
async acquireConnection() {
|
|
509
|
-
return new PGliteConnection(this.#client);
|
|
510
|
-
}
|
|
511
|
-
async beginTransaction(connection, _settings) {
|
|
512
|
-
await connection.executeQuery(CompiledQuery.raw("BEGIN"));
|
|
513
|
-
}
|
|
514
|
-
async commitTransaction(connection) {
|
|
515
|
-
await connection.executeQuery(CompiledQuery.raw("COMMIT"));
|
|
516
|
-
}
|
|
517
|
-
async rollbackTransaction(connection) {
|
|
518
|
-
await connection.executeQuery(CompiledQuery.raw("ROLLBACK"));
|
|
519
|
-
}
|
|
520
|
-
async destroy() {
|
|
521
|
-
await this.#client.close();
|
|
522
|
-
}
|
|
523
|
-
async init() {
|
|
524
|
-
}
|
|
525
|
-
async releaseConnection(_connection) {
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
var PGliteConnection = class {
|
|
529
|
-
#client;
|
|
530
|
-
constructor(client) {
|
|
531
|
-
this.#client = client;
|
|
532
|
-
}
|
|
533
|
-
async executeQuery(compiledQuery) {
|
|
534
|
-
return await this.#client.query(compiledQuery.sql, [...compiledQuery.parameters]);
|
|
535
|
-
}
|
|
536
|
-
// biome-ignore lint/correctness/useYield: .
|
|
537
|
-
async *streamQuery() {
|
|
538
|
-
throw new Error("PGlite does not support streaming.");
|
|
539
|
-
}
|
|
540
|
-
};
|
|
541
|
-
var KyselyPglite = class {
|
|
542
|
-
client;
|
|
543
|
-
constructor(dataDirOrOptionsOrPGlite, opts) {
|
|
544
|
-
ensureDataDirExist(dataDirOrOptionsOrPGlite);
|
|
545
|
-
let options = { ...opts };
|
|
546
|
-
if (typeof dataDirOrOptionsOrPGlite === "object" && dataDirOrOptionsOrPGlite instanceof PGlite) {
|
|
547
|
-
this.client = dataDirOrOptionsOrPGlite;
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (typeof dataDirOrOptionsOrPGlite === "string") {
|
|
551
|
-
options = {
|
|
552
|
-
dataDir: dataDirOrOptionsOrPGlite,
|
|
553
|
-
...options
|
|
554
|
-
};
|
|
555
|
-
} else {
|
|
556
|
-
options = dataDirOrOptionsOrPGlite ?? {};
|
|
557
|
-
}
|
|
558
|
-
this.client = new PGlite(options);
|
|
559
|
-
}
|
|
560
|
-
dialect = {
|
|
561
|
-
createAdapter: () => new PostgresAdapter(),
|
|
562
|
-
createDriver: () => new PGliteDriver(this.client),
|
|
563
|
-
createIntrospector: (db) => new PostgresIntrospector(db),
|
|
564
|
-
createQueryCompiler: () => new PostgresQueryCompiler()
|
|
565
|
-
};
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
// src/db/postgres/migration/pgdelta-pglite.ts
|
|
569
|
-
var parseArray = (value, parseElement = (entry) => entry) => {
|
|
570
|
-
if (!value || value === "{}") return [];
|
|
571
|
-
const inner = value.slice(1, -1);
|
|
572
|
-
if (inner === "") return [];
|
|
573
|
-
const result = [];
|
|
574
|
-
let current = "";
|
|
575
|
-
let inQuotes = false;
|
|
576
|
-
let depth = 0;
|
|
577
|
-
for (let i = 0; i < inner.length; i++) {
|
|
578
|
-
const char = inner[i];
|
|
579
|
-
if (char === '"' && inner[i - 1] !== "\\") {
|
|
580
|
-
inQuotes = !inQuotes;
|
|
581
|
-
current += char;
|
|
582
|
-
} else if (char === "{" && !inQuotes) {
|
|
583
|
-
depth++;
|
|
584
|
-
current += char;
|
|
585
|
-
} else if (char === "}" && !inQuotes) {
|
|
586
|
-
depth--;
|
|
587
|
-
current += char;
|
|
588
|
-
} else if (char === "," && !inQuotes && depth === 0) {
|
|
589
|
-
result.push(parseElement(current));
|
|
590
|
-
current = "";
|
|
591
|
-
} else {
|
|
592
|
-
current += char;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (current !== "") {
|
|
596
|
-
result.push(parseElement(current));
|
|
597
|
-
}
|
|
598
|
-
return result;
|
|
599
|
-
};
|
|
600
|
-
var parseStringElement = (value) => {
|
|
601
|
-
if (value === "NULL") return null;
|
|
602
|
-
if (value.startsWith('"') && value.endsWith('"')) {
|
|
603
|
-
return value.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
604
|
-
}
|
|
605
|
-
return value;
|
|
606
|
-
};
|
|
607
|
-
var parseIntElement = (value) => {
|
|
608
|
-
if (value === "NULL") return null;
|
|
609
|
-
return Number.parseInt(value, 10);
|
|
610
|
-
};
|
|
611
|
-
function isSqlTagLike(value) {
|
|
612
|
-
return typeof value === "object" && value !== null && "text" in value && "values" in value;
|
|
613
|
-
}
|
|
614
|
-
function normalizeValue(value, dataTypeID) {
|
|
615
|
-
if (value === null || value === void 0) return value;
|
|
616
|
-
switch (dataTypeID) {
|
|
617
|
-
case 20:
|
|
618
|
-
return typeof value === "bigint" ? value : BigInt(value);
|
|
619
|
-
case 22:
|
|
620
|
-
if (Array.isArray(value)) return value;
|
|
621
|
-
if (typeof value === "string") {
|
|
622
|
-
return value.split(" ").map(Number).filter((entry) => !Number.isNaN(entry));
|
|
623
|
-
}
|
|
624
|
-
return value;
|
|
625
|
-
case 1002:
|
|
626
|
-
case 1009:
|
|
627
|
-
case 1015:
|
|
628
|
-
return typeof value === "string" ? parseArray(value, parseStringElement) : value;
|
|
629
|
-
case 1005:
|
|
630
|
-
case 1007:
|
|
631
|
-
case 1016:
|
|
632
|
-
return typeof value === "string" ? parseArray(value, parseIntElement) : value;
|
|
633
|
-
default:
|
|
634
|
-
return value;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
function pgDeltaQueryable(db) {
|
|
638
|
-
return {
|
|
639
|
-
async query(input, values) {
|
|
640
|
-
const sql3 = isSqlTagLike(input) ? input.text : input;
|
|
641
|
-
const parameters = isSqlTagLike(input) ? input.values : values ?? [];
|
|
642
|
-
const result = await db.driver.query(sql3, [...parameters]);
|
|
643
|
-
const rows = result.rows.map(
|
|
644
|
-
(row) => Object.fromEntries(
|
|
645
|
-
result.fields.map((field) => [
|
|
646
|
-
field.name,
|
|
647
|
-
normalizeValue(row[field.name], field.dataTypeID)
|
|
648
|
-
])
|
|
649
|
-
)
|
|
650
|
-
);
|
|
651
|
-
return {
|
|
652
|
-
rows
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// src/db/postgres/migration/PostgresMigrator.ts
|
|
659
|
-
async function loadPgDelta() {
|
|
660
|
-
try {
|
|
661
|
-
return await import('@supabase/pg-delta');
|
|
662
|
-
} catch (error) {
|
|
663
|
-
throw new Error(
|
|
664
|
-
"Postgres/PGlite schema diffing now requires a Bun/Node-compatible runtime with pg-delta available.",
|
|
665
|
-
{ cause: error }
|
|
666
|
-
);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
function parseRiskWarning(input) {
|
|
670
|
-
const dropTableMatch = /^drop table (.+)$/.exec(input);
|
|
671
|
-
if (dropTableMatch) {
|
|
672
|
-
return {
|
|
673
|
-
table: dropTableMatch[1],
|
|
674
|
-
reason: "drop table"
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
const dropColumnMatch = /^drop column (.+) on (.+)$/.exec(input);
|
|
678
|
-
if (dropColumnMatch) {
|
|
679
|
-
return {
|
|
680
|
-
table: dropColumnMatch[2],
|
|
681
|
-
reason: `drop column ${dropColumnMatch[1]}`
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
const dropSequenceMatch = /^drop sequence (.+)$/.exec(input);
|
|
685
|
-
if (dropSequenceMatch) {
|
|
686
|
-
return {
|
|
687
|
-
table: dropSequenceMatch[1],
|
|
688
|
-
reason: "drop sequence"
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
return {
|
|
692
|
-
table: "unknown",
|
|
693
|
-
reason: input
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
var PostgresMigrator = class {
|
|
697
|
-
constructor(from, to, desiredSchema) {
|
|
698
|
-
this.from = from;
|
|
699
|
-
this.to = to;
|
|
700
|
-
this.desiredSchema = desiredSchema;
|
|
701
|
-
}
|
|
702
|
-
async diff() {
|
|
703
|
-
const { createPlan, extractCatalog } = await loadPgDelta();
|
|
704
|
-
const from = pgDeltaQueryable(this.from);
|
|
705
|
-
const to = pgDeltaQueryable(this.to);
|
|
706
|
-
await this.to.exec(this.desiredSchema);
|
|
707
|
-
const [fromCatalog, toCatalog] = await Promise.all([
|
|
708
|
-
extractCatalog(from),
|
|
709
|
-
extractCatalog(to)
|
|
710
|
-
]);
|
|
711
|
-
const result = await createPlan(fromCatalog, toCatalog);
|
|
712
|
-
if (!result) {
|
|
713
|
-
return {
|
|
714
|
-
diff: "",
|
|
715
|
-
plan: {
|
|
716
|
-
steps: [],
|
|
717
|
-
warnings: [],
|
|
718
|
-
unsafe: false
|
|
719
|
-
}
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
const warnings = result.plan.risk?.level === "data_loss" ? result.plan.risk.statements.map(parseRiskWarning) : [];
|
|
723
|
-
return {
|
|
724
|
-
diff: result.plan.statements.join("\n\n"),
|
|
725
|
-
plan: {
|
|
726
|
-
steps: result.plan.statements.map((sql3) => ({ sql: sql3 })),
|
|
727
|
-
warnings,
|
|
728
|
-
unsafe: result.plan.risk?.level === "data_loss"
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
async migratePlan(planResult, opts) {
|
|
733
|
-
if (planResult.steps.length === 0) {
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
736
|
-
if (planResult.unsafe && !opts?.force) {
|
|
737
|
-
throw new DataLossError(planResult.warnings ?? []);
|
|
738
|
-
}
|
|
739
|
-
await this.from.transaction(
|
|
740
|
-
planResult.steps.map((step) => step.sql),
|
|
741
|
-
{ intent: "migration" }
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
async migrate(opts) {
|
|
745
|
-
const result = await this.diff();
|
|
746
|
-
await this.migratePlan(result.plan, opts);
|
|
747
|
-
return result;
|
|
748
|
-
}
|
|
749
|
-
safeSortPlanSteps(steps) {
|
|
750
|
-
return steps;
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
|
|
754
|
-
// src/db/postgres/pglite/PgliteConnection.ts
|
|
755
|
-
var PgliteConnection = class _PgliteConnection extends BasePostgresConnection {
|
|
756
|
-
driver;
|
|
757
|
-
dialect = "postgres";
|
|
758
|
-
constructor(config = {}) {
|
|
759
|
-
const client = new PGlite({
|
|
760
|
-
...config.pgliteOptions,
|
|
761
|
-
dataDir: config.url ?? void 0,
|
|
762
|
-
extensions: {
|
|
763
|
-
...config.pgliteOptions?.extensions,
|
|
764
|
-
isn,
|
|
765
|
-
ltree,
|
|
766
|
-
file_fdw,
|
|
767
|
-
pgcrypto
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
const { dialect } = new KyselyPglite(client);
|
|
771
|
-
super(config);
|
|
772
|
-
this.driver = client;
|
|
773
|
-
this.kysely = new Kysely({ dialect });
|
|
774
|
-
}
|
|
775
|
-
async exec(query, ...parameters) {
|
|
776
|
-
try {
|
|
777
|
-
if (parameters.length > 0) {
|
|
778
|
-
const { rows } = await sql(query, ...parameters ?? []).execute(this.kysely);
|
|
779
|
-
return { rows };
|
|
780
|
-
}
|
|
781
|
-
const result = await this.driver.exec(query);
|
|
782
|
-
return {
|
|
783
|
-
rows: result[0].rows
|
|
784
|
-
};
|
|
785
|
-
} catch (error) {
|
|
786
|
-
console.error(error);
|
|
787
|
-
throw new Error(`Failed to execute query: ${query}`);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
async close() {
|
|
791
|
-
await this.driver.close();
|
|
792
|
-
}
|
|
793
|
-
createMigrator(desiredSchema) {
|
|
794
|
-
const migrationSchema = [this.config.baseSchema, desiredSchema].filter(Boolean).join("\n\n");
|
|
795
|
-
return new PostgresMigrator(this, new _PgliteConnection(), migrationSchema);
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
async function createPgliteConnection(config = {}) {
|
|
799
|
-
return new PgliteConnection(config);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
export { PgliteConnection, PostgresConnection, createPgliteConnection as createConnection, createPgliteConnection, createPostgresConnection };
|