@gencow/core 0.1.19 → 0.1.22

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 (42) hide show
  1. package/dist/crud.d.ts +30 -12
  2. package/dist/crud.js +233 -52
  3. package/dist/index.d.ts +18 -17
  4. package/dist/index.js +10 -10
  5. package/dist/reactive.d.ts +4 -4
  6. package/dist/rls-db.d.ts +3 -5
  7. package/dist/rls-db.js +3 -5
  8. package/dist/rls.d.ts +44 -1
  9. package/dist/rls.js +62 -2
  10. package/dist/server.d.ts +5 -4
  11. package/dist/server.js +4 -4
  12. package/dist/storage.d.ts +29 -2
  13. package/dist/storage.js +396 -8
  14. package/package.json +6 -2
  15. package/src/__tests__/crud-owner-rls.test.ts +380 -0
  16. package/src/__tests__/fixtures/basic/auth.ts +32 -0
  17. package/src/__tests__/fixtures/basic/drizzle.config.ts +15 -0
  18. package/src/__tests__/fixtures/basic/index.ts +6 -0
  19. package/src/__tests__/fixtures/basic/migrations/0000_faithful_silver_sable.sql +66 -0
  20. package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +438 -0
  21. package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +13 -0
  22. package/src/__tests__/fixtures/basic/schema.ts +35 -0
  23. package/src/__tests__/fixtures/basic/tasks.ts +15 -0
  24. package/src/__tests__/fixtures/common/auth-schema.ts +63 -0
  25. package/src/__tests__/helpers/pglite-migrations.ts +35 -0
  26. package/src/__tests__/helpers/pglite-rls-session.ts +54 -0
  27. package/src/__tests__/helpers/seed-like-fill.ts +196 -0
  28. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +53 -0
  29. package/src/__tests__/image-optimization.test.ts +652 -0
  30. package/src/__tests__/rls-crud-basic.test.ts +431 -0
  31. package/src/__tests__/tsconfig.json +8 -0
  32. package/src/crud.ts +272 -49
  33. package/src/index.ts +18 -17
  34. package/src/reactive.ts +4 -4
  35. package/src/rls-db.ts +3 -5
  36. package/src/rls.ts +87 -3
  37. package/src/server.ts +5 -4
  38. package/src/storage.ts +473 -8
  39. package/dist/scoped-db.d.ts +0 -34
  40. package/dist/scoped-db.js +0 -364
  41. package/dist/table.d.ts +0 -67
  42. package/dist/table.js +0 -98
@@ -0,0 +1,438 @@
1
+ {
2
+ "id": "5dae5382-aa29-4251-9a52-0df4786c5100",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.tasks": {
8
+ "name": "tasks",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "title": {
18
+ "name": "title",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "description": {
24
+ "name": "description",
25
+ "type": "text",
26
+ "primaryKey": false,
27
+ "notNull": false
28
+ },
29
+ "done": {
30
+ "name": "done",
31
+ "type": "boolean",
32
+ "primaryKey": false,
33
+ "notNull": true,
34
+ "default": false
35
+ },
36
+ "user_id": {
37
+ "name": "user_id",
38
+ "type": "text",
39
+ "primaryKey": false,
40
+ "notNull": true
41
+ },
42
+ "created_at": {
43
+ "name": "created_at",
44
+ "type": "timestamp",
45
+ "primaryKey": false,
46
+ "notNull": true,
47
+ "default": "now()"
48
+ },
49
+ "updated_at": {
50
+ "name": "updated_at",
51
+ "type": "timestamp",
52
+ "primaryKey": false,
53
+ "notNull": true,
54
+ "default": "now()"
55
+ }
56
+ },
57
+ "indexes": {},
58
+ "foreignKeys": {
59
+ "tasks_user_id_user_id_fk": {
60
+ "name": "tasks_user_id_user_id_fk",
61
+ "tableFrom": "tasks",
62
+ "tableTo": "user",
63
+ "columnsFrom": [
64
+ "user_id"
65
+ ],
66
+ "columnsTo": [
67
+ "id"
68
+ ],
69
+ "onDelete": "cascade",
70
+ "onUpdate": "no action"
71
+ }
72
+ },
73
+ "compositePrimaryKeys": {},
74
+ "uniqueConstraints": {},
75
+ "policies": {
76
+ "rls-select": {
77
+ "name": "rls-select",
78
+ "as": "PERMISSIVE",
79
+ "for": "SELECT",
80
+ "to": [
81
+ "public"
82
+ ],
83
+ "using": "\"tasks\".\"user_id\" = current_setting('app.current_user_id', true)"
84
+ },
85
+ "rls-insert": {
86
+ "name": "rls-insert",
87
+ "as": "PERMISSIVE",
88
+ "for": "INSERT",
89
+ "to": [
90
+ "public"
91
+ ],
92
+ "withCheck": "\"tasks\".\"user_id\" = current_setting('app.current_user_id', true)"
93
+ },
94
+ "rls-update": {
95
+ "name": "rls-update",
96
+ "as": "PERMISSIVE",
97
+ "for": "UPDATE",
98
+ "to": [
99
+ "public"
100
+ ],
101
+ "using": "\"tasks\".\"user_id\" = current_setting('app.current_user_id', true)",
102
+ "withCheck": "\"tasks\".\"user_id\" = current_setting('app.current_user_id', true)"
103
+ },
104
+ "rls-delete": {
105
+ "name": "rls-delete",
106
+ "as": "PERMISSIVE",
107
+ "for": "DELETE",
108
+ "to": [
109
+ "public"
110
+ ],
111
+ "using": "\"tasks\".\"user_id\" = current_setting('app.current_user_id', true)"
112
+ }
113
+ },
114
+ "checkConstraints": {},
115
+ "isRLSEnabled": false
116
+ },
117
+ "public.user": {
118
+ "name": "user",
119
+ "schema": "",
120
+ "columns": {
121
+ "id": {
122
+ "name": "id",
123
+ "type": "text",
124
+ "primaryKey": true,
125
+ "notNull": true
126
+ },
127
+ "name": {
128
+ "name": "name",
129
+ "type": "text",
130
+ "primaryKey": false,
131
+ "notNull": true
132
+ },
133
+ "email": {
134
+ "name": "email",
135
+ "type": "text",
136
+ "primaryKey": false,
137
+ "notNull": true
138
+ },
139
+ "email_verified": {
140
+ "name": "email_verified",
141
+ "type": "boolean",
142
+ "primaryKey": false,
143
+ "notNull": true,
144
+ "default": false
145
+ },
146
+ "image": {
147
+ "name": "image",
148
+ "type": "text",
149
+ "primaryKey": false,
150
+ "notNull": false
151
+ },
152
+ "created_at": {
153
+ "name": "created_at",
154
+ "type": "timestamp",
155
+ "primaryKey": false,
156
+ "notNull": true,
157
+ "default": "now()"
158
+ },
159
+ "updated_at": {
160
+ "name": "updated_at",
161
+ "type": "timestamp",
162
+ "primaryKey": false,
163
+ "notNull": true,
164
+ "default": "now()"
165
+ }
166
+ },
167
+ "indexes": {},
168
+ "foreignKeys": {},
169
+ "compositePrimaryKeys": {},
170
+ "uniqueConstraints": {
171
+ "user_email_unique": {
172
+ "name": "user_email_unique",
173
+ "nullsNotDistinct": false,
174
+ "columns": [
175
+ "email"
176
+ ]
177
+ }
178
+ },
179
+ "policies": {},
180
+ "checkConstraints": {},
181
+ "isRLSEnabled": false
182
+ },
183
+ "public.account": {
184
+ "name": "account",
185
+ "schema": "",
186
+ "columns": {
187
+ "id": {
188
+ "name": "id",
189
+ "type": "text",
190
+ "primaryKey": true,
191
+ "notNull": true
192
+ },
193
+ "account_id": {
194
+ "name": "account_id",
195
+ "type": "text",
196
+ "primaryKey": false,
197
+ "notNull": true
198
+ },
199
+ "provider_id": {
200
+ "name": "provider_id",
201
+ "type": "text",
202
+ "primaryKey": false,
203
+ "notNull": true
204
+ },
205
+ "user_id": {
206
+ "name": "user_id",
207
+ "type": "text",
208
+ "primaryKey": false,
209
+ "notNull": true
210
+ },
211
+ "access_token": {
212
+ "name": "access_token",
213
+ "type": "text",
214
+ "primaryKey": false,
215
+ "notNull": false
216
+ },
217
+ "refresh_token": {
218
+ "name": "refresh_token",
219
+ "type": "text",
220
+ "primaryKey": false,
221
+ "notNull": false
222
+ },
223
+ "id_token": {
224
+ "name": "id_token",
225
+ "type": "text",
226
+ "primaryKey": false,
227
+ "notNull": false
228
+ },
229
+ "access_token_expires_at": {
230
+ "name": "access_token_expires_at",
231
+ "type": "timestamp",
232
+ "primaryKey": false,
233
+ "notNull": false
234
+ },
235
+ "refresh_token_expires_at": {
236
+ "name": "refresh_token_expires_at",
237
+ "type": "timestamp",
238
+ "primaryKey": false,
239
+ "notNull": false
240
+ },
241
+ "scope": {
242
+ "name": "scope",
243
+ "type": "text",
244
+ "primaryKey": false,
245
+ "notNull": false
246
+ },
247
+ "password": {
248
+ "name": "password",
249
+ "type": "text",
250
+ "primaryKey": false,
251
+ "notNull": false
252
+ },
253
+ "created_at": {
254
+ "name": "created_at",
255
+ "type": "timestamp",
256
+ "primaryKey": false,
257
+ "notNull": true,
258
+ "default": "now()"
259
+ },
260
+ "updated_at": {
261
+ "name": "updated_at",
262
+ "type": "timestamp",
263
+ "primaryKey": false,
264
+ "notNull": true,
265
+ "default": "now()"
266
+ }
267
+ },
268
+ "indexes": {},
269
+ "foreignKeys": {
270
+ "account_user_id_user_id_fk": {
271
+ "name": "account_user_id_user_id_fk",
272
+ "tableFrom": "account",
273
+ "tableTo": "user",
274
+ "columnsFrom": [
275
+ "user_id"
276
+ ],
277
+ "columnsTo": [
278
+ "id"
279
+ ],
280
+ "onDelete": "cascade",
281
+ "onUpdate": "no action"
282
+ }
283
+ },
284
+ "compositePrimaryKeys": {},
285
+ "uniqueConstraints": {},
286
+ "policies": {},
287
+ "checkConstraints": {},
288
+ "isRLSEnabled": false
289
+ },
290
+ "public.session": {
291
+ "name": "session",
292
+ "schema": "",
293
+ "columns": {
294
+ "id": {
295
+ "name": "id",
296
+ "type": "text",
297
+ "primaryKey": true,
298
+ "notNull": true
299
+ },
300
+ "expires_at": {
301
+ "name": "expires_at",
302
+ "type": "timestamp",
303
+ "primaryKey": false,
304
+ "notNull": true
305
+ },
306
+ "token": {
307
+ "name": "token",
308
+ "type": "text",
309
+ "primaryKey": false,
310
+ "notNull": true
311
+ },
312
+ "created_at": {
313
+ "name": "created_at",
314
+ "type": "timestamp",
315
+ "primaryKey": false,
316
+ "notNull": true,
317
+ "default": "now()"
318
+ },
319
+ "updated_at": {
320
+ "name": "updated_at",
321
+ "type": "timestamp",
322
+ "primaryKey": false,
323
+ "notNull": true,
324
+ "default": "now()"
325
+ },
326
+ "ip_address": {
327
+ "name": "ip_address",
328
+ "type": "text",
329
+ "primaryKey": false,
330
+ "notNull": false
331
+ },
332
+ "user_agent": {
333
+ "name": "user_agent",
334
+ "type": "text",
335
+ "primaryKey": false,
336
+ "notNull": false
337
+ },
338
+ "user_id": {
339
+ "name": "user_id",
340
+ "type": "text",
341
+ "primaryKey": false,
342
+ "notNull": true
343
+ }
344
+ },
345
+ "indexes": {},
346
+ "foreignKeys": {
347
+ "session_user_id_user_id_fk": {
348
+ "name": "session_user_id_user_id_fk",
349
+ "tableFrom": "session",
350
+ "tableTo": "user",
351
+ "columnsFrom": [
352
+ "user_id"
353
+ ],
354
+ "columnsTo": [
355
+ "id"
356
+ ],
357
+ "onDelete": "cascade",
358
+ "onUpdate": "no action"
359
+ }
360
+ },
361
+ "compositePrimaryKeys": {},
362
+ "uniqueConstraints": {
363
+ "session_token_unique": {
364
+ "name": "session_token_unique",
365
+ "nullsNotDistinct": false,
366
+ "columns": [
367
+ "token"
368
+ ]
369
+ }
370
+ },
371
+ "policies": {},
372
+ "checkConstraints": {},
373
+ "isRLSEnabled": false
374
+ },
375
+ "public.verification": {
376
+ "name": "verification",
377
+ "schema": "",
378
+ "columns": {
379
+ "id": {
380
+ "name": "id",
381
+ "type": "text",
382
+ "primaryKey": true,
383
+ "notNull": true
384
+ },
385
+ "identifier": {
386
+ "name": "identifier",
387
+ "type": "text",
388
+ "primaryKey": false,
389
+ "notNull": true
390
+ },
391
+ "value": {
392
+ "name": "value",
393
+ "type": "text",
394
+ "primaryKey": false,
395
+ "notNull": true
396
+ },
397
+ "expires_at": {
398
+ "name": "expires_at",
399
+ "type": "timestamp",
400
+ "primaryKey": false,
401
+ "notNull": true
402
+ },
403
+ "created_at": {
404
+ "name": "created_at",
405
+ "type": "timestamp",
406
+ "primaryKey": false,
407
+ "notNull": false,
408
+ "default": "now()"
409
+ },
410
+ "updated_at": {
411
+ "name": "updated_at",
412
+ "type": "timestamp",
413
+ "primaryKey": false,
414
+ "notNull": false,
415
+ "default": "now()"
416
+ }
417
+ },
418
+ "indexes": {},
419
+ "foreignKeys": {},
420
+ "compositePrimaryKeys": {},
421
+ "uniqueConstraints": {},
422
+ "policies": {},
423
+ "checkConstraints": {},
424
+ "isRLSEnabled": false
425
+ }
426
+ },
427
+ "enums": {},
428
+ "schemas": {},
429
+ "sequences": {},
430
+ "roles": {},
431
+ "policies": {},
432
+ "views": {},
433
+ "_meta": {
434
+ "columns": {},
435
+ "schemas": {},
436
+ "tables": {}
437
+ }
438
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1776143474320,
9
+ "tag": "0000_faithful_silver_sable",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * gencow/schema.ts — Task App 스키마
3
+ *
4
+ * 🔒 Secure by Default:
5
+ * - pgTable + ownerRls + crud로 사용자별 데이터 자동 격리
6
+ * - onDelete: "cascade"로 유저 삭제 시 관련 데이터 자동 정리
7
+ *
8
+ * 변경 후: gencow dev가 자동 반영
9
+ */
10
+ import { ownerRls } from "../../../rls";
11
+ import { pgTable } from "drizzle-orm/pg-core";
12
+ import { text, boolean, timestamp } from "drizzle-orm/pg-core";
13
+ import { user } from "../common/auth-schema";
14
+ import { v4 as uuidv4 } from "uuid";
15
+
16
+ export { user } from "../common/auth-schema";
17
+
18
+ export const tasks = pgTable(
19
+ "tasks",
20
+ {
21
+ id: text("id")
22
+ .primaryKey()
23
+ .$defaultFn(() => uuidv4()),
24
+ title: text("title").notNull(),
25
+ description: text("description"),
26
+ done: boolean("done").default(false).notNull(),
27
+ // 🔒 RLS (Role-Level Security)
28
+ userId: text("user_id")
29
+ .notNull()
30
+ .references(() => user.id, { onDelete: "cascade" }),
31
+ createdAt: timestamp("created_at").defaultNow().notNull(),
32
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
33
+ },
34
+ (t) => ownerRls(t.userId)
35
+ );
@@ -0,0 +1,15 @@
1
+ /**
2
+ * gencow/tasks.ts — Task CRUD (Zero-Boilerplate Pattern)
3
+ *
4
+ * 🔒 Secure by Default:
5
+ * - ctx.db uses RLS (Row-Level Security) — auto-filters by userId
6
+ * - crud auto-registers query/mutation with auth + realtime
7
+ */
8
+ import { crud } from "../../../crud";
9
+ import { tasks } from "./schema";
10
+
11
+ // 자동 생성: list, get, create, update, remove (query + mutation + auth + realtime)
12
+ export const { list, get, create, update, remove } = crud(tasks, {
13
+ searchFields: ["title", "description"],
14
+ defaultLimit: 50,
15
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * packages/server/src/auth-schema.ts
3
+ *
4
+ * better-auth가 사용하는 Drizzle 스키마 테이블 정의.
5
+ * better-auth는 user, session, account, verification 4개 테이블을 필요로 합니다.
6
+ *
7
+ * @see https://www.better-auth.com/docs/adapters/drizzle
8
+ */
9
+ import { pgTable, text, boolean, timestamp } from "drizzle-orm/pg-core";
10
+
11
+ // ─── user 테이블 ────────────────────────────────────────
12
+
13
+ export const user = pgTable("user", {
14
+ id: text("id").primaryKey(),
15
+ name: text("name").notNull(),
16
+ email: text("email").notNull().unique(),
17
+ emailVerified: boolean("email_verified").notNull().default(false),
18
+ image: text("image"),
19
+ createdAt: timestamp("created_at").notNull().defaultNow(),
20
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
21
+ });
22
+
23
+ // ─── session 테이블 ─────────────────────────────────────
24
+
25
+ export const session = pgTable("session", {
26
+ id: text("id").primaryKey(),
27
+ expiresAt: timestamp("expires_at").notNull(),
28
+ token: text("token").notNull().unique(),
29
+ createdAt: timestamp("created_at").notNull().defaultNow(),
30
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
31
+ ipAddress: text("ip_address"),
32
+ userAgent: text("user_agent"),
33
+ userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
34
+ });
35
+
36
+ // ─── account 테이블 ─────────────────────────────────────
37
+
38
+ export const account = pgTable("account", {
39
+ id: text("id").primaryKey(),
40
+ accountId: text("account_id").notNull(),
41
+ providerId: text("provider_id").notNull(),
42
+ userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
43
+ accessToken: text("access_token"),
44
+ refreshToken: text("refresh_token"),
45
+ idToken: text("id_token"),
46
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
47
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
48
+ scope: text("scope"),
49
+ password: text("password"),
50
+ createdAt: timestamp("created_at").notNull().defaultNow(),
51
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
52
+ });
53
+
54
+ // ─── verification 테이블 ────────────────────────────────
55
+
56
+ export const verification = pgTable("verification", {
57
+ id: text("id").primaryKey(),
58
+ identifier: text("identifier").notNull(),
59
+ value: text("value").notNull(),
60
+ expiresAt: timestamp("expires_at").notNull(),
61
+ createdAt: timestamp("created_at").defaultNow(),
62
+ updatedAt: timestamp("updated_at").defaultNow(),
63
+ });
@@ -0,0 +1,35 @@
1
+ import { readFileSync, readdirSync } from "fs";
2
+ import { join } from "path";
3
+ import type { PGlite } from "@electric-sql/pglite";
4
+
5
+ function listMigrationSqlFiles(migrationsDir: string): string[] {
6
+ return readdirSync(migrationsDir)
7
+ .filter((f) => f.endsWith(".sql"))
8
+ .sort()
9
+ .map((f) => join(migrationsDir, f));
10
+ }
11
+
12
+ /**
13
+ * Apply Drizzle-generated SQL from a folder of `.sql` files (split on `--> statement-breakpoint`).
14
+ */
15
+ export async function loadAndApplyMigrations(
16
+ client: PGlite,
17
+ migrationsDir: string
18
+ ): Promise<void> {
19
+ const files = listMigrationSqlFiles(migrationsDir);
20
+ if (files.length === 0) {
21
+ throw new Error(
22
+ `No .sql migration files in ${migrationsDir} (generate migrations with your Drizzle workflow)`
23
+ );
24
+ }
25
+ for (const filePath of files) {
26
+ const raw = readFileSync(filePath, "utf8");
27
+ const chunks = raw
28
+ .split(/--> statement-breakpoint/g)
29
+ .map((s) => s.trim())
30
+ .filter(Boolean);
31
+ for (const sqlChunk of chunks) {
32
+ await client.exec(sqlChunk);
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,54 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+
3
+ /** Default role used in PGlite tests when you need RLS to apply (session user must not own the tables). */
4
+ export const DEFAULT_PGLITE_RLS_APP_ROLE = "gencow_rls_app";
5
+
6
+ function quoteIdent(name: string): string {
7
+ if (!/^[a-z_][a-z0-9_]*$/i.test(name)) {
8
+ throw new Error(`Invalid SQL identifier: ${name}`);
9
+ }
10
+ return `"${name.replace(/"/g, '""')}"`;
11
+ }
12
+
13
+ /**
14
+ * After migrations (and optional seed as the bootstrap user), create a non-superuser role and grant
15
+ * DML on `public` so queries run as **non-owner** → PostgreSQL applies RLS policies without
16
+ * `FORCE ROW LEVEL SECURITY`.
17
+ *
18
+ * Omits `GRANT CONNECT ON DATABASE` — PGlite’s default DB can be `template1` and that grant has
19
+ * caused engine errors; schema/table privileges are enough for the embedded single-connection case.
20
+ *
21
+ * Call {@link setPgliteSessionRole} on the same `PGlite` instance before running app queries.
22
+ */
23
+ export async function createPgliteRlsAppRole(
24
+ client: PGlite,
25
+ options?: { roleName?: string }
26
+ ): Promise<string> {
27
+ const roleName = options?.roleName ?? DEFAULT_PGLITE_RLS_APP_ROLE;
28
+ const role = quoteIdent(roleName);
29
+
30
+ await client.exec(`
31
+ CREATE ROLE ${role} LOGIN;
32
+ GRANT USAGE ON SCHEMA public TO ${role};
33
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO ${role};
34
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO ${role};
35
+ `);
36
+
37
+ return roleName;
38
+ }
39
+
40
+ /**
41
+ * Run follow-up queries in this session as the given role (must not be table owner so RLS applies).
42
+ * The bootstrap user must be allowed to `SET ROLE` (e.g. superuser, or `GRANT rls_role TO bootstrap`).
43
+ */
44
+ export async function setPgliteSessionRole(
45
+ client: PGlite,
46
+ roleName: string
47
+ ): Promise<void> {
48
+ await client.exec(`SET ROLE ${quoteIdent(roleName)}`);
49
+ }
50
+
51
+ /** Restore session user to the original login role (typically the PGlite bootstrap user). */
52
+ export async function resetPgliteSessionRole(client: PGlite): Promise<void> {
53
+ await client.exec("RESET ROLE");
54
+ }