@lark-apaas/express-datapaas 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Lark Technologies Pte. Ltd. and/or its affiliates
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
8
+ IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
9
+ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
10
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
11
+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
12
+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @lark-apaas/express-datapaas
2
+
3
+ 基于 Drizzle ORM 封装的 PostgreSQL 数据库模块,为 Express 应用提供连接管理与 RLS(行级安全)支持。
4
+
5
+ ## 功能特性
6
+
7
+ - 基于 Drizzle ORM + postgres-js 的数据库连接管理
8
+ - 自动在 pg session 层面注入用户上下文,无感支持 RLS
9
+ - 简洁的函数式 API,`createDatabase` 返回可直接使用的 `db` 实例
10
+ - Express 中间件形式的 RLS 上下文注入
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ npm install @lark-apaas/express-datapaas
16
+ ```
17
+
18
+ ### Peer Dependencies
19
+
20
+ 需要同时安装以下依赖:
21
+
22
+ ```bash
23
+ npm install drizzle-orm@0.44.6 express
24
+ ```
25
+
26
+ > **注意**: drizzle-orm 版本必须为 `0.44.6`,安装后会自动通过 postinstall 脚本打补丁以支持 RLS 功能。
27
+
28
+ ## 快速开始
29
+
30
+ ### 1. 创建数据库实例
31
+
32
+ ```typescript
33
+ import { createDatabase } from '@lark-apaas/express-datapaas';
34
+ import * as schema from './schema';
35
+
36
+ const { db, close } = createDatabase({
37
+ connectionString: process.env.DATABASE_URL!,
38
+ schema,
39
+ logger: false,
40
+ maxConnections: 1,
41
+ idleTimeout: 20,
42
+ connectionTimeout: 10,
43
+ ssl: false,
44
+ });
45
+
46
+ // 应用退出时关闭连接
47
+ process.on('SIGTERM', async () => {
48
+ await close();
49
+ });
50
+ ```
51
+
52
+ ### 2. 注册 RLS 中间件
53
+
54
+ ```typescript
55
+ import express from 'express';
56
+ import { createRlsMiddleware } from '@lark-apaas/express-datapaas';
57
+
58
+ const app = express();
59
+
60
+ // 在 userContext 中间件之后注册
61
+ app.use(createRlsMiddleware());
62
+ ```
63
+
64
+ 中间件会从 `req.userContext` 读取用户信息,自动为后续的数据库查询设置 RLS 上下文。
65
+
66
+ ### 3. 使用数据库
67
+
68
+ ```typescript
69
+ import { db } from './database';
70
+ import { users } from './schema';
71
+
72
+ app.get('/api/users', async (req, res) => {
73
+ // db 查询自动携带 RLS 上下文
74
+ const result = await db.select().from(users);
75
+ res.json(result);
76
+ });
77
+ ```
78
+
79
+ ## API 参考
80
+
81
+ ### createDatabase(config)
82
+
83
+ 创建数据库连接实例。
84
+
85
+ **参数 `CreateDatabaseConfig`:**
86
+
87
+ | 字段 | 类型 | 必填 | 默认值 | 说明 |
88
+ |------|------|------|--------|------|
89
+ | `connectionString` | `string` | 是 | - | PostgreSQL 连接串 |
90
+ | `schema` | `object` | 否 | - | Drizzle schema 对象 |
91
+ | `logger` | `boolean \| Logger` | 否 | `false` | 是否启用 SQL 日志 |
92
+ | `maxConnections` | `number` | 否 | `1` | 最大连接数 |
93
+ | `idleTimeout` | `number` | 否 | `20` | 空闲连接超时(秒) |
94
+ | `connectionTimeout` | `number` | 否 | `10` | 连接超时(秒) |
95
+ | `ssl` | `string \| boolean \| object` | 否 | `false` | SSL 配置,连接串含 `sslmode=require` 时自动启用 |
96
+
97
+ **返回值 `DatabaseInstance`:**
98
+
99
+ | 字段 | 类型 | 说明 |
100
+ |------|------|------|
101
+ | `db` | `PostgresJsDatabase` | Drizzle 数据库实例,可直接用于查询 |
102
+ | `close` | `() => Promise<void>` | 关闭数据库连接 |
103
+
104
+ ### createRlsMiddleware(options?)
105
+
106
+ 创建 Express RLS 中间件。
107
+
108
+ **参数 `RlsMiddlewareOptions`(可选):**
109
+
110
+ | 字段 | 类型 | 说明 |
111
+ |------|------|------|
112
+ | `roleSchema` | `string` | 角色 schema 标识。省略时自动使用 `createDatabase` 从连接串解析的值 |
113
+
114
+ **UserContext 结构:**
115
+
116
+ 中间件从 `req.userContext` 读取以下字段:
117
+
118
+ ```typescript
119
+ interface UserContext {
120
+ userId?: string; // 用户 ID
121
+ tenantId?: number; // 租户 ID
122
+ appId?: string; // 应用 ID
123
+ roles?: string[]; // 用户角色列表
124
+ isSystemAccount: boolean; // 是否系统账号
125
+ userType?: string; // 用户类型
126
+ }
127
+ ```
128
+
129
+ **RLS 行为:**
130
+
131
+ | 场景 | 设置的 PostgreSQL 角色 |
132
+ |------|----------------------|
133
+ | 系统账号 (`isSystemAccount: true`) | `service_role_{schema}` |
134
+ | 已登录用户 (`userId` 存在) | `authenticated_{schema}` |
135
+ | 匿名用户 | `anon_{schema}` |
136
+
137
+ 同时会设置 `app.user_id`、`app.role_ids`、`app.user_type` 等会话变量。
138
+
139
+ ## 注意事项
140
+
141
+ 1. **drizzle-orm 版本锁定**:必须使用 `drizzle-orm@0.44.6`,该版本已通过 patch-package 打补丁以支持 RLS 上下文注入
142
+ 2. **应用层不要单独安装 drizzle-orm**:如果需要 drizzle-orm 的类型导出,直接从 `@lark-apaas/express-datapaas` 导入(如 `PostgresJsDatabase`)
143
+ 3. **中间件顺序**:`createRlsMiddleware()` 必须在设置 `req.userContext` 的中间件之后注册
144
+ 4. **连接关闭**:应用退出时务必调用 `close()` 释放连接资源
145
+
146
+ ## License
147
+
148
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ createDatabase: () => createDatabase,
35
+ createRlsMiddleware: () => createRlsMiddleware
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/create-database.ts
40
+ var import_postgres_js = require("drizzle-orm/postgres-js");
41
+ var import_postgres = __toESM(require("postgres"), 1);
42
+
43
+ // src/utils.ts
44
+ function parseConnectionInfo(connectionString, config) {
45
+ const { maxConnections, idleTimeout, connectionTimeout, ssl, logger } = config || {};
46
+ let parsedConfigFromUrl = null;
47
+ try {
48
+ parsedConfigFromUrl = new URL(connectionString);
49
+ const temp = new URL(connectionString);
50
+ temp.searchParams.delete("schema");
51
+ connectionString = temp.toString();
52
+ } catch (error) {
53
+ throw new Error("Failed to parse connection info");
54
+ }
55
+ const { username, password, hostname, port, pathname } = parsedConfigFromUrl;
56
+ const connectionConfig = {
57
+ username: decodeURIComponent(username),
58
+ password: decodeURIComponent(password),
59
+ host: [
60
+ hostname
61
+ ],
62
+ port: [
63
+ parseInt(port) || 5432
64
+ ],
65
+ pathname: decodeURIComponent(pathname),
66
+ max: maxConnections || 1,
67
+ idle_timeout: idleTimeout || 20,
68
+ connect_timeout: connectionTimeout || 10,
69
+ ssl,
70
+ onnotice: logger ? console.log : void 0
71
+ };
72
+ return {
73
+ connectionString,
74
+ connectionConfig
75
+ };
76
+ }
77
+ __name(parseConnectionInfo, "parseConnectionInfo");
78
+ function parseRoleSchema(connectionString) {
79
+ try {
80
+ const url = new URL(connectionString);
81
+ const roleSchema = url.searchParams.get("schema") || "";
82
+ const sslModeRequired = url.searchParams.get("sslmode") === "require";
83
+ return {
84
+ roleSchema,
85
+ sslModeRequired
86
+ };
87
+ } catch {
88
+ return {
89
+ roleSchema: "",
90
+ sslModeRequired: false
91
+ };
92
+ }
93
+ }
94
+ __name(parseRoleSchema, "parseRoleSchema");
95
+
96
+ // src/create-database.ts
97
+ var _cachedRoleSchema = "";
98
+ function _getRoleSchema() {
99
+ return _cachedRoleSchema;
100
+ }
101
+ __name(_getRoleSchema, "_getRoleSchema");
102
+ function verifyDrizzlePatched() {
103
+ try {
104
+ const session2 = require("drizzle-orm/postgres-js");
105
+ const ok = session2 && typeof session2.getAuthContextStore === "function" && typeof session2.runWithAuthContext === "function";
106
+ if (!ok) {
107
+ throw new Error("drizzle-orm \u672A\u68C0\u6D4B\u5230\u8865\u4E01\u5BFC\u51FA\uFF1AgetAuthContextStore/runWithAuthContext");
108
+ }
109
+ } catch (err) {
110
+ const reason = err instanceof Error ? err.message : String(err);
111
+ throw new Error(`drizzle-orm \u8865\u4E01\u672A\u751F\u6548\u6216\u7248\u672C\u4E0D\u5339\u914D\uFF1A${reason}`);
112
+ }
113
+ }
114
+ __name(verifyDrizzlePatched, "verifyDrizzlePatched");
115
+ function createDatabase(config) {
116
+ verifyDrizzlePatched();
117
+ const { roleSchema, sslModeRequired } = parseRoleSchema(config.connectionString);
118
+ _cachedRoleSchema = roleSchema;
119
+ const resolvedSsl = config.ssl || (sslModeRequired ? "require" : false);
120
+ const { connectionString, connectionConfig } = parseConnectionInfo(config.connectionString, {
121
+ maxConnections: config.maxConnections,
122
+ idleTimeout: config.idleTimeout,
123
+ connectionTimeout: config.connectionTimeout,
124
+ ssl: resolvedSsl,
125
+ logger: typeof config.logger === "boolean" ? config.logger : false
126
+ });
127
+ const sql = (0, import_postgres.default)(connectionString, connectionConfig);
128
+ const db = (0, import_postgres_js.drizzle)(sql, {
129
+ schema: config.schema,
130
+ logger: config.logger
131
+ });
132
+ return {
133
+ db,
134
+ close: /* @__PURE__ */ __name(async () => {
135
+ await sql.end();
136
+ }, "close")
137
+ };
138
+ }
139
+ __name(createDatabase, "createDatabase");
140
+
141
+ // src/create-rls-middleware.ts
142
+ var session = __toESM(require("drizzle-orm/postgres-js"), 1);
143
+ function createRlsMiddleware(options) {
144
+ return /* @__PURE__ */ __name(function rlsMiddleware(req, _res, next) {
145
+ const roleSchema = options?.roleSchema ?? _getRoleSchema();
146
+ const userContext = req.userContext;
147
+ if (!userContext) {
148
+ return next();
149
+ }
150
+ const roles = (userContext.roles || []).join(",");
151
+ const userId = userContext.userId;
152
+ const isSystemAccount = userContext.isSystemAccount;
153
+ const userType = userContext.userType;
154
+ const sqls = [
155
+ userId ? `SET LOCAL app.user_id = '${userId}'` : `SET LOCAL app.user_id = ''`,
156
+ isSystemAccount ? `SET LOCAL ROLE 'service_role_${roleSchema}'` : userId ? `SET LOCAL ROLE 'authenticated_${roleSchema}'` : `SET LOCAL ROLE 'anon_${roleSchema}'`,
157
+ roles ? `SET LOCAL app.role_ids = '${roles}'` : `SET LOCAL app.role_ids = ''`,
158
+ userType ? `SET LOCAL app.user_type = '${userType}'` : `SET LOCAL app.user_type = ''`
159
+ ].filter(Boolean).join(";");
160
+ session.runWithAuthContext({
161
+ preSql: sqls
162
+ }, () => {
163
+ next();
164
+ });
165
+ }, "rlsMiddleware");
166
+ }
167
+ __name(createRlsMiddleware, "createRlsMiddleware");
168
+ // Annotate the CommonJS export names for ESM import in node:
169
+ 0 && (module.exports = {
170
+ createDatabase,
171
+ createRlsMiddleware
172
+ });
173
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/create-database.ts","../src/utils.ts","../src/create-rls-middleware.ts"],"sourcesContent":["export { createDatabase } from './create-database';\nexport { createRlsMiddleware } from './create-rls-middleware';\nexport type { CreateDatabaseConfig, DatabaseInstance, UserContext } from './types';\nexport type { RlsMiddlewareOptions } from './create-rls-middleware';\nexport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\n","import { drizzle } from 'drizzle-orm/postgres-js';\nimport postgres from 'postgres';\nimport { parseConnectionInfo, parseRoleSchema } from './utils';\nimport type { CreateDatabaseConfig, DatabaseInstance } from './types';\n\n// 模块级缓存,供 createRlsMiddleware 零参数调用时读取\nlet _cachedRoleSchema = '';\n\nexport function _getRoleSchema(): string {\n return _cachedRoleSchema;\n}\n\nfunction verifyDrizzlePatched(): void {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const session = require('drizzle-orm/postgres-js');\n const ok =\n session &&\n typeof session.getAuthContextStore === 'function' &&\n typeof session.runWithAuthContext === 'function';\n if (!ok) {\n throw new Error(\n 'drizzle-orm 未检测到补丁导出:getAuthContextStore/runWithAuthContext',\n );\n }\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`drizzle-orm 补丁未生效或版本不匹配:${reason}`);\n }\n}\n\nexport function createDatabase(config: CreateDatabaseConfig): DatabaseInstance {\n verifyDrizzlePatched();\n\n const { roleSchema, sslModeRequired } = parseRoleSchema(config.connectionString);\n _cachedRoleSchema = roleSchema;\n\n const resolvedSsl = config.ssl || (sslModeRequired ? 'require' : false);\n\n const { connectionString, connectionConfig } = parseConnectionInfo(config.connectionString, {\n maxConnections: config.maxConnections,\n idleTimeout: config.idleTimeout,\n connectionTimeout: config.connectionTimeout,\n ssl: resolvedSsl,\n logger: typeof config.logger === 'boolean' ? config.logger : false,\n });\n\n const sql = postgres(connectionString, connectionConfig);\n const db = drizzle(sql, { schema: config.schema, logger: config.logger });\n\n return {\n db,\n close: async () => {\n await sql.end();\n },\n };\n}\n","import { type PostgresOptions } from './types';\n\ninterface ConnectionConfig {\n maxConnections?: number;\n idleTimeout?: number;\n connectionTimeout?: number;\n ssl?: PostgresOptions['ssl'];\n logger?: boolean;\n}\n\nexport function parseConnectionInfo(connectionString: string, config?: ConnectionConfig) {\n const { maxConnections, idleTimeout, connectionTimeout, ssl, logger } = config || {};\n\n let parsedConfigFromUrl = null;\n try {\n parsedConfigFromUrl = new URL(connectionString);\n const temp = new URL(connectionString);\n temp.searchParams.delete('schema');\n connectionString = temp.toString();\n } catch (error) {\n throw new Error('Failed to parse connection info');\n }\n\n const { username, password, hostname, port, pathname } = parsedConfigFromUrl;\n const connectionConfig = {\n username: decodeURIComponent(username),\n password: decodeURIComponent(password),\n host: [hostname] as unknown as string,\n port: [parseInt(port) || 5432] as unknown as number,\n pathname: decodeURIComponent(pathname),\n max: maxConnections || 1,\n idle_timeout: idleTimeout || 20,\n connect_timeout: connectionTimeout || 10,\n ssl,\n onnotice: logger ? console.log : undefined,\n };\n\n return { connectionString, connectionConfig };\n}\n\nexport function parseRoleSchema(connectionString: string): { roleSchema: string; sslModeRequired: boolean } {\n try {\n const url = new URL(connectionString);\n const roleSchema = url.searchParams.get('schema') || '';\n const sslModeRequired = url.searchParams.get('sslmode') === 'require';\n return { roleSchema, sslModeRequired };\n } catch {\n return { roleSchema: '', sslModeRequired: false };\n }\n}\n","import { type Request, type Response, type NextFunction } from 'express';\nimport * as session from 'drizzle-orm/postgres-js';\nimport { _getRoleSchema } from './create-database';\nimport type { UserContext } from './types';\n\nexport interface RlsMiddlewareOptions {\n roleSchema?: string;\n}\n\nexport function createRlsMiddleware(options?: RlsMiddlewareOptions) {\n return function rlsMiddleware(req: Request, _res: Response, next: NextFunction) {\n const roleSchema = options?.roleSchema ?? _getRoleSchema();\n const userContext = (req as unknown as { userContext: UserContext }).userContext;\n\n if (!userContext) {\n return next();\n }\n\n const roles = (userContext.roles || []).join(',');\n const userId = userContext.userId;\n const isSystemAccount = userContext.isSystemAccount;\n const userType = userContext.userType;\n\n const sqls = [\n userId\n ? `SET LOCAL app.user_id = '${userId}'`\n : `SET LOCAL app.user_id = ''`,\n isSystemAccount\n ? `SET LOCAL ROLE 'service_role_${roleSchema}'`\n : userId\n ? `SET LOCAL ROLE 'authenticated_${roleSchema}'`\n : `SET LOCAL ROLE 'anon_${roleSchema}'`,\n roles\n ? `SET LOCAL app.role_ids = '${roles}'`\n : `SET LOCAL app.role_ids = ''`,\n userType\n ? `SET LOCAL app.user_type = '${userType}'`\n : `SET LOCAL app.user_type = ''`,\n ]\n .filter(Boolean)\n .join(';');\n\n (session as unknown as { runWithAuthContext: (store: { preSql: string }, fn: () => void) => void })\n .runWithAuthContext({ preSql: sqls }, () => {\n next();\n });\n };\n}\n"],"mappings":"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","names":["parseConnectionInfo","connectionString","config","maxConnections","idleTimeout","connectionTimeout","ssl","logger","parsedConfigFromUrl","URL","temp","searchParams","delete","toString","error","Error","username","password","hostname","port","pathname","connectionConfig","decodeURIComponent","host","parseInt","max","idle_timeout","connect_timeout","onnotice","console","log","undefined","parseRoleSchema","url","roleSchema","get","sslModeRequired","_cachedRoleSchema","_getRoleSchema","verifyDrizzlePatched","session","require","ok","getAuthContextStore","runWithAuthContext","Error","err","reason","message","String","createDatabase","config","roleSchema","sslModeRequired","parseRoleSchema","connectionString","resolvedSsl","ssl","connectionConfig","parseConnectionInfo","maxConnections","idleTimeout","connectionTimeout","logger","sql","postgres","db","drizzle","schema","close","end","createRlsMiddleware","options","rlsMiddleware","req","_res","next","roleSchema","_getRoleSchema","userContext","roles","join","userId","isSystemAccount","userType","sqls","filter","Boolean","session","runWithAuthContext","preSql"]}
@@ -0,0 +1,37 @@
1
+ import { Options } from 'postgres';
2
+ import { Logger } from 'drizzle-orm';
3
+ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
4
+ export { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
5
+ import { Request, Response, NextFunction } from 'express';
6
+
7
+ type PostgresOptions = Options<any>;
8
+ interface CreateDatabaseConfig {
9
+ connectionString: string;
10
+ schema?: any;
11
+ logger?: boolean | Logger;
12
+ maxConnections?: number;
13
+ idleTimeout?: number;
14
+ connectionTimeout?: number;
15
+ ssl?: PostgresOptions['ssl'];
16
+ }
17
+ interface DatabaseInstance {
18
+ db: PostgresJsDatabase;
19
+ close: () => Promise<void>;
20
+ }
21
+ interface UserContext {
22
+ userId?: string;
23
+ tenantId?: number;
24
+ appId?: string;
25
+ roles?: string[];
26
+ isSystemAccount: boolean;
27
+ userType?: string;
28
+ }
29
+
30
+ declare function createDatabase(config: CreateDatabaseConfig): DatabaseInstance;
31
+
32
+ interface RlsMiddlewareOptions {
33
+ roleSchema?: string;
34
+ }
35
+ declare function createRlsMiddleware(options?: RlsMiddlewareOptions): (req: Request, _res: Response, next: NextFunction) => void;
36
+
37
+ export { type CreateDatabaseConfig, type DatabaseInstance, type RlsMiddlewareOptions, type UserContext, createDatabase, createRlsMiddleware };
@@ -0,0 +1,37 @@
1
+ import { Options } from 'postgres';
2
+ import { Logger } from 'drizzle-orm';
3
+ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
4
+ export { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
5
+ import { Request, Response, NextFunction } from 'express';
6
+
7
+ type PostgresOptions = Options<any>;
8
+ interface CreateDatabaseConfig {
9
+ connectionString: string;
10
+ schema?: any;
11
+ logger?: boolean | Logger;
12
+ maxConnections?: number;
13
+ idleTimeout?: number;
14
+ connectionTimeout?: number;
15
+ ssl?: PostgresOptions['ssl'];
16
+ }
17
+ interface DatabaseInstance {
18
+ db: PostgresJsDatabase;
19
+ close: () => Promise<void>;
20
+ }
21
+ interface UserContext {
22
+ userId?: string;
23
+ tenantId?: number;
24
+ appId?: string;
25
+ roles?: string[];
26
+ isSystemAccount: boolean;
27
+ userType?: string;
28
+ }
29
+
30
+ declare function createDatabase(config: CreateDatabaseConfig): DatabaseInstance;
31
+
32
+ interface RlsMiddlewareOptions {
33
+ roleSchema?: string;
34
+ }
35
+ declare function createRlsMiddleware(options?: RlsMiddlewareOptions): (req: Request, _res: Response, next: NextFunction) => void;
36
+
37
+ export { type CreateDatabaseConfig, type DatabaseInstance, type RlsMiddlewareOptions, type UserContext, createDatabase, createRlsMiddleware };
package/dist/index.js ADDED
@@ -0,0 +1,143 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/create-database.ts
11
+ import { drizzle } from "drizzle-orm/postgres-js";
12
+ import postgres from "postgres";
13
+
14
+ // src/utils.ts
15
+ function parseConnectionInfo(connectionString, config) {
16
+ const { maxConnections, idleTimeout, connectionTimeout, ssl, logger } = config || {};
17
+ let parsedConfigFromUrl = null;
18
+ try {
19
+ parsedConfigFromUrl = new URL(connectionString);
20
+ const temp = new URL(connectionString);
21
+ temp.searchParams.delete("schema");
22
+ connectionString = temp.toString();
23
+ } catch (error) {
24
+ throw new Error("Failed to parse connection info");
25
+ }
26
+ const { username, password, hostname, port, pathname } = parsedConfigFromUrl;
27
+ const connectionConfig = {
28
+ username: decodeURIComponent(username),
29
+ password: decodeURIComponent(password),
30
+ host: [
31
+ hostname
32
+ ],
33
+ port: [
34
+ parseInt(port) || 5432
35
+ ],
36
+ pathname: decodeURIComponent(pathname),
37
+ max: maxConnections || 1,
38
+ idle_timeout: idleTimeout || 20,
39
+ connect_timeout: connectionTimeout || 10,
40
+ ssl,
41
+ onnotice: logger ? console.log : void 0
42
+ };
43
+ return {
44
+ connectionString,
45
+ connectionConfig
46
+ };
47
+ }
48
+ __name(parseConnectionInfo, "parseConnectionInfo");
49
+ function parseRoleSchema(connectionString) {
50
+ try {
51
+ const url = new URL(connectionString);
52
+ const roleSchema = url.searchParams.get("schema") || "";
53
+ const sslModeRequired = url.searchParams.get("sslmode") === "require";
54
+ return {
55
+ roleSchema,
56
+ sslModeRequired
57
+ };
58
+ } catch {
59
+ return {
60
+ roleSchema: "",
61
+ sslModeRequired: false
62
+ };
63
+ }
64
+ }
65
+ __name(parseRoleSchema, "parseRoleSchema");
66
+
67
+ // src/create-database.ts
68
+ var _cachedRoleSchema = "";
69
+ function _getRoleSchema() {
70
+ return _cachedRoleSchema;
71
+ }
72
+ __name(_getRoleSchema, "_getRoleSchema");
73
+ function verifyDrizzlePatched() {
74
+ try {
75
+ const session2 = __require("drizzle-orm/postgres-js");
76
+ const ok = session2 && typeof session2.getAuthContextStore === "function" && typeof session2.runWithAuthContext === "function";
77
+ if (!ok) {
78
+ throw new Error("drizzle-orm \u672A\u68C0\u6D4B\u5230\u8865\u4E01\u5BFC\u51FA\uFF1AgetAuthContextStore/runWithAuthContext");
79
+ }
80
+ } catch (err) {
81
+ const reason = err instanceof Error ? err.message : String(err);
82
+ throw new Error(`drizzle-orm \u8865\u4E01\u672A\u751F\u6548\u6216\u7248\u672C\u4E0D\u5339\u914D\uFF1A${reason}`);
83
+ }
84
+ }
85
+ __name(verifyDrizzlePatched, "verifyDrizzlePatched");
86
+ function createDatabase(config) {
87
+ verifyDrizzlePatched();
88
+ const { roleSchema, sslModeRequired } = parseRoleSchema(config.connectionString);
89
+ _cachedRoleSchema = roleSchema;
90
+ const resolvedSsl = config.ssl || (sslModeRequired ? "require" : false);
91
+ const { connectionString, connectionConfig } = parseConnectionInfo(config.connectionString, {
92
+ maxConnections: config.maxConnections,
93
+ idleTimeout: config.idleTimeout,
94
+ connectionTimeout: config.connectionTimeout,
95
+ ssl: resolvedSsl,
96
+ logger: typeof config.logger === "boolean" ? config.logger : false
97
+ });
98
+ const sql = postgres(connectionString, connectionConfig);
99
+ const db = drizzle(sql, {
100
+ schema: config.schema,
101
+ logger: config.logger
102
+ });
103
+ return {
104
+ db,
105
+ close: /* @__PURE__ */ __name(async () => {
106
+ await sql.end();
107
+ }, "close")
108
+ };
109
+ }
110
+ __name(createDatabase, "createDatabase");
111
+
112
+ // src/create-rls-middleware.ts
113
+ import * as session from "drizzle-orm/postgres-js";
114
+ function createRlsMiddleware(options) {
115
+ return /* @__PURE__ */ __name(function rlsMiddleware(req, _res, next) {
116
+ const roleSchema = options?.roleSchema ?? _getRoleSchema();
117
+ const userContext = req.userContext;
118
+ if (!userContext) {
119
+ return next();
120
+ }
121
+ const roles = (userContext.roles || []).join(",");
122
+ const userId = userContext.userId;
123
+ const isSystemAccount = userContext.isSystemAccount;
124
+ const userType = userContext.userType;
125
+ const sqls = [
126
+ userId ? `SET LOCAL app.user_id = '${userId}'` : `SET LOCAL app.user_id = ''`,
127
+ isSystemAccount ? `SET LOCAL ROLE 'service_role_${roleSchema}'` : userId ? `SET LOCAL ROLE 'authenticated_${roleSchema}'` : `SET LOCAL ROLE 'anon_${roleSchema}'`,
128
+ roles ? `SET LOCAL app.role_ids = '${roles}'` : `SET LOCAL app.role_ids = ''`,
129
+ userType ? `SET LOCAL app.user_type = '${userType}'` : `SET LOCAL app.user_type = ''`
130
+ ].filter(Boolean).join(";");
131
+ session.runWithAuthContext({
132
+ preSql: sqls
133
+ }, () => {
134
+ next();
135
+ });
136
+ }, "rlsMiddleware");
137
+ }
138
+ __name(createRlsMiddleware, "createRlsMiddleware");
139
+ export {
140
+ createDatabase,
141
+ createRlsMiddleware
142
+ };
143
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/create-database.ts","../src/utils.ts","../src/create-rls-middleware.ts"],"sourcesContent":["import { drizzle } from 'drizzle-orm/postgres-js';\nimport postgres from 'postgres';\nimport { parseConnectionInfo, parseRoleSchema } from './utils';\nimport type { CreateDatabaseConfig, DatabaseInstance } from './types';\n\n// 模块级缓存,供 createRlsMiddleware 零参数调用时读取\nlet _cachedRoleSchema = '';\n\nexport function _getRoleSchema(): string {\n return _cachedRoleSchema;\n}\n\nfunction verifyDrizzlePatched(): void {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const session = require('drizzle-orm/postgres-js');\n const ok =\n session &&\n typeof session.getAuthContextStore === 'function' &&\n typeof session.runWithAuthContext === 'function';\n if (!ok) {\n throw new Error(\n 'drizzle-orm 未检测到补丁导出:getAuthContextStore/runWithAuthContext',\n );\n }\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`drizzle-orm 补丁未生效或版本不匹配:${reason}`);\n }\n}\n\nexport function createDatabase(config: CreateDatabaseConfig): DatabaseInstance {\n verifyDrizzlePatched();\n\n const { roleSchema, sslModeRequired } = parseRoleSchema(config.connectionString);\n _cachedRoleSchema = roleSchema;\n\n const resolvedSsl = config.ssl || (sslModeRequired ? 'require' : false);\n\n const { connectionString, connectionConfig } = parseConnectionInfo(config.connectionString, {\n maxConnections: config.maxConnections,\n idleTimeout: config.idleTimeout,\n connectionTimeout: config.connectionTimeout,\n ssl: resolvedSsl,\n logger: typeof config.logger === 'boolean' ? config.logger : false,\n });\n\n const sql = postgres(connectionString, connectionConfig);\n const db = drizzle(sql, { schema: config.schema, logger: config.logger });\n\n return {\n db,\n close: async () => {\n await sql.end();\n },\n };\n}\n","import { type PostgresOptions } from './types';\n\ninterface ConnectionConfig {\n maxConnections?: number;\n idleTimeout?: number;\n connectionTimeout?: number;\n ssl?: PostgresOptions['ssl'];\n logger?: boolean;\n}\n\nexport function parseConnectionInfo(connectionString: string, config?: ConnectionConfig) {\n const { maxConnections, idleTimeout, connectionTimeout, ssl, logger } = config || {};\n\n let parsedConfigFromUrl = null;\n try {\n parsedConfigFromUrl = new URL(connectionString);\n const temp = new URL(connectionString);\n temp.searchParams.delete('schema');\n connectionString = temp.toString();\n } catch (error) {\n throw new Error('Failed to parse connection info');\n }\n\n const { username, password, hostname, port, pathname } = parsedConfigFromUrl;\n const connectionConfig = {\n username: decodeURIComponent(username),\n password: decodeURIComponent(password),\n host: [hostname] as unknown as string,\n port: [parseInt(port) || 5432] as unknown as number,\n pathname: decodeURIComponent(pathname),\n max: maxConnections || 1,\n idle_timeout: idleTimeout || 20,\n connect_timeout: connectionTimeout || 10,\n ssl,\n onnotice: logger ? console.log : undefined,\n };\n\n return { connectionString, connectionConfig };\n}\n\nexport function parseRoleSchema(connectionString: string): { roleSchema: string; sslModeRequired: boolean } {\n try {\n const url = new URL(connectionString);\n const roleSchema = url.searchParams.get('schema') || '';\n const sslModeRequired = url.searchParams.get('sslmode') === 'require';\n return { roleSchema, sslModeRequired };\n } catch {\n return { roleSchema: '', sslModeRequired: false };\n }\n}\n","import { type Request, type Response, type NextFunction } from 'express';\nimport * as session from 'drizzle-orm/postgres-js';\nimport { _getRoleSchema } from './create-database';\nimport type { UserContext } from './types';\n\nexport interface RlsMiddlewareOptions {\n roleSchema?: string;\n}\n\nexport function createRlsMiddleware(options?: RlsMiddlewareOptions) {\n return function rlsMiddleware(req: Request, _res: Response, next: NextFunction) {\n const roleSchema = options?.roleSchema ?? _getRoleSchema();\n const userContext = (req as unknown as { userContext: UserContext }).userContext;\n\n if (!userContext) {\n return next();\n }\n\n const roles = (userContext.roles || []).join(',');\n const userId = userContext.userId;\n const isSystemAccount = userContext.isSystemAccount;\n const userType = userContext.userType;\n\n const sqls = [\n userId\n ? `SET LOCAL app.user_id = '${userId}'`\n : `SET LOCAL app.user_id = ''`,\n isSystemAccount\n ? `SET LOCAL ROLE 'service_role_${roleSchema}'`\n : userId\n ? `SET LOCAL ROLE 'authenticated_${roleSchema}'`\n : `SET LOCAL ROLE 'anon_${roleSchema}'`,\n roles\n ? `SET LOCAL app.role_ids = '${roles}'`\n : `SET LOCAL app.role_ids = ''`,\n userType\n ? `SET LOCAL app.user_type = '${userType}'`\n : `SET LOCAL app.user_type = ''`,\n ]\n .filter(Boolean)\n .join(';');\n\n (session as unknown as { runWithAuthContext: (store: { preSql: string }, fn: () => void) => void })\n .runWithAuthContext({ preSql: sqls }, () => {\n next();\n });\n };\n}\n"],"mappings":"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","names":["drizzle","postgres","parseConnectionInfo","connectionString","config","maxConnections","idleTimeout","connectionTimeout","ssl","logger","parsedConfigFromUrl","URL","temp","searchParams","delete","toString","error","Error","username","password","hostname","port","pathname","connectionConfig","decodeURIComponent","host","parseInt","max","idle_timeout","connect_timeout","onnotice","console","log","undefined","parseRoleSchema","url","roleSchema","get","sslModeRequired","_cachedRoleSchema","_getRoleSchema","verifyDrizzlePatched","session","require","ok","getAuthContextStore","runWithAuthContext","Error","err","reason","message","String","createDatabase","config","roleSchema","sslModeRequired","parseRoleSchema","connectionString","resolvedSsl","ssl","connectionConfig","parseConnectionInfo","maxConnections","idleTimeout","connectionTimeout","logger","sql","postgres","db","drizzle","schema","close","end","session","createRlsMiddleware","options","rlsMiddleware","req","_res","next","roleSchema","_getRoleSchema","userContext","roles","join","userId","isSystemAccount","userType","sqls","filter","Boolean","session","runWithAuthContext","preSql"]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@lark-apaas/express-datapaas",
3
+ "version": "0.0.1",
4
+ "description": "Drizzle ORM + PostgreSQL with RLS support for Express",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "plugin",
14
+ "express",
15
+ "server",
16
+ "typescript",
17
+ "drizzle",
18
+ "postgres"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "patches",
26
+ "scripts"
27
+ ],
28
+ "exports": {
29
+ ".": {
30
+ "import": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "require": "./dist/index.cjs"
33
+ }
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "postinstall": "npm run apply-patch",
38
+ "apply-patch": "node scripts/apply-patch.js",
39
+ "dev": "tsup --watch",
40
+ "clean": "rm -rf dist",
41
+ "lint": "eslint src/**/*.ts",
42
+ "type-check": "tsc --noEmit",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest --watch",
45
+ "prepublishOnly": "npm run build"
46
+ },
47
+ "dependencies": {
48
+ "patch-package": "^8.0.0",
49
+ "postgres": "^3.4.3"
50
+ },
51
+ "devDependencies": {
52
+ "@types/express": "^5.0.0",
53
+ "@types/node": "^20.0.0",
54
+ "tsup": "^8.0.0",
55
+ "typescript": "^5.0.0",
56
+ "vitest": "^3.2.4"
57
+ },
58
+ "peerDependencies": {
59
+ "drizzle-orm": "0.44.6",
60
+ "express": "^4.0.0 || ^5.0.0"
61
+ }
62
+ }
@@ -0,0 +1,178 @@
1
+ diff --git a/node_modules/drizzle-orm/postgres-js/session.cjs b/node_modules/drizzle-orm/postgres-js/session.cjs
2
+ index dac222a..7708081 100644
3
+ --- a/node_modules/drizzle-orm/postgres-js/session.cjs
4
+ +++ b/node_modules/drizzle-orm/postgres-js/session.cjs
5
+ @@ -20,9 +20,12 @@ var session_exports = {};
6
+ __export(session_exports, {
7
+ PostgresJsPreparedQuery: () => PostgresJsPreparedQuery,
8
+ PostgresJsSession: () => PostgresJsSession,
9
+ - PostgresJsTransaction: () => PostgresJsTransaction
10
+ + PostgresJsTransaction: () => PostgresJsTransaction,
11
+ + getAuthContextStore: () => getAuthContextStore,
12
+ + runWithAuthContext: () => runWithAuthContext,
13
+ });
14
+ module.exports = __toCommonJS(session_exports);
15
+ +var { AsyncLocalStorage } = require('node:async_hooks');
16
+ var import_core = require("../cache/core/index.cjs");
17
+ var import_entity = require("../entity.cjs");
18
+ var import_logger = require("../logger.cjs");
19
+ @@ -31,10 +34,24 @@ var import_session = require("../pg-core/session.cjs");
20
+ var import_sql = require("../sql/sql.cjs");
21
+ var import_tracing = require("../tracing.cjs");
22
+ var import_utils = require("../utils.cjs");
23
+ +
24
+ +const asyncLocalStorage = new AsyncLocalStorage();
25
+ +
26
+ +function getAuthContextStore() {
27
+ + return asyncLocalStorage.getStore();
28
+ +}
29
+ +
30
+ +function runWithAuthContext(store, fn) {
31
+ + return asyncLocalStorage.run(store, fn);
32
+ +}
33
+ +
34
+ class PostgresJsPreparedQuery extends import_session.PgPreparedQuery {
35
+ constructor(client, queryString, params, logger, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
36
+ super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
37
+ this.client = client;
38
+ + const authContextStore = getAuthContextStore();
39
+ + this.preSql = authContextStore?.preSql;
40
+ + this.postSql = authContextStore?.postSql;
41
+ this.queryString = queryString;
42
+ this.params = params;
43
+ this.logger = logger;
44
+ @@ -51,21 +68,103 @@ class PostgresJsPreparedQuery extends import_session.PgPreparedQuery {
45
+ "drizzle.query.params": JSON.stringify(params)
46
+ });
47
+ this.logger.logQuery(this.queryString, params);
48
+ - const { fields, queryString: query, client, joinsNotNullableMap, customResultMapper } = this;
49
+ + const { fields, client, joinsNotNullableMap, customResultMapper, preSql, postSql } = this;
50
+ +
51
+ + // 在同一事务中依次执行:preSql(可选)→ 主查询 → postSql(可选)
52
+ + if (preSql || postSql) {
53
+ + return import_tracing.tracer.startActiveSpan("drizzle.driver.execute.transactional", async () => {
54
+ + // 若当前不在事务中(client 有 begin 方法),则开启事务;否则复用当前事务
55
+ + if (typeof client.begin === 'function') {
56
+ + return client.begin(async (tx) => {
57
+ + // 若存在 preSql,则执行
58
+ + if (preSql) {
59
+ + await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.pre", () => tx.unsafe(preSql));
60
+ + }
61
+ +
62
+ + // 执行主查询
63
+ + let result;
64
+ + if (!fields && !customResultMapper) {
65
+ + result = await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.main", () => {
66
+ + return this.queryWithCache(this.queryString, params, async () => {
67
+ + return await tx.unsafe(this.queryString, params);
68
+ + });
69
+ + });
70
+ + } else {
71
+ + const rows = await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.main", () => {
72
+ + span?.setAttributes({
73
+ + "drizzle.query.text": this.queryString,
74
+ + "drizzle.query.params": JSON.stringify(params)
75
+ + });
76
+ + return this.queryWithCache(this.queryString, params, async () => {
77
+ + return await tx.unsafe(this.queryString, params).values();
78
+ + });;
79
+ + });
80
+ + result = import_tracing.tracer.startActiveSpan("drizzle.mapResponse", () => {
81
+ + return customResultMapper ? customResultMapper(rows) : rows.map((row) => (0, import_utils.mapResultRow)(fields, row, joinsNotNullableMap));
82
+ + });
83
+ + }
84
+ +
85
+ + // 若存在 postSql,则执行
86
+ + if (postSql) {
87
+ + await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.post", () => tx.unsafe(postSql));
88
+ + }
89
+ +
90
+ + return result;
91
+ + });
92
+ + } else {
93
+ + // 已在事务中,直接执行
94
+ + // 若存在 preSql,则执行
95
+ + if (preSql) {
96
+ + await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.pre", () => client.unsafe(preSql));
97
+ + }
98
+ +
99
+ + // 执行主查询
100
+ + let result;
101
+ + if (!fields && !customResultMapper) {
102
+ + result = await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.main", () => {
103
+ + return this.queryWithCache(this.queryString, params, async () => {
104
+ + return await client.unsafe(this.queryString, params);
105
+ + });
106
+ + });
107
+ + } else {
108
+ + const rows = await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.main", () => {
109
+ + span?.setAttributes({
110
+ + "drizzle.query.text": this.queryString,
111
+ + "drizzle.query.params": JSON.stringify(params)
112
+ + });
113
+ + return this.queryWithCache(this.queryString, params, async () => {
114
+ + return await client.unsafe(this.queryString, params).values();
115
+ + });
116
+ + });
117
+ + result = import_tracing.tracer.startActiveSpan("drizzle.mapResponse", () => {
118
+ + return customResultMapper ? customResultMapper(rows) : rows.map((row) => (0, import_utils.mapResultRow)(fields, row, joinsNotNullableMap));
119
+ + });
120
+ + }
121
+ +
122
+ + // 若存在 postSql,则执行
123
+ + if (postSql) {
124
+ + await import_tracing.tracer.startActiveSpan("drizzle.driver.execute.post", () => client.unsafe(postSql));
125
+ + }
126
+ +
127
+ + return result;
128
+ + }
129
+ + });
130
+ + }
131
+ + // 单语句执行(原始逻辑)
132
+ if (!fields && !customResultMapper) {
133
+ return import_tracing.tracer.startActiveSpan("drizzle.driver.execute", () => {
134
+ - return this.queryWithCache(query, params, async () => {
135
+ - return await client.unsafe(query, params);
136
+ + return this.queryWithCache(this.queryString, params, async () => {
137
+ + return await client.unsafe(this.queryString, params);
138
+ });
139
+ });
140
+ }
141
+ const rows = await import_tracing.tracer.startActiveSpan("drizzle.driver.execute", () => {
142
+ span?.setAttributes({
143
+ - "drizzle.query.text": query,
144
+ + "drizzle.query.text": this.queryString,
145
+ "drizzle.query.params": JSON.stringify(params)
146
+ });
147
+ - return this.queryWithCache(query, params, async () => {
148
+ - return await client.unsafe(query, params).values();
149
+ + return this.queryWithCache(this.queryString, params, async () => {
150
+ + return await client.unsafe(this.queryString, params).values();
151
+ });
152
+ });
153
+ return import_tracing.tracer.startActiveSpan("drizzle.mapResponse", () => {
154
+ @@ -169,6 +268,8 @@ class PostgresJsTransaction extends import_pg_core.PgTransaction {
155
+ 0 && (module.exports = {
156
+ PostgresJsPreparedQuery,
157
+ PostgresJsSession,
158
+ - PostgresJsTransaction
159
+ + PostgresJsTransaction,
160
+ + getAuthContextStore,
161
+ + runWithAuthContext,
162
+ });
163
+ //# sourceMappingURL=session.cjs.map
164
+
165
+ diff --git a/node_modules/drizzle-orm/postgres-js/session.d.ts b/node_modules/drizzle-orm/postgres-js/session.d.ts
166
+ index bccaa53..b07425b 100644
167
+ --- a/node_modules/drizzle-orm/postgres-js/session.d.ts
168
+ +++ b/node_modules/drizzle-orm/postgres-js/session.d.ts
169
+ @@ -58,3 +58,8 @@ export declare class PostgresJsTransaction<TFullSchema extends Record<string, un
170
+ export interface PostgresJsQueryResultHKT extends PgQueryResultHKT {
171
+ type: RowList<Assume<this['row'], Row>[]>;
172
+ }
173
+ +export interface RequestSessionContext {
174
+ + preSql?: string;
175
+ +}
176
+ +export declare function getAuthContextStore(): RequestSessionContext | undefined;
177
+ +export declare function runWithAuthContext(store: RequestSessionContext, fn: () => void): void;
178
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "child_process";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const sdkRoot = path.resolve(__dirname, "..");
8
+ const hostRoot = path.resolve(sdkRoot, "../../.."); // npm、yarn 场景下,安装在 node_modules/@lark-apaas/express-datapaas
9
+
10
+ console.log(`[patch] Applying drizzle-orm patch at host: ${hostRoot}`);
11
+
12
+ try {
13
+ execSync(
14
+ "npx patch-package --patch-dir node_modules/@lark-apaas/express-datapaas/patches",
15
+ { stdio: "inherit", cwd: hostRoot }
16
+ );
17
+ console.log("[patch] drizzle-orm patched ✅");
18
+ } catch (err) {
19
+ console.error("[patch] Failed to apply drizzle-orm patch ❌", err);
20
+ process.exit(1);
21
+ }