@gencow/core 0.1.24 → 0.1.26
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/dist/crud.d.ts +2 -2
- package/dist/crud.js +225 -208
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/reactive.js +10 -3
- package/dist/retry.js +1 -1
- package/dist/rls-db.d.ts +2 -2
- package/dist/rls-db.js +1 -5
- package/dist/scheduler.d.ts +2 -0
- package/dist/scheduler.js +16 -6
- package/dist/server.d.ts +0 -1
- package/dist/server.js +0 -1
- package/dist/storage.js +29 -22
- package/dist/v.d.ts +2 -2
- package/dist/workflow.js +4 -11
- package/dist/workflows-api.js +5 -12
- package/package.json +45 -42
- package/src/__tests__/auth.test.ts +90 -86
- package/src/__tests__/crons.test.ts +69 -67
- package/src/__tests__/crud-codegen-integration.test.ts +164 -170
- package/src/__tests__/crud-owner-rls.test.ts +308 -301
- package/src/__tests__/crud.test.ts +694 -711
- package/src/__tests__/dist-exports.test.ts +120 -120
- package/src/__tests__/fixtures/basic/auth.ts +16 -16
- package/src/__tests__/fixtures/basic/drizzle.config.ts +1 -4
- package/src/__tests__/fixtures/basic/index.ts +1 -1
- package/src/__tests__/fixtures/basic/schema.ts +1 -1
- package/src/__tests__/fixtures/basic/tasks.ts +4 -4
- package/src/__tests__/fixtures/common/auth-schema.ts +38 -34
- package/src/__tests__/helpers/basic-rls-fixture.ts +80 -78
- package/src/__tests__/helpers/pglite-migrations.ts +2 -5
- package/src/__tests__/helpers/pglite-rls-session.ts +13 -16
- package/src/__tests__/helpers/seed-like-fill.ts +47 -41
- package/src/__tests__/helpers/test-gencow-ctx-rls.ts +4 -7
- package/src/__tests__/httpaction.test.ts +91 -91
- package/src/__tests__/image-optimization.test.ts +570 -574
- package/src/__tests__/load.test.ts +321 -308
- package/src/__tests__/network-sim.test.ts +238 -215
- package/src/__tests__/reactive.test.ts +380 -358
- package/src/__tests__/retry.test.ts +99 -84
- package/src/__tests__/rls-crud-basic.test.ts +172 -245
- package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +81 -81
- package/src/__tests__/rls-custom-mutation-handlers.test.ts +47 -94
- package/src/__tests__/rls-custom-query-handlers.test.ts +92 -92
- package/src/__tests__/rls-db-leased-connection.test.ts +2 -6
- package/src/__tests__/rls-session-and-policies.test.ts +181 -199
- package/src/__tests__/scheduler-durable-v2.test.ts +199 -181
- package/src/__tests__/scheduler-durable.test.ts +117 -117
- package/src/__tests__/scheduler-exec.test.ts +258 -246
- package/src/__tests__/scheduler.test.ts +129 -111
- package/src/__tests__/storage.test.ts +282 -269
- package/src/__tests__/tsconfig.json +6 -6
- package/src/__tests__/validator.test.ts +236 -232
- package/src/__tests__/workflow.test.ts +309 -286
- package/src/__tests__/ws-integration.test.ts +223 -218
- package/src/__tests__/ws-scale.test.ts +168 -159
- package/src/auth-config.ts +18 -18
- package/src/auth.ts +106 -106
- package/src/crons.ts +77 -77
- package/src/crud.ts +523 -479
- package/src/index.ts +69 -5
- package/src/reactive.ts +357 -331
- package/src/retry.ts +51 -54
- package/src/rls-db.ts +195 -205
- package/src/rls.ts +33 -36
- package/src/scheduler.ts +237 -211
- package/src/server.ts +0 -1
- package/src/storage.ts +632 -593
- package/src/v.ts +119 -114
- package/src/workflow-types.ts +67 -70
- package/src/workflow.ts +99 -116
- package/src/workflows-api.ts +231 -241
- package/dist/db.d.ts +0 -13
- package/dist/db.js +0 -16
- package/src/db.ts +0 -18
package/src/retry.ts
CHANGED
|
@@ -29,18 +29,18 @@
|
|
|
29
29
|
// ─── 타입 ───────────────────────────────────────────────
|
|
30
30
|
|
|
31
31
|
export interface RetryOptions {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
/** 최대 시도 횟수 (기본 3) */
|
|
33
|
+
maxAttempts?: number;
|
|
34
|
+
/** 첫 번째 재시도 대기 시간 (ms, 기본 1000) */
|
|
35
|
+
initialBackoffMs?: number;
|
|
36
|
+
/** backoff 배수 (기본 2 → 1s, 2s, 4s, 8s, ...) */
|
|
37
|
+
base?: number;
|
|
38
|
+
/** 최대 대기 시간 (ms, 기본 30000) */
|
|
39
|
+
maxBackoffMs?: number;
|
|
40
|
+
/** 재시도할 에러인지 판단 (기본: 모든 에러 재시도) */
|
|
41
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
42
|
+
/** 재시도 시 호출되는 콜백 (로깅용) */
|
|
43
|
+
onRetry?: (error: unknown, attempt: number, delayMs: number) => void;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// ─── 기본값 ─────────────────────────────────────────────
|
|
@@ -60,53 +60,50 @@ const JITTER_MAX = 1.25;
|
|
|
60
60
|
* Exponential backoff + jitter로 재시도.
|
|
61
61
|
* delay = min(initialBackoffMs × base^attempt × jitter, maxBackoffMs)
|
|
62
62
|
*/
|
|
63
|
-
export async function withRetry<T>(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
|
|
64
|
+
const {
|
|
65
|
+
maxAttempts = DEFAULT_MAX_ATTEMPTS,
|
|
66
|
+
initialBackoffMs = DEFAULT_INITIAL_BACKOFF_MS,
|
|
67
|
+
base = DEFAULT_BASE,
|
|
68
|
+
maxBackoffMs = DEFAULT_MAX_BACKOFF_MS,
|
|
69
|
+
shouldRetry,
|
|
70
|
+
onRetry,
|
|
71
|
+
} = options;
|
|
72
|
+
|
|
73
|
+
if (maxAttempts < 1) {
|
|
74
|
+
throw new Error(`withRetry: maxAttempts must be >= 1, got ${maxAttempts}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
78
|
+
try {
|
|
79
|
+
return await fn();
|
|
80
|
+
} catch (error: unknown) {
|
|
81
|
+
const isLastAttempt = attempt === maxAttempts - 1;
|
|
82
|
+
|
|
83
|
+
// 마지막 시도면 에러 throw
|
|
84
|
+
if (isLastAttempt) throw error;
|
|
85
|
+
|
|
86
|
+
// shouldRetry가 있으면 판단
|
|
87
|
+
if (shouldRetry && !shouldRetry(error, attempt)) throw error;
|
|
88
|
+
|
|
89
|
+
// 딜레이 계산: exponential backoff + jitter
|
|
90
|
+
const jitter = JITTER_MIN + Math.random() * (JITTER_MAX - JITTER_MIN);
|
|
91
|
+
const rawDelay = initialBackoffMs * Math.pow(base, attempt) * jitter;
|
|
92
|
+
const delay = Math.min(rawDelay, maxBackoffMs);
|
|
93
|
+
|
|
94
|
+
// 콜백 호출
|
|
95
|
+
if (onRetry) onRetry(error, attempt + 1, delay);
|
|
96
|
+
|
|
97
|
+
await sleep(delay);
|
|
78
98
|
}
|
|
99
|
+
}
|
|
79
100
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return await fn();
|
|
83
|
-
} catch (error: unknown) {
|
|
84
|
-
const isLastAttempt = attempt === maxAttempts - 1;
|
|
85
|
-
|
|
86
|
-
// 마지막 시도면 에러 throw
|
|
87
|
-
if (isLastAttempt) throw error;
|
|
88
|
-
|
|
89
|
-
// shouldRetry가 있으면 판단
|
|
90
|
-
if (shouldRetry && !shouldRetry(error, attempt)) throw error;
|
|
91
|
-
|
|
92
|
-
// 딜레이 계산: exponential backoff + jitter
|
|
93
|
-
const jitter = JITTER_MIN + Math.random() * (JITTER_MAX - JITTER_MIN);
|
|
94
|
-
const rawDelay = initialBackoffMs * Math.pow(base, attempt) * jitter;
|
|
95
|
-
const delay = Math.min(rawDelay, maxBackoffMs);
|
|
96
|
-
|
|
97
|
-
// 콜백 호출
|
|
98
|
-
if (onRetry) onRetry(error, attempt + 1, delay);
|
|
99
|
-
|
|
100
|
-
await sleep(delay);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// TypeScript를 위한 unreachable — 실제로 여기에 도달하지 않음
|
|
105
|
-
throw new Error("withRetry: unreachable");
|
|
101
|
+
// TypeScript를 위한 unreachable — 실제로 여기에 도달하지 않음
|
|
102
|
+
throw new Error("withRetry: unreachable");
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
// ─── 내부 유틸리티 ──────────────────────────────────────
|
|
109
106
|
|
|
110
107
|
function sleep(ms: number): Promise<void> {
|
|
111
|
-
|
|
108
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
112
109
|
}
|
package/src/rls-db.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { sql } from "drizzle-orm";
|
|
3
|
-
import type {
|
|
3
|
+
import type { PgAsyncDatabase } from "drizzle-orm/pg-core";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* RLS DB wrapper — execution paths for `withRlsConnection`:
|
|
@@ -15,66 +15,62 @@ import type { PgDatabase } from "drizzle-orm/pg-core";
|
|
|
15
15
|
* Optional fields become `app.*` settings; add arbitrary allowed keys via `vars`.
|
|
16
16
|
*/
|
|
17
17
|
export type RlsSessionContext = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
/** `app.current_user_id` — required by `ownerRls()` policies in this repo. */
|
|
19
|
+
userId: string;
|
|
20
|
+
/** When set: `app.current_user_role`. */
|
|
21
|
+
role?: string;
|
|
22
|
+
/** When set: `app.tenant_id`. */
|
|
23
|
+
tenantId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Extra transaction-local settings: keys must be validated `app.*` GUC names
|
|
26
|
+
* and must not duplicate `app.current_user_id` / `app.current_user_role` / `app.tenant_id`
|
|
27
|
+
* (use the top-level fields for those).
|
|
28
|
+
*/
|
|
29
|
+
vars?: Record<string, string>;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const gucNameRe = /^app\.[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$/;
|
|
33
33
|
|
|
34
|
-
const RESERVED_VARS_KEYS = new Set([
|
|
35
|
-
"app.current_user_id",
|
|
36
|
-
"app.current_user_role",
|
|
37
|
-
"app.tenant_id",
|
|
38
|
-
]);
|
|
34
|
+
const RESERVED_VARS_KEYS = new Set(["app.current_user_id", "app.current_user_role", "app.tenant_id"]);
|
|
39
35
|
|
|
40
36
|
function assertSafeGucName(key: string): void {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
if (!gucNameRe.test(key)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`createRlsDb: GUC name "${key}" is invalid — use lowercase app.* names (e.g. app.org_id)`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
/** Ordered `(name, value)` pairs for `set_config(name, value, true)`. */
|
|
49
45
|
function rlsSetConfigPairs(rls: RlsSessionContext): [string, string][] {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
46
|
+
const pairs: [string, string][] = [["app.current_user_id", rls.userId]];
|
|
47
|
+
if (rls.role !== undefined) {
|
|
48
|
+
pairs.push(["app.current_user_role", rls.role]);
|
|
49
|
+
}
|
|
50
|
+
if (rls.tenantId !== undefined) {
|
|
51
|
+
pairs.push(["app.tenant_id", rls.tenantId]);
|
|
52
|
+
}
|
|
53
|
+
if (rls.vars) {
|
|
54
|
+
for (const [key, value] of Object.entries(rls.vars)) {
|
|
55
|
+
assertSafeGucName(key);
|
|
56
|
+
if (RESERVED_VARS_KEYS.has(key)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`createRlsDb: vars must not set "${key}" — use userId, role, or tenantId on the context object`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
pairs.push([key, value]);
|
|
67
62
|
}
|
|
68
|
-
|
|
63
|
+
}
|
|
64
|
+
return pairs;
|
|
69
65
|
}
|
|
70
66
|
|
|
71
67
|
async function forEachSetConfig(
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
rls: RlsSessionContext,
|
|
69
|
+
setOne: (name: string, value: string) => Promise<void>,
|
|
74
70
|
): Promise<void> {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
for (const [name, value] of rlsSetConfigPairs(rls)) {
|
|
72
|
+
await setOne(name, value);
|
|
73
|
+
}
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
/**
|
|
@@ -83,36 +79,32 @@ async function forEachSetConfig(
|
|
|
83
79
|
const rlsExecClient = new AsyncLocalStorage<unknown>();
|
|
84
80
|
|
|
85
81
|
function isDrizzleTransactionDb(db: unknown): boolean {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
const d = db as { constructor?: { name?: string }; rollback?: unknown };
|
|
83
|
+
if (typeof d?.rollback === "function") {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
const name = d?.constructor?.name;
|
|
87
|
+
return typeof name === "string" && name.includes("Transaction");
|
|
92
88
|
}
|
|
93
89
|
|
|
94
90
|
async function execSetConfig(client: any, name: string, value: string): Promise<void> {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"createRlsDb: unsupported SQL driver (expected Bun SQL, node-pg client/pool, or PGlite)",
|
|
105
|
-
);
|
|
91
|
+
if (typeof client?.unsafe === "function") {
|
|
92
|
+
await client.unsafe(`select set_config($1::text, $2::text, true)`, [name, value]);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (typeof client?.query === "function") {
|
|
96
|
+
await client.query(`select set_config($1::text, $2::text, true)`, [name, value]);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
throw new Error("createRlsDb: unsupported SQL driver (expected Bun SQL, node-pg client/pool, or PGlite)");
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
async function applyRlsSessionVars(client: any, rls: RlsSessionContext): Promise<void> {
|
|
109
|
-
|
|
103
|
+
await forEachSetConfig(rls, (name, value) => execSetConfig(client, name, value));
|
|
110
104
|
}
|
|
111
105
|
|
|
112
106
|
async function injectRlsVarsOnTx(tx: any, rls: RlsSessionContext): Promise<void> {
|
|
113
|
-
|
|
114
|
-
tx.execute(sql`SELECT set_config(${name}, ${value}, true)`),
|
|
115
|
-
);
|
|
107
|
+
await forEachSetConfig(rls, (name, value) => tx.execute(sql`SELECT set_config(${name}, ${value}, true)`));
|
|
116
108
|
}
|
|
117
109
|
|
|
118
110
|
/**
|
|
@@ -122,26 +114,26 @@ async function injectRlsVarsOnTx(tx: any, rls: RlsSessionContext): Promise<void>
|
|
|
122
114
|
* @internal Exported only for unit tests — not re-exported from `@gencow/core` public API.
|
|
123
115
|
*/
|
|
124
116
|
export async function withRlsLeasedConnection<T>(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
leased: { query: (sql: string) => Promise<unknown>; release: () => void },
|
|
118
|
+
rls: RlsSessionContext,
|
|
119
|
+
fn: (client: { query: (sql: string) => Promise<unknown>; release: () => void }) => Promise<T>,
|
|
128
120
|
): Promise<T> {
|
|
121
|
+
try {
|
|
122
|
+
await leased.query("begin");
|
|
123
|
+
await applyRlsSessionVars(leased, rls);
|
|
124
|
+
const result = await rlsExecClient.run(leased, () => fn(leased));
|
|
125
|
+
await leased.query("commit");
|
|
126
|
+
return result;
|
|
127
|
+
} catch (e) {
|
|
129
128
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
await leased.query("commit");
|
|
134
|
-
return result;
|
|
135
|
-
} catch (e) {
|
|
136
|
-
try {
|
|
137
|
-
await leased.query("rollback");
|
|
138
|
-
} catch {
|
|
139
|
-
/* ignore */
|
|
140
|
-
}
|
|
141
|
-
throw e;
|
|
142
|
-
} finally {
|
|
143
|
-
leased.release();
|
|
129
|
+
await leased.query("rollback");
|
|
130
|
+
} catch {
|
|
131
|
+
/* ignore */
|
|
144
132
|
}
|
|
133
|
+
throw e;
|
|
134
|
+
} finally {
|
|
135
|
+
leased.release();
|
|
136
|
+
}
|
|
145
137
|
}
|
|
146
138
|
|
|
147
139
|
/**
|
|
@@ -150,104 +142,99 @@ export async function withRlsLeasedConnection<T>(
|
|
|
150
142
|
* When `db` is already a Drizzle transaction object, reuses that connection (no nested top-level tx).
|
|
151
143
|
*/
|
|
152
144
|
async function withRlsConnection<T>(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
session: any,
|
|
146
|
+
rls: RlsSessionContext,
|
|
147
|
+
reuseOuterConnection: boolean,
|
|
148
|
+
fn: (client: any) => Promise<T>,
|
|
157
149
|
): Promise<T> {
|
|
158
|
-
|
|
159
|
-
const c = session.client;
|
|
160
|
-
return rlsExecClient.run(c, async () => {
|
|
161
|
-
await applyRlsSessionVars(c, rls);
|
|
162
|
-
return fn(c);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
150
|
+
if (reuseOuterConnection) {
|
|
165
151
|
const c = session.client;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return runInner
|
|
152
|
+
return rlsExecClient.run(c, async () => {
|
|
153
|
+
await applyRlsSessionVars(c, rls);
|
|
154
|
+
return fn(c);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const c = session.client;
|
|
158
|
+
const runInner = async (client: any) => {
|
|
159
|
+
await applyRlsSessionVars(client, rls);
|
|
160
|
+
return rlsExecClient.run(client, () => fn(client));
|
|
161
|
+
};
|
|
162
|
+
if (typeof c.begin === "function") {
|
|
163
|
+
return c.begin(runInner);
|
|
164
|
+
}
|
|
165
|
+
if (typeof c.transaction === "function") {
|
|
166
|
+
return c.transaction(runInner);
|
|
167
|
+
}
|
|
168
|
+
if (typeof c.connect === "function" && typeof c.query === "function") {
|
|
169
|
+
const leased = await c.connect();
|
|
170
|
+
return withRlsLeasedConnection(leased, rls, fn);
|
|
171
|
+
}
|
|
172
|
+
return runInner(c);
|
|
181
173
|
}
|
|
182
174
|
|
|
183
|
-
function wrapPreparedQuery(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
rls: RlsSessionContext,
|
|
187
|
-
reuseOuterConnection: boolean,
|
|
188
|
-
) {
|
|
189
|
-
const origExecute = pq.execute.bind(pq);
|
|
190
|
-
const origAll = pq.all.bind(pq);
|
|
175
|
+
function wrapPreparedQuery(pq: any, session: any, rls: RlsSessionContext, reuseOuterConnection: boolean) {
|
|
176
|
+
const origExecute = pq.execute.bind(pq);
|
|
177
|
+
const origAll = pq.all.bind(pq);
|
|
191
178
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
179
|
+
pq.execute = async (placeholderValues?: unknown) => {
|
|
180
|
+
const active = rlsExecClient.getStore();
|
|
181
|
+
if (active) {
|
|
182
|
+
const prev = pq.client;
|
|
183
|
+
pq.client = active;
|
|
184
|
+
try {
|
|
185
|
+
return await origExecute(placeholderValues);
|
|
186
|
+
} finally {
|
|
187
|
+
pq.client = prev;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
191
|
+
const prev = pq.client;
|
|
192
|
+
pq.client = client;
|
|
193
|
+
try {
|
|
194
|
+
return await origExecute(placeholderValues);
|
|
195
|
+
} finally {
|
|
196
|
+
pq.client = prev;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
};
|
|
213
200
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
201
|
+
pq.all = async (placeholderValues?: unknown) => {
|
|
202
|
+
const active = rlsExecClient.getStore();
|
|
203
|
+
if (active) {
|
|
204
|
+
const prev = pq.client;
|
|
205
|
+
pq.client = active;
|
|
206
|
+
try {
|
|
207
|
+
return await origAll(placeholderValues);
|
|
208
|
+
} finally {
|
|
209
|
+
pq.client = prev;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
213
|
+
const prev = pq.client;
|
|
214
|
+
pq.client = client;
|
|
215
|
+
try {
|
|
216
|
+
return await origAll(placeholderValues);
|
|
217
|
+
} finally {
|
|
218
|
+
pq.client = prev;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
235
222
|
}
|
|
236
223
|
|
|
237
224
|
function wrapSession(session: any, rls: RlsSessionContext, reuseOuterConnection: boolean) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
225
|
+
return new Proxy(session, {
|
|
226
|
+
get(sTarget, sProp, sRecv) {
|
|
227
|
+
if (sProp === "prepareQuery") {
|
|
228
|
+
return (...args: unknown[]) => {
|
|
229
|
+
const pq = sTarget.prepareQuery(...args);
|
|
230
|
+
wrapPreparedQuery(pq, sTarget, rls, reuseOuterConnection);
|
|
231
|
+
return pq;
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const v = Reflect.get(sTarget, sProp, sRecv);
|
|
235
|
+
return typeof v === "function" ? v.bind(sTarget) : v;
|
|
236
|
+
},
|
|
237
|
+
});
|
|
251
238
|
}
|
|
252
239
|
|
|
253
240
|
/**
|
|
@@ -258,37 +245,40 @@ function wrapSession(session: any, rls: RlsSessionContext, reuseOuterConnection:
|
|
|
258
245
|
* `db.transaction()` still injects the same variables at the start of the callback transaction.
|
|
259
246
|
*/
|
|
260
247
|
export function createRlsDb(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
):
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
248
|
+
db: PgAsyncDatabase<any, any, any, any>,
|
|
249
|
+
rls: RlsSessionContext,
|
|
250
|
+
): PgAsyncDatabase<any, any, any, any> {
|
|
251
|
+
const reuseOuterConnection = isDrizzleTransactionDb(db);
|
|
252
|
+
const baseSession = (db as unknown as { session: any }).session;
|
|
253
|
+
const wrappedSession = wrapSession(baseSession, rls, reuseOuterConnection);
|
|
267
254
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
255
|
+
return new Proxy(db, {
|
|
256
|
+
get(target, prop, receiver) {
|
|
257
|
+
if (prop === "session") {
|
|
258
|
+
return wrappedSession;
|
|
259
|
+
}
|
|
260
|
+
if (prop === "_") {
|
|
261
|
+
const inner = target._;
|
|
262
|
+
return new Proxy(inner, {
|
|
263
|
+
get(i, p, r) {
|
|
264
|
+
if (p === "session") return wrappedSession;
|
|
265
|
+
return Reflect.get(i, p, r);
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (prop === "transaction") {
|
|
270
|
+
return async (callback: any, ...rest: any[]) => {
|
|
271
|
+
return await target.transaction(
|
|
272
|
+
async (tx: any) => {
|
|
273
|
+
await injectRlsVarsOnTx(tx, rls);
|
|
274
|
+
return await callback(tx);
|
|
275
|
+
},
|
|
276
|
+
...(rest as any),
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const value = Reflect.get(target, prop, receiver);
|
|
281
|
+
return typeof value === "function" ? value.bind(receiver) : value;
|
|
282
|
+
},
|
|
283
|
+
});
|
|
294
284
|
}
|