@stratum-hq/db-adapters 0.1.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/dist/__tests__/base-adapter.test.d.ts +2 -0
- package/dist/__tests__/base-adapter.test.d.ts.map +1 -0
- package/dist/__tests__/base-adapter.test.js +194 -0
- package/dist/__tests__/base-adapter.test.js.map +1 -0
- package/dist/__tests__/database-manager.test.d.ts +2 -0
- package/dist/__tests__/database-manager.test.d.ts.map +1 -0
- package/dist/__tests__/database-manager.test.js +110 -0
- package/dist/__tests__/database-manager.test.js.map +1 -0
- package/dist/__tests__/pool-manager.test.d.ts +2 -0
- package/dist/__tests__/pool-manager.test.d.ts.map +1 -0
- package/dist/__tests__/pool-manager.test.js +174 -0
- package/dist/__tests__/pool-manager.test.js.map +1 -0
- package/dist/__tests__/rls-manager.test.d.ts +2 -0
- package/dist/__tests__/rls-manager.test.d.ts.map +1 -0
- package/dist/__tests__/rls-manager.test.js +180 -0
- package/dist/__tests__/rls-manager.test.js.map +1 -0
- package/dist/__tests__/rls-session.test.d.ts +2 -0
- package/dist/__tests__/rls-session.test.d.ts.map +1 -0
- package/dist/__tests__/rls-session.test.js +102 -0
- package/dist/__tests__/rls-session.test.js.map +1 -0
- package/dist/__tests__/schema-manager.test.d.ts +2 -0
- package/dist/__tests__/schema-manager.test.d.ts.map +1 -0
- package/dist/__tests__/schema-manager.test.js +116 -0
- package/dist/__tests__/schema-manager.test.js.map +1 -0
- package/dist/adapters/database-prisma.d.ts +50 -0
- package/dist/adapters/database-prisma.d.ts.map +1 -0
- package/dist/adapters/database-prisma.js +66 -0
- package/dist/adapters/database-prisma.js.map +1 -0
- package/dist/adapters/database-raw.d.ts +23 -0
- package/dist/adapters/database-raw.d.ts.map +1 -0
- package/dist/adapters/database-raw.js +52 -0
- package/dist/adapters/database-raw.js.map +1 -0
- package/dist/adapters/prisma.d.ts +14 -0
- package/dist/adapters/prisma.d.ts.map +1 -0
- package/dist/adapters/prisma.js +29 -0
- package/dist/adapters/prisma.js.map +1 -0
- package/dist/adapters/raw.d.ts +8 -0
- package/dist/adapters/raw.d.ts.map +1 -0
- package/dist/adapters/raw.js +26 -0
- package/dist/adapters/raw.js.map +1 -0
- package/dist/adapters/schema-prisma.d.ts +11 -0
- package/dist/adapters/schema-prisma.d.ts.map +1 -0
- package/dist/adapters/schema-prisma.js +35 -0
- package/dist/adapters/schema-prisma.js.map +1 -0
- package/dist/adapters/schema-raw.d.ts +13 -0
- package/dist/adapters/schema-raw.d.ts.map +1 -0
- package/dist/adapters/schema-raw.js +63 -0
- package/dist/adapters/schema-raw.js.map +1 -0
- package/dist/base-adapter.d.ts +7 -0
- package/dist/base-adapter.d.ts.map +1 -0
- package/dist/base-adapter.js +32 -0
- package/dist/base-adapter.js.map +1 -0
- package/dist/database/manager.d.ts +27 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/manager.js +68 -0
- package/dist/database/manager.js.map +1 -0
- package/dist/database/pool-manager.d.ts +44 -0
- package/dist/database/pool-manager.d.ts.map +1 -0
- package/dist/database/pool-manager.js +102 -0
- package/dist/database/pool-manager.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/migration-helpers.d.ts +6 -0
- package/dist/migration-helpers.d.ts.map +1 -0
- package/dist/migration-helpers.js +34 -0
- package/dist/migration-helpers.js.map +1 -0
- package/dist/rls/manager.d.ts +7 -0
- package/dist/rls/manager.d.ts.map +1 -0
- package/dist/rls/manager.js +46 -0
- package/dist/rls/manager.js.map +1 -0
- package/dist/rls/session.d.ts +5 -0
- package/dist/rls/session.d.ts.map +1 -0
- package/dist/rls/session.js +18 -0
- package/dist/rls/session.js.map +1 -0
- package/dist/schema/manager.d.ts +8 -0
- package/dist/schema/manager.d.ts.map +1 -0
- package/dist/schema/manager.js +49 -0
- package/dist/schema/manager.js.map +1 -0
- package/dist/schema/session.d.ts +5 -0
- package/dist/schema/session.d.ts.map +1 -0
- package/dist/schema/session.js +21 -0
- package/dist/schema/session.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const manager_js_1 = require("../rls/manager.js");
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mock pg.PoolClient
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
function makeMockClient() {
|
|
9
|
+
return {
|
|
10
|
+
query: vitest_1.vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Tests
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
(0, vitest_1.describe)("RLS Manager", () => {
|
|
17
|
+
let client;
|
|
18
|
+
(0, vitest_1.beforeEach)(() => {
|
|
19
|
+
client = makeMockClient();
|
|
20
|
+
});
|
|
21
|
+
// -----------------------------------------------------------------------
|
|
22
|
+
// Table name validation
|
|
23
|
+
// -----------------------------------------------------------------------
|
|
24
|
+
(0, vitest_1.describe)("table name validation", () => {
|
|
25
|
+
(0, vitest_1.it)("rejects names containing SQL injection attempts", async () => {
|
|
26
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "users; DROP TABLE")).rejects.toThrow("Invalid table name");
|
|
27
|
+
await (0, vitest_1.expect)((0, manager_js_1.dropPolicy)(client, "users; DROP TABLE")).rejects.toThrow("Invalid table name");
|
|
28
|
+
await (0, vitest_1.expect)((0, manager_js_1.enableRLS)(client, "users; DROP TABLE")).rejects.toThrow("Invalid table name");
|
|
29
|
+
await (0, vitest_1.expect)((0, manager_js_1.disableRLS)(client, "users; DROP TABLE")).rejects.toThrow("Invalid table name");
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)("rejects names with spaces", async () => {
|
|
32
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "my table")).rejects.toThrow("Invalid table name");
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)("rejects names starting with a digit", async () => {
|
|
35
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "1users")).rejects.toThrow("Invalid table name");
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.it)("rejects names with hyphens", async () => {
|
|
38
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "my-table")).rejects.toThrow("Invalid table name");
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)("rejects names with parentheses", async () => {
|
|
41
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "users()")).rejects.toThrow("Invalid table name");
|
|
42
|
+
});
|
|
43
|
+
(0, vitest_1.it)("rejects empty string", async () => {
|
|
44
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "")).rejects.toThrow("Invalid table name");
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)("accepts simple alphanumeric names", async () => {
|
|
47
|
+
client.query.mockResolvedValueOnce({
|
|
48
|
+
rows: [{ count: "0" }],
|
|
49
|
+
});
|
|
50
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "users")).resolves.toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)("accepts names with underscores", async () => {
|
|
53
|
+
client.query.mockResolvedValueOnce({
|
|
54
|
+
rows: [{ count: "0" }],
|
|
55
|
+
});
|
|
56
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "tenant_data")).resolves.toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.it)("accepts names starting with underscore", async () => {
|
|
59
|
+
client.query.mockResolvedValueOnce({
|
|
60
|
+
rows: [{ count: "0" }],
|
|
61
|
+
});
|
|
62
|
+
await (0, vitest_1.expect)((0, manager_js_1.createPolicy)(client, "_internal")).resolves.toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
// -----------------------------------------------------------------------
|
|
66
|
+
// createPolicy
|
|
67
|
+
// -----------------------------------------------------------------------
|
|
68
|
+
(0, vitest_1.describe)("createPolicy", () => {
|
|
69
|
+
(0, vitest_1.it)("checks for existing policy before creating", async () => {
|
|
70
|
+
client.query.mockResolvedValueOnce({
|
|
71
|
+
rows: [{ count: "0" }],
|
|
72
|
+
});
|
|
73
|
+
await (0, manager_js_1.createPolicy)(client, "orders");
|
|
74
|
+
const calls = client.query.mock.calls;
|
|
75
|
+
(0, vitest_1.expect)(calls).toHaveLength(2);
|
|
76
|
+
// First call: check pg_policies
|
|
77
|
+
(0, vitest_1.expect)(calls[0][0]).toContain("pg_policies");
|
|
78
|
+
(0, vitest_1.expect)(calls[0][0]).toContain("tenant_isolation");
|
|
79
|
+
(0, vitest_1.expect)(calls[0][1]).toEqual(["orders"]);
|
|
80
|
+
// Second call: CREATE POLICY
|
|
81
|
+
(0, vitest_1.expect)(calls[1][0]).toContain("CREATE POLICY tenant_isolation ON orders");
|
|
82
|
+
(0, vitest_1.expect)(calls[1][0]).toContain("current_setting('app.current_tenant_id')");
|
|
83
|
+
});
|
|
84
|
+
(0, vitest_1.it)("skips creation when policy already exists", async () => {
|
|
85
|
+
client.query.mockResolvedValueOnce({
|
|
86
|
+
rows: [{ count: "1" }],
|
|
87
|
+
});
|
|
88
|
+
await (0, manager_js_1.createPolicy)(client, "orders");
|
|
89
|
+
const calls = client.query.mock.calls;
|
|
90
|
+
// Only the SELECT check, no CREATE POLICY
|
|
91
|
+
(0, vitest_1.expect)(calls).toHaveLength(1);
|
|
92
|
+
(0, vitest_1.expect)(calls[0][0]).toContain("pg_policies");
|
|
93
|
+
});
|
|
94
|
+
(0, vitest_1.it)("uses the validated table name in the SQL", async () => {
|
|
95
|
+
client.query.mockResolvedValueOnce({
|
|
96
|
+
rows: [{ count: "0" }],
|
|
97
|
+
});
|
|
98
|
+
await (0, manager_js_1.createPolicy)(client, "user_accounts");
|
|
99
|
+
const createCall = client.query.mock
|
|
100
|
+
.calls[1][0];
|
|
101
|
+
(0, vitest_1.expect)(createCall).toContain("ON user_accounts USING");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
// -----------------------------------------------------------------------
|
|
105
|
+
// dropPolicy
|
|
106
|
+
// -----------------------------------------------------------------------
|
|
107
|
+
(0, vitest_1.describe)("dropPolicy", () => {
|
|
108
|
+
(0, vitest_1.it)("generates correct DROP POLICY SQL", async () => {
|
|
109
|
+
await (0, manager_js_1.dropPolicy)(client, "orders");
|
|
110
|
+
const call = client.query.mock.calls[0];
|
|
111
|
+
(0, vitest_1.expect)(call[0]).toBe("DROP POLICY IF EXISTS tenant_isolation ON orders");
|
|
112
|
+
});
|
|
113
|
+
(0, vitest_1.it)("uses the validated table name", async () => {
|
|
114
|
+
await (0, manager_js_1.dropPolicy)(client, "tenant_invoices");
|
|
115
|
+
const sql = client.query.mock.calls[0][0];
|
|
116
|
+
(0, vitest_1.expect)(sql).toContain("ON tenant_invoices");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
// -----------------------------------------------------------------------
|
|
120
|
+
// enableRLS
|
|
121
|
+
// -----------------------------------------------------------------------
|
|
122
|
+
(0, vitest_1.describe)("enableRLS", () => {
|
|
123
|
+
(0, vitest_1.it)("generates correct ALTER TABLE statements", async () => {
|
|
124
|
+
await (0, manager_js_1.enableRLS)(client, "products");
|
|
125
|
+
const calls = client.query.mock.calls;
|
|
126
|
+
(0, vitest_1.expect)(calls).toHaveLength(2);
|
|
127
|
+
(0, vitest_1.expect)(calls[0][0]).toBe("ALTER TABLE products ENABLE ROW LEVEL SECURITY");
|
|
128
|
+
(0, vitest_1.expect)(calls[1][0]).toBe("ALTER TABLE products FORCE ROW LEVEL SECURITY");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// -----------------------------------------------------------------------
|
|
132
|
+
// disableRLS
|
|
133
|
+
// -----------------------------------------------------------------------
|
|
134
|
+
(0, vitest_1.describe)("disableRLS", () => {
|
|
135
|
+
(0, vitest_1.it)("generates correct ALTER TABLE statements for disabling", async () => {
|
|
136
|
+
await (0, manager_js_1.disableRLS)(client, "products");
|
|
137
|
+
const calls = client.query.mock.calls;
|
|
138
|
+
(0, vitest_1.expect)(calls).toHaveLength(2);
|
|
139
|
+
(0, vitest_1.expect)(calls[0][0]).toBe("ALTER TABLE products NO FORCE ROW LEVEL SECURITY");
|
|
140
|
+
(0, vitest_1.expect)(calls[1][0]).toBe("ALTER TABLE products DISABLE ROW LEVEL SECURITY");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
// -----------------------------------------------------------------------
|
|
144
|
+
// isRLSEnabled
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
(0, vitest_1.describe)("isRLSEnabled", () => {
|
|
147
|
+
(0, vitest_1.it)("queries pg_class with the table name as a parameter", async () => {
|
|
148
|
+
client.query.mockResolvedValueOnce({
|
|
149
|
+
rows: [{ relrowsecurity: true }],
|
|
150
|
+
});
|
|
151
|
+
await (0, manager_js_1.isRLSEnabled)(client, "orders");
|
|
152
|
+
const call = client.query.mock.calls[0];
|
|
153
|
+
(0, vitest_1.expect)(call[0]).toContain("pg_class");
|
|
154
|
+
(0, vitest_1.expect)(call[0]).toContain("relrowsecurity");
|
|
155
|
+
(0, vitest_1.expect)(call[1]).toEqual(["orders"]);
|
|
156
|
+
});
|
|
157
|
+
(0, vitest_1.it)("returns true when RLS is enabled", async () => {
|
|
158
|
+
client.query.mockResolvedValueOnce({
|
|
159
|
+
rows: [{ relrowsecurity: true }],
|
|
160
|
+
});
|
|
161
|
+
const result = await (0, manager_js_1.isRLSEnabled)(client, "orders");
|
|
162
|
+
(0, vitest_1.expect)(result).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
(0, vitest_1.it)("returns false when RLS is disabled", async () => {
|
|
165
|
+
client.query.mockResolvedValueOnce({
|
|
166
|
+
rows: [{ relrowsecurity: false }],
|
|
167
|
+
});
|
|
168
|
+
const result = await (0, manager_js_1.isRLSEnabled)(client, "orders");
|
|
169
|
+
(0, vitest_1.expect)(result).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
(0, vitest_1.it)("returns false when table does not exist (no rows)", async () => {
|
|
172
|
+
client.query.mockResolvedValueOnce({
|
|
173
|
+
rows: [],
|
|
174
|
+
});
|
|
175
|
+
const result = await (0, manager_js_1.isRLSEnabled)(client, "nonexistent");
|
|
176
|
+
(0, vitest_1.expect)(result).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
//# sourceMappingURL=rls-manager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rls-manager.test.js","sourceRoot":"","sources":["../../src/__tests__/rls-manager.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rls-session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/rls-session.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const session_js_1 = require("../rls/session.js");
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mock pg.PoolClient
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
function makeMockClient() {
|
|
9
|
+
return {
|
|
10
|
+
query: vitest_1.vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Tests
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
(0, vitest_1.describe)("RLS Session", () => {
|
|
17
|
+
let client;
|
|
18
|
+
(0, vitest_1.beforeEach)(() => {
|
|
19
|
+
client = makeMockClient();
|
|
20
|
+
});
|
|
21
|
+
// -----------------------------------------------------------------------
|
|
22
|
+
// setTenantContext
|
|
23
|
+
// -----------------------------------------------------------------------
|
|
24
|
+
(0, vitest_1.describe)("setTenantContext", () => {
|
|
25
|
+
(0, vitest_1.it)("sets the correct session variable via set_config", async () => {
|
|
26
|
+
await (0, session_js_1.setTenantContext)(client, "tenant-abc-123");
|
|
27
|
+
const call = client.query.mock.calls[0];
|
|
28
|
+
(0, vitest_1.expect)(call[0]).toBe("SELECT set_config('app.current_tenant_id', $1, true)");
|
|
29
|
+
(0, vitest_1.expect)(call[1]).toEqual(["tenant-abc-123"]);
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)("passes the tenant ID as a parameterized value", async () => {
|
|
32
|
+
await (0, session_js_1.setTenantContext)(client, "f47ac10b-58cc-4372-a567-0e02b2c3d479");
|
|
33
|
+
const params = client.query.mock
|
|
34
|
+
.calls[0][1];
|
|
35
|
+
(0, vitest_1.expect)(params).toEqual(["f47ac10b-58cc-4372-a567-0e02b2c3d479"]);
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.it)("uses transaction-local scope (third arg true)", async () => {
|
|
38
|
+
await (0, session_js_1.setTenantContext)(client, "any-tenant");
|
|
39
|
+
const sql = client.query.mock.calls[0][0];
|
|
40
|
+
// The third argument to set_config is `true`, meaning transaction-local
|
|
41
|
+
(0, vitest_1.expect)(sql).toContain("true");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
// -----------------------------------------------------------------------
|
|
45
|
+
// resetTenantContext
|
|
46
|
+
// -----------------------------------------------------------------------
|
|
47
|
+
(0, vitest_1.describe)("resetTenantContext", () => {
|
|
48
|
+
(0, vitest_1.it)("resets the session variable", async () => {
|
|
49
|
+
await (0, session_js_1.resetTenantContext)(client);
|
|
50
|
+
const call = client.query.mock.calls[0];
|
|
51
|
+
(0, vitest_1.expect)(call[0]).toBe("RESET app.current_tenant_id");
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.it)("does not pass any parameters", async () => {
|
|
54
|
+
await (0, session_js_1.resetTenantContext)(client);
|
|
55
|
+
const call = client.query.mock.calls[0];
|
|
56
|
+
// Only the SQL string, no params array
|
|
57
|
+
(0, vitest_1.expect)(call).toHaveLength(1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
// -----------------------------------------------------------------------
|
|
61
|
+
// getCurrentTenantId
|
|
62
|
+
// -----------------------------------------------------------------------
|
|
63
|
+
(0, vitest_1.describe)("getCurrentTenantId", () => {
|
|
64
|
+
(0, vitest_1.it)("reads the session variable via current_setting", async () => {
|
|
65
|
+
client.query.mockResolvedValueOnce({
|
|
66
|
+
rows: [{ current_setting: "tenant-xyz" }],
|
|
67
|
+
});
|
|
68
|
+
await (0, session_js_1.getCurrentTenantId)(client);
|
|
69
|
+
const sql = client.query.mock.calls[0][0];
|
|
70
|
+
(0, vitest_1.expect)(sql).toContain("current_setting('app.current_tenant_id'");
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)("returns the tenant ID when set", async () => {
|
|
73
|
+
client.query.mockResolvedValueOnce({
|
|
74
|
+
rows: [{ current_setting: "tenant-xyz" }],
|
|
75
|
+
});
|
|
76
|
+
const result = await (0, session_js_1.getCurrentTenantId)(client);
|
|
77
|
+
(0, vitest_1.expect)(result).toBe("tenant-xyz");
|
|
78
|
+
});
|
|
79
|
+
(0, vitest_1.it)("returns null when the setting is empty string", async () => {
|
|
80
|
+
client.query.mockResolvedValueOnce({
|
|
81
|
+
rows: [{ current_setting: "" }],
|
|
82
|
+
});
|
|
83
|
+
const result = await (0, session_js_1.getCurrentTenantId)(client);
|
|
84
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.it)("returns null when rows are empty", async () => {
|
|
87
|
+
client.query.mockResolvedValueOnce({
|
|
88
|
+
rows: [],
|
|
89
|
+
});
|
|
90
|
+
const result = await (0, session_js_1.getCurrentTenantId)(client);
|
|
91
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.it)("returns null when current_setting is null", async () => {
|
|
94
|
+
client.query.mockResolvedValueOnce({
|
|
95
|
+
rows: [{ current_setting: null }],
|
|
96
|
+
});
|
|
97
|
+
const result = await (0, session_js_1.getCurrentTenantId)(client);
|
|
98
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=rls-session.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rls-session.test.js","sourceRoot":"","sources":["../../src/__tests__/rls-session.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-manager.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schema-manager.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const manager_js_1 = require("../schema/manager.js");
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
function makeClient(queryResults = {}) {
|
|
9
|
+
const query = vitest_1.vi.fn(async (sql) => {
|
|
10
|
+
// Return the configured result or a sensible default
|
|
11
|
+
for (const [pattern, result] of Object.entries(queryResults)) {
|
|
12
|
+
if (sql.includes(pattern)) {
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return { rows: [], rowCount: 0 };
|
|
17
|
+
});
|
|
18
|
+
return { query };
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// tenantSchemaName
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
(0, vitest_1.describe)("tenantSchemaName", () => {
|
|
24
|
+
(0, vitest_1.it)("prepends tenant_ to a simple slug", () => {
|
|
25
|
+
(0, vitest_1.expect)((0, manager_js_1.tenantSchemaName)("acme")).toBe("tenant_acme");
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("handles slugs with underscores and digits", () => {
|
|
28
|
+
(0, vitest_1.expect)((0, manager_js_1.tenantSchemaName)("acme_corp_2024")).toBe("tenant_acme_corp_2024");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// createSchema
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
(0, vitest_1.describe)("createSchema", () => {
|
|
35
|
+
(0, vitest_1.it)("executes CREATE SCHEMA IF NOT EXISTS with the correct name", async () => {
|
|
36
|
+
const client = makeClient();
|
|
37
|
+
await (0, manager_js_1.createSchema)(client, "acme");
|
|
38
|
+
(0, vitest_1.expect)(client.query).toHaveBeenCalledWith("CREATE SCHEMA IF NOT EXISTS tenant_acme");
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)("rejects an invalid slug that produces an invalid schema name", async () => {
|
|
41
|
+
const client = makeClient();
|
|
42
|
+
// Injecting SQL-special characters via an invalid slug
|
|
43
|
+
await (0, vitest_1.expect)((0, manager_js_1.createSchema)(client, "bad; DROP TABLE tenants--")).rejects.toThrow("Invalid schema name");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// dropSchema
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
(0, vitest_1.describe)("dropSchema", () => {
|
|
50
|
+
(0, vitest_1.it)("executes DROP SCHEMA IF EXISTS … CASCADE", async () => {
|
|
51
|
+
const client = makeClient();
|
|
52
|
+
await (0, manager_js_1.dropSchema)(client, "acme");
|
|
53
|
+
(0, vitest_1.expect)(client.query).toHaveBeenCalledWith("DROP SCHEMA IF EXISTS tenant_acme CASCADE");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// schemaExists
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
(0, vitest_1.describe)("schemaExists", () => {
|
|
60
|
+
(0, vitest_1.it)("returns true when count > 0", async () => {
|
|
61
|
+
const client = makeClient({ pg_namespace: { rows: [{ count: "1" }], rowCount: 1 } });
|
|
62
|
+
const result = await (0, manager_js_1.schemaExists)(client, "acme");
|
|
63
|
+
(0, vitest_1.expect)(result).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.it)("returns false when count is 0", async () => {
|
|
66
|
+
const client = makeClient({ pg_namespace: { rows: [{ count: "0" }], rowCount: 1 } });
|
|
67
|
+
const result = await (0, manager_js_1.schemaExists)(client, "acme");
|
|
68
|
+
(0, vitest_1.expect)(result).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
(0, vitest_1.it)("uses parameterized query with the full schema name", async () => {
|
|
71
|
+
const client = makeClient({ pg_namespace: { rows: [{ count: "0" }], rowCount: 1 } });
|
|
72
|
+
await (0, manager_js_1.schemaExists)(client, "acme");
|
|
73
|
+
(0, vitest_1.expect)(client.query).toHaveBeenCalledWith(vitest_1.expect.stringContaining("pg_namespace"), ["tenant_acme"]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// listTenantSchemas
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
(0, vitest_1.describe)("listTenantSchemas", () => {
|
|
80
|
+
(0, vitest_1.it)("returns schema names from pg_namespace", async () => {
|
|
81
|
+
const client = makeClient({
|
|
82
|
+
pg_namespace: {
|
|
83
|
+
rows: [{ nspname: "tenant_acme" }, { nspname: "tenant_beta" }],
|
|
84
|
+
rowCount: 2,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
const schemas = await (0, manager_js_1.listTenantSchemas)(client);
|
|
88
|
+
(0, vitest_1.expect)(schemas).toEqual(["tenant_acme", "tenant_beta"]);
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)("returns empty array when no tenant schemas exist", async () => {
|
|
91
|
+
const client = makeClient({
|
|
92
|
+
pg_namespace: { rows: [], rowCount: 0 },
|
|
93
|
+
});
|
|
94
|
+
const schemas = await (0, manager_js_1.listTenantSchemas)(client);
|
|
95
|
+
(0, vitest_1.expect)(schemas).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// replicateTableToSchema
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
(0, vitest_1.describe)("replicateTableToSchema", () => {
|
|
102
|
+
(0, vitest_1.it)("executes CREATE TABLE … LIKE public.tableName", async () => {
|
|
103
|
+
const client = makeClient();
|
|
104
|
+
await (0, manager_js_1.replicateTableToSchema)(client, "orders", "tenant_acme");
|
|
105
|
+
(0, vitest_1.expect)(client.query).toHaveBeenCalledWith("CREATE TABLE IF NOT EXISTS tenant_acme.orders (LIKE public.orders INCLUDING ALL)");
|
|
106
|
+
});
|
|
107
|
+
(0, vitest_1.it)("rejects an invalid table name", async () => {
|
|
108
|
+
const client = makeClient();
|
|
109
|
+
await (0, vitest_1.expect)((0, manager_js_1.replicateTableToSchema)(client, "orders; DROP TABLE tenants--", "tenant_acme")).rejects.toThrow("Invalid table name");
|
|
110
|
+
});
|
|
111
|
+
(0, vitest_1.it)("rejects an invalid schema name", async () => {
|
|
112
|
+
const client = makeClient();
|
|
113
|
+
await (0, vitest_1.expect)((0, manager_js_1.replicateTableToSchema)(client, "orders", "bad schema name")).rejects.toThrow("Invalid schema name");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=schema-manager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-manager.test.js","sourceRoot":"","sources":["../../src/__tests__/schema-manager.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DatabasePoolManager } from "../database/pool-manager.js";
|
|
2
|
+
interface PrismaClientLike {
|
|
3
|
+
$extends: (extension: unknown) => PrismaClientLike;
|
|
4
|
+
$executeRaw: (query: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
|
|
5
|
+
$transaction: <T>(fn: (tx: PrismaClientLike) => Promise<T>) => Promise<T>;
|
|
6
|
+
$connect: () => Promise<void>;
|
|
7
|
+
$disconnect: () => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
type PrismaConstructor = new (options: {
|
|
10
|
+
datasources: {
|
|
11
|
+
db: {
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
}) => PrismaClientLike;
|
|
16
|
+
/**
|
|
17
|
+
* Prisma adapter for DB_PER_TENANT isolation.
|
|
18
|
+
*
|
|
19
|
+
* Creates a Prisma client instance scoped to the tenant's dedicated database
|
|
20
|
+
* by overriding the datasource URL with the per-tenant database name.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* const adapter = new DatabasePrismaAdapter(poolManager, PrismaClient, baseUrl);
|
|
24
|
+
* const prisma = adapter.getClient('acme_corp');
|
|
25
|
+
* const rows = await prisma.someModel.findMany();
|
|
26
|
+
*/
|
|
27
|
+
export declare class DatabasePrismaAdapter {
|
|
28
|
+
private readonly poolManager;
|
|
29
|
+
private readonly PrismaClient;
|
|
30
|
+
private readonly baseDatasourceUrl;
|
|
31
|
+
private readonly clients;
|
|
32
|
+
constructor(poolManager: DatabasePoolManager, PrismaClient: PrismaConstructor, baseDatasourceUrl: string);
|
|
33
|
+
/**
|
|
34
|
+
* Returns a Prisma client connected to the tenant's dedicated database.
|
|
35
|
+
* Clients are cached per tenant slug; call disconnect() to free resources.
|
|
36
|
+
*/
|
|
37
|
+
getClient(tenantSlug: string): PrismaClientLike;
|
|
38
|
+
/** Disconnects and removes the cached Prisma client for the given tenant. */
|
|
39
|
+
disconnectClient(tenantSlug: string): Promise<void>;
|
|
40
|
+
/** Disconnects all cached Prisma clients. Call during application shutdown. */
|
|
41
|
+
disconnectAll(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Replaces the database name in a PostgreSQL connection URL.
|
|
44
|
+
* Handles both connection string formats:
|
|
45
|
+
* postgres://user:pass@host:port/dbname[?params]
|
|
46
|
+
*/
|
|
47
|
+
private buildDatasourceUrl;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=database-prisma.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-prisma.d.ts","sourceRoot":"","sources":["../../src/adapters/database-prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAGlE,UAAU,gBAAgB;IACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,gBAAgB,CAAC;IACnD,WAAW,EAAE,CACX,KAAK,EAAE,oBAAoB,EAC3B,GAAG,MAAM,EAAE,OAAO,EAAE,KACjB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,KAAK,iBAAiB,GAAG,KAAK,OAAO,EAAE;IAAE,WAAW,EAAE;QAAE,EAAE,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,KAAK,gBAAgB,CAAC;AAErG;;;;;;;;;;GAUG;AACH,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IALpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;gBAGjD,WAAW,EAAE,mBAAmB,EAChC,YAAY,EAAE,iBAAiB,EAC/B,iBAAiB,EAAE,MAAM;IAG5C;;;OAGG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB;IAe/C,6EAA6E;IACvE,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzD,+EAA+E;IACzE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;CAI3B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatabasePrismaAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Prisma adapter for DB_PER_TENANT isolation.
|
|
6
|
+
*
|
|
7
|
+
* Creates a Prisma client instance scoped to the tenant's dedicated database
|
|
8
|
+
* by overriding the datasource URL with the per-tenant database name.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const adapter = new DatabasePrismaAdapter(poolManager, PrismaClient, baseUrl);
|
|
12
|
+
* const prisma = adapter.getClient('acme_corp');
|
|
13
|
+
* const rows = await prisma.someModel.findMany();
|
|
14
|
+
*/
|
|
15
|
+
class DatabasePrismaAdapter {
|
|
16
|
+
poolManager;
|
|
17
|
+
PrismaClient;
|
|
18
|
+
baseDatasourceUrl;
|
|
19
|
+
clients = new Map();
|
|
20
|
+
constructor(poolManager, PrismaClient, baseDatasourceUrl) {
|
|
21
|
+
this.poolManager = poolManager;
|
|
22
|
+
this.PrismaClient = PrismaClient;
|
|
23
|
+
this.baseDatasourceUrl = baseDatasourceUrl;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns a Prisma client connected to the tenant's dedicated database.
|
|
27
|
+
* Clients are cached per tenant slug; call disconnect() to free resources.
|
|
28
|
+
*/
|
|
29
|
+
getClient(tenantSlug) {
|
|
30
|
+
const existing = this.clients.get(tenantSlug);
|
|
31
|
+
if (existing)
|
|
32
|
+
return existing;
|
|
33
|
+
const dbName = `stratum_tenant_${tenantSlug}`;
|
|
34
|
+
const tenantUrl = this.buildDatasourceUrl(this.baseDatasourceUrl, dbName);
|
|
35
|
+
const client = new this.PrismaClient({
|
|
36
|
+
datasources: { db: { url: tenantUrl } },
|
|
37
|
+
});
|
|
38
|
+
this.clients.set(tenantSlug, client);
|
|
39
|
+
return client;
|
|
40
|
+
}
|
|
41
|
+
/** Disconnects and removes the cached Prisma client for the given tenant. */
|
|
42
|
+
async disconnectClient(tenantSlug) {
|
|
43
|
+
const client = this.clients.get(tenantSlug);
|
|
44
|
+
if (!client)
|
|
45
|
+
return;
|
|
46
|
+
this.clients.delete(tenantSlug);
|
|
47
|
+
await client.$disconnect();
|
|
48
|
+
}
|
|
49
|
+
/** Disconnects all cached Prisma clients. Call during application shutdown. */
|
|
50
|
+
async disconnectAll() {
|
|
51
|
+
const entries = Array.from(this.clients.entries());
|
|
52
|
+
this.clients.clear();
|
|
53
|
+
await Promise.all(entries.map(([, client]) => client.$disconnect()));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Replaces the database name in a PostgreSQL connection URL.
|
|
57
|
+
* Handles both connection string formats:
|
|
58
|
+
* postgres://user:pass@host:port/dbname[?params]
|
|
59
|
+
*/
|
|
60
|
+
buildDatasourceUrl(baseUrl, dbName) {
|
|
61
|
+
// Replace the database portion of the URL (last path segment before query string).
|
|
62
|
+
return baseUrl.replace(/\/[^/?]+(\?|$)/, `/${dbName}$1`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.DatabasePrismaAdapter = DatabasePrismaAdapter;
|
|
66
|
+
//# sourceMappingURL=database-prisma.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-prisma.js","sourceRoot":"","sources":["../../src/adapters/database-prisma.ts"],"names":[],"mappings":";;;AAgBA;;;;;;;;;;GAUG;AACH,MAAa,qBAAqB;IAIb;IACA;IACA;IALF,OAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;IAEpE,YACmB,WAAgC,EAChC,YAA+B,EAC/B,iBAAyB;QAFzB,gBAAW,GAAX,WAAW,CAAqB;QAChC,iBAAY,GAAZ,YAAY,CAAmB;QAC/B,sBAAiB,GAAjB,iBAAiB,CAAQ;IACzC,CAAC;IAEJ;;;OAGG;IACH,SAAS,CAAC,UAAkB;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,MAAM,GAAG,kBAAkB,UAAU,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QAE1E,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC;YACnC,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,gBAAgB,CAAC,UAAkB;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,aAAa;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;OAIG;IACK,kBAAkB,CAAC,OAAe,EAAE,MAAc;QACxD,mFAAmF;QACnF,OAAO,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,MAAM,IAAI,CAAC,CAAC;IAC3D,CAAC;CACF;AApDD,sDAoDC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
import { DatabasePoolManager } from "../database/pool-manager.js";
|
|
3
|
+
/**
|
|
4
|
+
* Raw SQL adapter for DB_PER_TENANT isolation.
|
|
5
|
+
*
|
|
6
|
+
* Each query is routed to the dedicated database for the given tenant slug
|
|
7
|
+
* by obtaining a connection from the tenant's pool via DatabasePoolManager.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DatabaseRawAdapter {
|
|
10
|
+
private readonly poolManager;
|
|
11
|
+
constructor(poolManager: DatabasePoolManager);
|
|
12
|
+
/**
|
|
13
|
+
* Executes a single query against the tenant's dedicated database.
|
|
14
|
+
* The connection is acquired and released automatically.
|
|
15
|
+
*/
|
|
16
|
+
query<T extends pg.QueryResultRow = pg.QueryResultRow>(tenantSlug: string, text: string, values?: unknown[]): Promise<pg.QueryResult<T>>;
|
|
17
|
+
/**
|
|
18
|
+
* Executes a callback inside an explicit transaction against the tenant's dedicated database.
|
|
19
|
+
* The transaction is automatically committed on success and rolled back on error.
|
|
20
|
+
*/
|
|
21
|
+
executeWithTenantContext<T>(tenantSlug: string, queryFn: (client: pg.PoolClient) => Promise<T>): Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=database-raw.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-raw.d.ts","sourceRoot":"","sources":["../../src/adapters/database-raw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;GAKG;AACH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,mBAAmB;IAE7D;;;OAGG;IACG,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAU7B;;;OAGG;IACG,wBAAwB,CAAC,CAAC,EAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC;CAed"}
|