gencow 0.1.125 → 0.1.127
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/bin/gencow.mjs +176 -30
- package/core/index.js +192 -5
- package/dashboard/assets/{index-C1WhUSu8.js → index-D3c-3Tbj.js} +73 -74
- package/dashboard/assets/index-treFoOsZ.css +1 -0
- package/dashboard/index.html +2 -2
- package/package.json +1 -1
- package/server/index.js +203 -16
- package/server/index.js.map +3 -3
- package/dashboard/assets/index-DoG4z4Kv.css +0 -1
package/bin/gencow.mjs
CHANGED
|
@@ -425,6 +425,40 @@ function spawnInServer(cmd, args, env = process.env) {
|
|
|
425
425
|
return spawn(cmd, args, { cwd: serverRoot, stdio: "inherit", shell: true, env });
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
// ─── Core Import Resolution ───────────────────────────────
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @gencow/core import 경로 해석 — 3단계 fallback
|
|
432
|
+
*
|
|
433
|
+
* 1. CLI 패키지 내 번들된 core (../core/index.js relative to server/)
|
|
434
|
+
* 2. 사용자 프로젝트 node_modules/@gencow/core/dist/index.js (절대 경로)
|
|
435
|
+
* 3. bare specifier "@gencow/core" (모노레포 등 이미 resolve 가능한 경우)
|
|
436
|
+
*
|
|
437
|
+
* Bun ESM은 NODE_PATH를 무시하므로, bare specifier fallback은
|
|
438
|
+
* .gencow-extract.ts 파일 위치(CLI server 디렉토리) 기준으로만 해석됨.
|
|
439
|
+
* 따라서 2단계에서 프로젝트 node_modules의 절대 경로를 명시적으로 시도하여
|
|
440
|
+
* 글로벌/npx 설치 환경에서도 안정적으로 resolve.
|
|
441
|
+
*
|
|
442
|
+
* @param {string} serverRoot - findServerRoot()가 반환한 서버 루트 경로
|
|
443
|
+
* @returns {string} import 경로 (절대 경로 또는 bare specifier)
|
|
444
|
+
*/
|
|
445
|
+
function resolveCoreImport(serverRoot) {
|
|
446
|
+
// 1차: CLI 패키지 내 번들된 core
|
|
447
|
+
const bundledCorePath = resolve(serverRoot, "../core/index.js");
|
|
448
|
+
if (existsSync(bundledCorePath)) return bundledCorePath.replace(/\\/g, "/");
|
|
449
|
+
|
|
450
|
+
// 2차: 사용자 프로젝트 node_modules (절대 경로 — Bun ESM에서 확실히 resolve)
|
|
451
|
+
const cwdCoreDist = resolve(process.cwd(), "node_modules/@gencow/core/dist/index.js");
|
|
452
|
+
if (existsSync(cwdCoreDist)) return cwdCoreDist.replace(/\\/g, "/");
|
|
453
|
+
|
|
454
|
+
// 2차-b: src/ 직접 참조 (모노레포 workspace 등에서 dist 미빌드 시)
|
|
455
|
+
const cwdCoreSrc = resolve(process.cwd(), "node_modules/@gencow/core/src/index.ts");
|
|
456
|
+
if (existsSync(cwdCoreSrc)) return cwdCoreSrc.replace(/\\/g, "/");
|
|
457
|
+
|
|
458
|
+
// 3차: bare specifier — 모노레포 또는 이미 resolve 가능한 환경
|
|
459
|
+
return "@gencow/core";
|
|
460
|
+
}
|
|
461
|
+
|
|
428
462
|
// ─── API Codegen ──────────────────────────────────────────
|
|
429
463
|
|
|
430
464
|
function generateApiTs(config) {
|
|
@@ -433,11 +467,8 @@ function generateApiTs(config) {
|
|
|
433
467
|
const apiTsPath = resolve(absoluteFunctions, "api.ts");
|
|
434
468
|
const extractTsPath = resolve(serverRoot, ".gencow-extract.ts");
|
|
435
469
|
|
|
436
|
-
// Resolve @gencow/core — bundled
|
|
437
|
-
const
|
|
438
|
-
const coreImport = existsSync(bundledCorePath)
|
|
439
|
-
? bundledCorePath.replace(/\\/g, "/") // Windows 호환
|
|
440
|
-
: "@gencow/core"; // 모노레포 등 이미 resolve 가능한 경우
|
|
470
|
+
// Resolve @gencow/core — 3단계 fallback (bundled → CWD node_modules → bare specifier)
|
|
471
|
+
const coreImport = resolveCoreImport(serverRoot);
|
|
441
472
|
|
|
442
473
|
const script = `
|
|
443
474
|
import { getRegisteredQueries, getRegisteredMutations } from "${coreImport}";
|
|
@@ -677,6 +708,11 @@ function cleanupOldBackups(backupDir, keep = 3) {
|
|
|
677
708
|
} catch { /* ignore */ }
|
|
678
709
|
}
|
|
679
710
|
|
|
711
|
+
function isLocalDbTarget(args) {
|
|
712
|
+
if (process.env.GENCOW_LOCAL === "1") return true;
|
|
713
|
+
return Array.isArray(args) && args.includes("--local");
|
|
714
|
+
}
|
|
715
|
+
|
|
680
716
|
// ─── Commands ─────────────────────────────────────────────
|
|
681
717
|
|
|
682
718
|
const commands = {
|
|
@@ -783,8 +819,8 @@ const commands = {
|
|
|
783
819
|
"@gencow/core": "latest",
|
|
784
820
|
"@electric-sql/pglite": "^0.3.15",
|
|
785
821
|
"better-auth": "^1.5.1",
|
|
786
|
-
"drizzle-orm": "^0.
|
|
787
|
-
"drizzle-kit": "^0.31.
|
|
822
|
+
"drizzle-orm": "^0.45.1",
|
|
823
|
+
"drizzle-kit": "^0.31.4",
|
|
788
824
|
"postgres": "^3.4.8",
|
|
789
825
|
};
|
|
790
826
|
const pkgJsonPath = resolve(projectDir, "package.json");
|
|
@@ -1049,9 +1085,81 @@ ${hasPrompt ? `
|
|
|
1049
1085
|
},
|
|
1050
1086
|
|
|
1051
1087
|
// ── db:reset ─────────────────────────────────────────
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1088
|
+
// TRUNCATE all user tables + seed.ts 실행.
|
|
1089
|
+
// 로컬 전용: Cloud DB reset은 위험하므로 CLI에서 미지원 (Dashboard에서만 가능)
|
|
1090
|
+
// 사용: gencow db:reset --local
|
|
1091
|
+
async "db:reset"(...args) {
|
|
1092
|
+
const config = loadConfig();
|
|
1093
|
+
const cwd = process.cwd();
|
|
1094
|
+
const isLocal = isLocalDbTarget(args);
|
|
1095
|
+
|
|
1096
|
+
if (!isLocal) {
|
|
1097
|
+
error("db:reset only applies to the local database.");
|
|
1098
|
+
info(`${DIM}Cloud DB reset is only available via Dashboard (data protection).${RESET}`);
|
|
1099
|
+
info(`${DIM}For local DB: set ${GREEN}GENCOW_LOCAL=1${RESET}${DIM} and run this command again.${RESET}`);
|
|
1100
|
+
log("");
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const port = process.env.PORT || config.port || 5456;
|
|
1105
|
+
|
|
1106
|
+
log(`\n${BOLD}${CYAN}Gencow DB Reset${RESET}\n`);
|
|
1107
|
+
|
|
1108
|
+
let serverAlive = false;
|
|
1109
|
+
try {
|
|
1110
|
+
const res = await fetch(`http://localhost:${port}/_admin/status`, { signal: AbortSignal.timeout(2000) });
|
|
1111
|
+
serverAlive = res.ok;
|
|
1112
|
+
} catch { /* 서버 꺼져있음 */ }
|
|
1113
|
+
|
|
1114
|
+
if (serverAlive) {
|
|
1115
|
+
info(`Server detected on :${port} — using API reset (TRUNCATE + seed)`);
|
|
1116
|
+
log("");
|
|
1117
|
+
|
|
1118
|
+
try {
|
|
1119
|
+
const res = await fetch(`http://localhost:${port}/_admin/reset`, {
|
|
1120
|
+
method: "POST",
|
|
1121
|
+
headers: { "Content-Type": "application/json" },
|
|
1122
|
+
body: JSON.stringify({ confirm: "delete all data" }),
|
|
1123
|
+
});
|
|
1124
|
+
const data = await res.json();
|
|
1125
|
+
|
|
1126
|
+
if (!res.ok) {
|
|
1127
|
+
error(`Reset failed: ${data.error || "Unknown error"}`);
|
|
1128
|
+
log("");
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
success(`TRUNCATE done: ${data.truncated?.length || 0} tables`);
|
|
1133
|
+
if (data.truncated?.length > 0) {
|
|
1134
|
+
info(` ${DIM}${data.truncated.join(", ")}${RESET}`);
|
|
1135
|
+
}
|
|
1136
|
+
if (data.seeded) {
|
|
1137
|
+
success(`Seed completed ✓`);
|
|
1138
|
+
} else {
|
|
1139
|
+
info(`${DIM}No seed.ts found — skipping${RESET}`);
|
|
1140
|
+
}
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
error(`Server connection failed: ${e.message}`);
|
|
1143
|
+
}
|
|
1144
|
+
} else {
|
|
1145
|
+
const dbPath = resolve(cwd, config.db.url);
|
|
1146
|
+
if (existsSync(dbPath)) {
|
|
1147
|
+
const backupDir = resolve(cwd, ".gencow", "backups");
|
|
1148
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1149
|
+
const backupPath = resolve(backupDir, ts);
|
|
1150
|
+
mkdirSync(backupPath, { recursive: true });
|
|
1151
|
+
cpSync(dbPath, backupPath, { recursive: true });
|
|
1152
|
+
success(`Backup created: .gencow/backups/${ts}`);
|
|
1153
|
+
|
|
1154
|
+
cleanupOldBackups(backupDir, 3);
|
|
1155
|
+
|
|
1156
|
+
rmSync(dbPath, { recursive: true, force: true });
|
|
1157
|
+
success("DB deleted — restart with gencow dev:local to create a new one");
|
|
1158
|
+
} else {
|
|
1159
|
+
info("DB does not exist yet. Run gencow dev:local to get started.");
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
log("");
|
|
1055
1163
|
},
|
|
1056
1164
|
|
|
1057
1165
|
// ── db:seed ──────────────────────────────────────────
|
|
@@ -1059,11 +1167,48 @@ ${hasPrompt ? `
|
|
|
1059
1167
|
// Cloud-first: 기본=Cloud, --local=로컬
|
|
1060
1168
|
async "db:seed"(...args) {
|
|
1061
1169
|
const config = loadConfig();
|
|
1062
|
-
const isLocal = args
|
|
1170
|
+
const isLocal = isLocalDbTarget(args);
|
|
1063
1171
|
|
|
1064
1172
|
if (isLocal) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1173
|
+
const port = process.env.PORT || config.port || 5456;
|
|
1174
|
+
|
|
1175
|
+
log(`\n${BOLD}${CYAN}Gencow DB Seed${RESET} ${DIM}(local)${RESET}\n`);
|
|
1176
|
+
|
|
1177
|
+
try {
|
|
1178
|
+
const statusRes = await fetch(`http://localhost:${port}/_admin/status`, { signal: AbortSignal.timeout(2000) });
|
|
1179
|
+
if (!statusRes.ok) throw new Error("Server not ready");
|
|
1180
|
+
} catch {
|
|
1181
|
+
error(`Server not running on :${port}`);
|
|
1182
|
+
info(`${DIM}Start the local server first: ${GREEN}gencow dev:local${RESET}`);
|
|
1183
|
+
log("");
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
try {
|
|
1188
|
+
const res = await fetch(`http://localhost:${port}/_admin/seed`, {
|
|
1189
|
+
method: "POST",
|
|
1190
|
+
headers: { "Content-Type": "application/json" },
|
|
1191
|
+
});
|
|
1192
|
+
const data = await res.json();
|
|
1193
|
+
|
|
1194
|
+
if (res.status === 404) {
|
|
1195
|
+
warn("gencow/seed.ts not found");
|
|
1196
|
+
info(`\n ${DIM}Create gencow/seed.ts:${RESET}\n`);
|
|
1197
|
+
log(` ${DIM}import { tasks } from "./schema";${RESET}`);
|
|
1198
|
+
log(` ${DIM}export default async function seed(ctx) {${RESET}`);
|
|
1199
|
+
log(` ${DIM} await ctx.db.insert(tasks).values([${RESET}`);
|
|
1200
|
+
log(` ${DIM} { title: "Hello World" },${RESET}`);
|
|
1201
|
+
log(` ${DIM} ]);${RESET}`);
|
|
1202
|
+
log(` ${DIM}};${RESET}`);
|
|
1203
|
+
} else if (!res.ok) {
|
|
1204
|
+
error(`Seed failed: ${data.error || "Unknown error"}`);
|
|
1205
|
+
} else {
|
|
1206
|
+
success("Seed completed ✓");
|
|
1207
|
+
}
|
|
1208
|
+
} catch (e) {
|
|
1209
|
+
error(`Server connection failed: ${e.message}`);
|
|
1210
|
+
}
|
|
1211
|
+
log("");
|
|
1067
1212
|
return;
|
|
1068
1213
|
}
|
|
1069
1214
|
|
|
@@ -1198,15 +1343,11 @@ ${hasPrompt ? `
|
|
|
1198
1343
|
if (devArgs.includes("--verbose")) {
|
|
1199
1344
|
process.env.GENCOW_VERBOSE = "true";
|
|
1200
1345
|
}
|
|
1201
|
-
|
|
1202
|
-
if (
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
info(`${DIM}Use cloud mode (default): gencow dev${RESET}\n`);
|
|
1206
|
-
return;
|
|
1346
|
+
const useLocal = process.env.GENCOW_LOCAL === "1" || devArgs.includes("--local");
|
|
1347
|
+
if (useLocal) {
|
|
1348
|
+
const forwarded = devArgs.filter(a => a !== "--local");
|
|
1349
|
+
return commands["dev:local"](...forwarded);
|
|
1207
1350
|
}
|
|
1208
|
-
// --cloud 하위 호환 (이미 기본이므로 그대로 진행)
|
|
1209
|
-
// 기본 = 클라우드 모드
|
|
1210
1351
|
return commands["dev:cloud"](...devArgs);
|
|
1211
1352
|
},
|
|
1212
1353
|
|
|
@@ -1422,11 +1563,20 @@ ${hasPrompt ? `
|
|
|
1422
1563
|
// Cloud-first: 기본=Cloud, --local=로컬
|
|
1423
1564
|
async "db:push"(...args) {
|
|
1424
1565
|
const config = loadConfig();
|
|
1425
|
-
const isLocal = args
|
|
1566
|
+
const isLocal = isLocalDbTarget(args);
|
|
1426
1567
|
|
|
1427
1568
|
if (isLocal) {
|
|
1428
|
-
|
|
1429
|
-
|
|
1569
|
+
let hasDb = !!process.env.DATABASE_URL;
|
|
1570
|
+
if (!hasDb) {
|
|
1571
|
+
const envPath = resolve(process.cwd(), ".env");
|
|
1572
|
+
if (existsSync(envPath)) hasDb = readFileSync(envPath, "utf8").includes("DATABASE_URL=");
|
|
1573
|
+
}
|
|
1574
|
+
const targetDb = hasDb ? "local PG" : "local fallback: PGlite";
|
|
1575
|
+
log(`\n${BOLD}${CYAN}Gencow DB Push${RESET} ${DIM}(${targetDb})${RESET}\n`);
|
|
1576
|
+
info("Pushing schema.ts → database (no migration files)...");
|
|
1577
|
+
runInServer("pnpm db:push --force", buildEnv(config));
|
|
1578
|
+
success("Schema pushed!");
|
|
1579
|
+
log(` ${DIM}Tables are in sync with schema.ts${RESET}\n`);
|
|
1430
1580
|
return;
|
|
1431
1581
|
}
|
|
1432
1582
|
|
|
@@ -3875,12 +4025,8 @@ ${BOLD}Examples:${RESET}
|
|
|
3875
4025
|
try { runtimeRoot = findServerRoot(); } catch { runtimeRoot = process.cwd(); }
|
|
3876
4026
|
const extractTsPath = resolve(runtimeRoot, ".gencow-extract.ts");
|
|
3877
4027
|
|
|
3878
|
-
// @gencow/core —
|
|
3879
|
-
|
|
3880
|
-
const bundledCorePath = resolve(runtimeRoot, "../core/index.js");
|
|
3881
|
-
const coreImport = existsSync(bundledCorePath)
|
|
3882
|
-
? bundledCorePath.replace(/\\/g, "/")
|
|
3883
|
-
: "@gencow/core";
|
|
4028
|
+
// @gencow/core — 3단계 fallback (bundled → CWD node_modules → bare specifier)
|
|
4029
|
+
const coreImport = resolveCoreImport(runtimeRoot);
|
|
3884
4030
|
|
|
3885
4031
|
const script = `
|
|
3886
4032
|
import { getRegisteredQueries, getRegisteredMutations } from "${coreImport}";
|
package/core/index.js
CHANGED
|
@@ -1979,22 +1979,209 @@ function ownerRls(userIdColumn, options) {
|
|
|
1979
1979
|
}
|
|
1980
1980
|
|
|
1981
1981
|
// ../core/src/rls-db.ts
|
|
1982
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1982
1983
|
import { sql as sql2 } from "drizzle-orm";
|
|
1983
|
-
|
|
1984
|
+
var gucNameRe = /^app\.[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$/;
|
|
1985
|
+
var RESERVED_VARS_KEYS = /* @__PURE__ */ new Set([
|
|
1986
|
+
"app.current_user_id",
|
|
1987
|
+
"app.current_user_role",
|
|
1988
|
+
"app.tenant_id"
|
|
1989
|
+
]);
|
|
1990
|
+
function assertSafeGucName(key) {
|
|
1991
|
+
if (!gucNameRe.test(key)) {
|
|
1992
|
+
throw new Error(
|
|
1993
|
+
`createRlsDb: GUC name "${key}" is invalid \u2014 use lowercase app.* names (e.g. app.org_id)`
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function rlsSetConfigPairs(rls) {
|
|
1998
|
+
const pairs = [["app.current_user_id", rls.userId]];
|
|
1999
|
+
if (rls.role !== void 0) {
|
|
2000
|
+
pairs.push(["app.current_user_role", rls.role]);
|
|
2001
|
+
}
|
|
2002
|
+
if (rls.tenantId !== void 0) {
|
|
2003
|
+
pairs.push(["app.tenant_id", rls.tenantId]);
|
|
2004
|
+
}
|
|
2005
|
+
if (rls.vars) {
|
|
2006
|
+
for (const [key, value] of Object.entries(rls.vars)) {
|
|
2007
|
+
assertSafeGucName(key);
|
|
2008
|
+
if (RESERVED_VARS_KEYS.has(key)) {
|
|
2009
|
+
throw new Error(
|
|
2010
|
+
`createRlsDb: vars must not set "${key}" \u2014 use userId, role, or tenantId on the context object`
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
pairs.push([key, value]);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
return pairs;
|
|
2017
|
+
}
|
|
2018
|
+
async function forEachSetConfig(rls, setOne) {
|
|
2019
|
+
for (const [name, value] of rlsSetConfigPairs(rls)) {
|
|
2020
|
+
await setOne(name, value);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
var rlsExecClient = new AsyncLocalStorage();
|
|
2024
|
+
function isDrizzleTransactionDb(db) {
|
|
2025
|
+
const d = db;
|
|
2026
|
+
if (typeof d?.rollback === "function") {
|
|
2027
|
+
return true;
|
|
2028
|
+
}
|
|
2029
|
+
const name = d?.constructor?.name;
|
|
2030
|
+
return typeof name === "string" && name.includes("Transaction");
|
|
2031
|
+
}
|
|
2032
|
+
async function execSetConfig(client, name, value) {
|
|
2033
|
+
if (typeof client?.unsafe === "function") {
|
|
2034
|
+
await client.unsafe(`select set_config($1::text, $2::text, true)`, [name, value]);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
if (typeof client?.query === "function") {
|
|
2038
|
+
await client.query(`select set_config($1::text, $2::text, true)`, [name, value]);
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
throw new Error(
|
|
2042
|
+
"createRlsDb: unsupported SQL driver (expected Bun SQL, node-pg client/pool, or PGlite)"
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
async function applyRlsSessionVars(client, rls) {
|
|
2046
|
+
await forEachSetConfig(rls, (name, value) => execSetConfig(client, name, value));
|
|
2047
|
+
}
|
|
2048
|
+
async function injectRlsVarsOnTx(tx, rls) {
|
|
2049
|
+
await forEachSetConfig(
|
|
2050
|
+
rls,
|
|
2051
|
+
(name, value) => tx.execute(sql2`SELECT set_config(${name}, ${value}, true)`)
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
async function withRlsLeasedConnection(leased, rls, fn) {
|
|
2055
|
+
try {
|
|
2056
|
+
await leased.query("begin");
|
|
2057
|
+
await applyRlsSessionVars(leased, rls);
|
|
2058
|
+
const result = await rlsExecClient.run(leased, () => fn(leased));
|
|
2059
|
+
await leased.query("commit");
|
|
2060
|
+
return result;
|
|
2061
|
+
} catch (e) {
|
|
2062
|
+
try {
|
|
2063
|
+
await leased.query("rollback");
|
|
2064
|
+
} catch {
|
|
2065
|
+
}
|
|
2066
|
+
throw e;
|
|
2067
|
+
} finally {
|
|
2068
|
+
leased.release();
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
async function withRlsConnection(session, rls, reuseOuterConnection, fn) {
|
|
2072
|
+
if (reuseOuterConnection) {
|
|
2073
|
+
const c2 = session.client;
|
|
2074
|
+
return rlsExecClient.run(c2, async () => {
|
|
2075
|
+
await applyRlsSessionVars(c2, rls);
|
|
2076
|
+
return fn(c2);
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
const c = session.client;
|
|
2080
|
+
const runInner = async (client) => {
|
|
2081
|
+
await applyRlsSessionVars(client, rls);
|
|
2082
|
+
return rlsExecClient.run(client, () => fn(client));
|
|
2083
|
+
};
|
|
2084
|
+
if (typeof c.begin === "function") {
|
|
2085
|
+
return c.begin(runInner);
|
|
2086
|
+
}
|
|
2087
|
+
if (typeof c.transaction === "function") {
|
|
2088
|
+
return c.transaction(runInner);
|
|
2089
|
+
}
|
|
2090
|
+
if (typeof c.connect === "function" && typeof c.query === "function") {
|
|
2091
|
+
const leased = await c.connect();
|
|
2092
|
+
return withRlsLeasedConnection(leased, rls, fn);
|
|
2093
|
+
}
|
|
2094
|
+
return runInner(c);
|
|
2095
|
+
}
|
|
2096
|
+
function wrapPreparedQuery(pq, session, rls, reuseOuterConnection) {
|
|
2097
|
+
const origExecute = pq.execute.bind(pq);
|
|
2098
|
+
const origAll = pq.all.bind(pq);
|
|
2099
|
+
pq.execute = async (placeholderValues) => {
|
|
2100
|
+
const active = rlsExecClient.getStore();
|
|
2101
|
+
if (active) {
|
|
2102
|
+
const prev = pq.client;
|
|
2103
|
+
pq.client = active;
|
|
2104
|
+
try {
|
|
2105
|
+
return await origExecute(placeholderValues);
|
|
2106
|
+
} finally {
|
|
2107
|
+
pq.client = prev;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
2111
|
+
const prev = pq.client;
|
|
2112
|
+
pq.client = client;
|
|
2113
|
+
try {
|
|
2114
|
+
return await origExecute(placeholderValues);
|
|
2115
|
+
} finally {
|
|
2116
|
+
pq.client = prev;
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
};
|
|
2120
|
+
pq.all = async (placeholderValues) => {
|
|
2121
|
+
const active = rlsExecClient.getStore();
|
|
2122
|
+
if (active) {
|
|
2123
|
+
const prev = pq.client;
|
|
2124
|
+
pq.client = active;
|
|
2125
|
+
try {
|
|
2126
|
+
return await origAll(placeholderValues);
|
|
2127
|
+
} finally {
|
|
2128
|
+
pq.client = prev;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
2132
|
+
const prev = pq.client;
|
|
2133
|
+
pq.client = client;
|
|
2134
|
+
try {
|
|
2135
|
+
return await origAll(placeholderValues);
|
|
2136
|
+
} finally {
|
|
2137
|
+
pq.client = prev;
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
function wrapSession(session, rls, reuseOuterConnection) {
|
|
2143
|
+
return new Proxy(session, {
|
|
2144
|
+
get(sTarget, sProp, sRecv) {
|
|
2145
|
+
if (sProp === "prepareQuery") {
|
|
2146
|
+
return (...args) => {
|
|
2147
|
+
const pq = sTarget.prepareQuery(...args);
|
|
2148
|
+
wrapPreparedQuery(pq, sTarget, rls, reuseOuterConnection);
|
|
2149
|
+
return pq;
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
const v2 = Reflect.get(sTarget, sProp, sRecv);
|
|
2153
|
+
return typeof v2 === "function" ? v2.bind(sTarget) : v2;
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
function createRlsDb(db, rls) {
|
|
2158
|
+
const reuseOuterConnection = isDrizzleTransactionDb(db);
|
|
2159
|
+
const baseSession = db.session;
|
|
2160
|
+
const wrappedSession = wrapSession(baseSession, rls, reuseOuterConnection);
|
|
1984
2161
|
return new Proxy(db, {
|
|
1985
2162
|
get(target, prop, receiver) {
|
|
2163
|
+
if (prop === "session") {
|
|
2164
|
+
return wrappedSession;
|
|
2165
|
+
}
|
|
2166
|
+
if (prop === "_") {
|
|
2167
|
+
const inner = target._;
|
|
2168
|
+
return new Proxy(inner, {
|
|
2169
|
+
get(i, p, r) {
|
|
2170
|
+
if (p === "session") return wrappedSession;
|
|
2171
|
+
return Reflect.get(i, p, r);
|
|
2172
|
+
}
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
1986
2175
|
if (prop === "transaction") {
|
|
1987
2176
|
return async (callback, ...rest) => {
|
|
1988
2177
|
return await target.transaction(async (tx) => {
|
|
1989
|
-
await tx
|
|
1990
|
-
sql2`SELECT set_config('app.current_user_id', ${userId}, true)`
|
|
1991
|
-
);
|
|
2178
|
+
await injectRlsVarsOnTx(tx, rls);
|
|
1992
2179
|
return await callback(tx);
|
|
1993
2180
|
}, ...rest);
|
|
1994
2181
|
};
|
|
1995
2182
|
}
|
|
1996
2183
|
const value = Reflect.get(target, prop, receiver);
|
|
1997
|
-
return typeof value === "function" ? value.bind(
|
|
2184
|
+
return typeof value === "function" ? value.bind(receiver) : value;
|
|
1998
2185
|
}
|
|
1999
2186
|
});
|
|
2000
2187
|
}
|