@lark-apaas/nestjs-datapaas 1.0.17 → 1.0.19

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/index.cjs CHANGED
@@ -225,6 +225,7 @@ var DrizzleDatabaseManager = class {
225
225
  requestContext;
226
226
  db = null;
227
227
  sqlClient = null;
228
+ connectionOpPromise = null;
228
229
  currentConnectionInfo = null;
229
230
  isConnected = false;
230
231
  skipInitDbConnection = false;
@@ -233,7 +234,7 @@ var DrizzleDatabaseManager = class {
233
234
  tokenWatchFilePath = null;
234
235
  tokenWatchDebounceTimer = null;
235
236
  refreshingByTokenChange = false;
236
- reconnectingPromise = null;
237
+ sqlClientShutdownTimeoutSeconds = 5;
237
238
  /**
238
239
  * 构造函数:注入配置、日志能力与请求上下文。
239
240
  */
@@ -269,32 +270,30 @@ var DrizzleDatabaseManager = class {
269
270
  };
270
271
  return this.db;
271
272
  }
272
- try {
273
- const resolvedConnectionString = await this.buildConnectionStringWithToken();
274
- const { connectionString, connectionConfig } = parseConnectionInfo(resolvedConnectionString, this.config);
275
- const baseSql = (0, import_postgres.default)(connectionString, connectionConfig);
276
- const sqlProxy = genPostgresProxy(baseSql, this.observable, this.requestContext);
277
- this.db = (0, import_postgres_js.drizzle)(sqlProxy, {
278
- schema: this.config.schema,
279
- logger: this.config.logger
280
- });
281
- this.sqlClient = baseSql;
282
- this.currentResolvedConnectionString = connectionString;
283
- this.currentConnectionInfo = {
284
- connectionString,
285
- connectionConfig: {
286
- ssl: connectionConfig.ssl,
287
- connectionTimeout: connectionConfig.connect_timeout,
288
- idleTimeout: connectionConfig.idle_timeout,
289
- maxConnections: connectionConfig.max
290
- }
291
- };
292
- this.isConnected = true;
273
+ if (this.db && this.isConnected) {
293
274
  return this.db;
294
- } catch (error) {
295
- this.isConnected = false;
296
- throw new Error(`Failed to initialize database connection: ${error instanceof Error ? error.message : "Unknown error"}`);
297
275
  }
276
+ if (this.connectionOpPromise) {
277
+ return this.connectionOpPromise;
278
+ }
279
+ return this.runWithConnectionLock(async () => {
280
+ if (this.db && this.isConnected) {
281
+ return this.db;
282
+ }
283
+ try {
284
+ const token = await this.readConnectionTokenFromFile();
285
+ const resolvedConnectionString = this.appendTokenToConnectionString(this.config.connectionString, token);
286
+ const nextState = await this.createConnectedState(resolvedConnectionString, token);
287
+ this.applyConnectedState(nextState);
288
+ return nextState.db;
289
+ } catch (error) {
290
+ if (!this.db || !this.isConnected) {
291
+ this.isConnected = false;
292
+ this.db = null;
293
+ }
294
+ throw new Error(`Failed to initialize database connection: ${error instanceof Error ? error.message : "Unknown error"}`);
295
+ }
296
+ });
298
297
  }
299
298
  /**
300
299
  * 获取数据库实例
@@ -327,22 +326,10 @@ var DrizzleDatabaseManager = class {
327
326
  * 重新连接数据库。
328
327
  * 注意:重连时不关闭 token watcher,避免重连后丢失后续文件变更监听。
329
328
  * 同时通过 promise 锁避免并发重连导致状态抖动。
330
- * @param forceRefresh 是否强制刷新连接信息
331
329
  * @returns 新的数据库实例
332
330
  */
333
- async reconnect(_forceRefresh = false) {
334
- if (this.reconnectingPromise) {
335
- return this.reconnectingPromise;
336
- }
337
- this.reconnectingPromise = (async () => {
338
- await this.disconnect(false);
339
- return this.initialize();
340
- })();
341
- try {
342
- return await this.reconnectingPromise;
343
- } finally {
344
- this.reconnectingPromise = null;
345
- }
331
+ async reconnect() {
332
+ return this.reconnectInternal();
346
333
  }
347
334
  /**
348
335
  * 断开数据库连接
@@ -351,21 +338,19 @@ var DrizzleDatabaseManager = class {
351
338
  if (shouldStopWatcher) {
352
339
  this.stopTokenWatcher();
353
340
  }
354
- try {
355
- if (this.sqlClient && typeof this.sqlClient.end === "function") {
356
- await this.sqlClient.end({
357
- timeout: 5
358
- });
359
- }
360
- } catch (error) {
361
- console.error("Error disconnecting from database:", error);
362
- } finally {
363
- this.sqlClient = null;
364
- this.db = null;
365
- this.isConnected = false;
366
- this.currentConnectionInfo = null;
367
- this.currentResolvedConnectionString = null;
341
+ const pendingOp = this.connectionOpPromise;
342
+ if (pendingOp) {
343
+ await Promise.allSettled([
344
+ pendingOp
345
+ ]);
368
346
  }
347
+ await this.shutdownSqlClientSafely(this.sqlClient, "Error disconnecting from database:");
348
+ this.sqlClient = null;
349
+ this.db = null;
350
+ this.isConnected = false;
351
+ this.currentConnectionInfo = null;
352
+ this.currentResolvedConnectionString = null;
353
+ this.currentToken = null;
369
354
  }
370
355
  /**
371
356
  * 获取当前连接信息
@@ -435,13 +420,80 @@ var DrizzleDatabaseManager = class {
435
420
  url.searchParams.set("options", nextOptions);
436
421
  return url.toString();
437
422
  }
438
- /**
439
- * 读取当前 token 并构造最终连接串,同时更新内存中的 currentToken。
440
- */
441
- async buildConnectionStringWithToken() {
442
- const token = await this.readConnectionTokenFromFile();
443
- this.currentToken = token;
444
- return this.appendTokenToConnectionString(this.config.connectionString, token);
423
+ async createConnectedState(resolvedConnectionString, token) {
424
+ const { connectionString, connectionConfig } = parseConnectionInfo(resolvedConnectionString, this.config);
425
+ const baseSql = (0, import_postgres.default)(connectionString, connectionConfig);
426
+ try {
427
+ const sqlProxy = genPostgresProxy(baseSql, this.observable, this.requestContext);
428
+ const db = (0, import_postgres_js.drizzle)(sqlProxy, {
429
+ schema: this.config.schema,
430
+ logger: this.config.logger
431
+ });
432
+ return {
433
+ db,
434
+ sqlClient: baseSql,
435
+ resolvedConnectionString: connectionString,
436
+ token,
437
+ connectionInfo: {
438
+ connectionString,
439
+ connectionConfig: {
440
+ ssl: connectionConfig.ssl,
441
+ connectionTimeout: connectionConfig.connect_timeout,
442
+ idleTimeout: connectionConfig.idle_timeout,
443
+ maxConnections: connectionConfig.max
444
+ }
445
+ }
446
+ };
447
+ } catch (error) {
448
+ await this.shutdownSqlClientSafely(baseSql, "Error closing failed new database client:");
449
+ throw error;
450
+ }
451
+ }
452
+ applyConnectedState(nextState) {
453
+ this.db = nextState.db;
454
+ this.sqlClient = nextState.sqlClient;
455
+ this.currentConnectionInfo = nextState.connectionInfo;
456
+ this.currentResolvedConnectionString = nextState.resolvedConnectionString;
457
+ this.currentToken = nextState.token;
458
+ this.isConnected = true;
459
+ }
460
+ reconnectInternal(nextResolvedConnectionString, nextToken) {
461
+ return this.runWithConnectionLock(() => this.reconnectWithResolvedConnectionString(nextResolvedConnectionString, nextToken));
462
+ }
463
+ runWithConnectionLock(operation) {
464
+ if (this.connectionOpPromise) {
465
+ return this.connectionOpPromise;
466
+ }
467
+ this.connectionOpPromise = (async () => {
468
+ try {
469
+ return await operation();
470
+ } finally {
471
+ this.connectionOpPromise = null;
472
+ }
473
+ })();
474
+ return this.connectionOpPromise;
475
+ }
476
+ async reconnectWithResolvedConnectionString(nextResolvedConnectionString, nextToken) {
477
+ const resolvedToken = nextToken ?? await this.readConnectionTokenFromFile();
478
+ const resolvedConnectionString = nextResolvedConnectionString ?? this.appendTokenToConnectionString(this.config.connectionString, resolvedToken);
479
+ const nextState = await this.createConnectedState(resolvedConnectionString, resolvedToken);
480
+ const previousSqlClient = this.sqlClient;
481
+ this.applyConnectedState(nextState);
482
+ if (previousSqlClient && previousSqlClient !== nextState.sqlClient) {
483
+ await this.shutdownSqlClientSafely(previousSqlClient, "Error shutting down previous database client after reconnect:");
484
+ }
485
+ return nextState.db;
486
+ }
487
+ async shutdownSqlClientSafely(client, errorPrefix) {
488
+ try {
489
+ if (client && typeof client.end === "function") {
490
+ await client.end({
491
+ timeout: this.sqlClientShutdownTimeoutSeconds
492
+ });
493
+ }
494
+ } catch (error) {
495
+ console.error(errorPrefix, error);
496
+ }
445
497
  }
446
498
  /**
447
499
  * 启动 token 文件监听;若已存在旧监听则先清理后重建。
@@ -513,7 +565,7 @@ var DrizzleDatabaseManager = class {
513
565
  if (sameToken && sameConnectionString && this.isConnected) {
514
566
  return;
515
567
  }
516
- await this.reconnect(true);
568
+ await this.reconnectInternal(nextResolvedConnectionString, nextToken);
517
569
  } catch (error) {
518
570
  console.error("Failed to refresh database connection from token file:", error);
519
571
  } finally {
@@ -580,11 +632,10 @@ var DataPaasDatabaseService = class {
580
632
  }
581
633
  /**
582
634
  * 重新连接数据库
583
- * @param forceRefresh 是否强制刷新连接信息
584
635
  * @returns 新的数据库实例
585
636
  */
586
- async reconnect(forceRefresh = false) {
587
- return this.databaseManager.reconnect(forceRefresh);
637
+ async reconnect() {
638
+ return this.databaseManager.reconnect();
588
639
  }
589
640
  /**
590
641
  * 获取当前连接信息
@@ -632,7 +683,20 @@ DataPaasDatabaseService = _ts_decorate2([
632
683
 
633
684
  // src/database/sql-exection-context.middleware.ts
634
685
  var import_common4 = require("@nestjs/common");
635
- var session = __toESM(require("drizzle-orm/postgres-js"), 1);
686
+
687
+ // src/database/drizzle-auth-context.ts
688
+ var import_node_async_hooks = require("async_hooks");
689
+ var asyncLocalStorage = new import_node_async_hooks.AsyncLocalStorage();
690
+ function getAuthContextStore() {
691
+ return asyncLocalStorage.getStore();
692
+ }
693
+ __name(getAuthContextStore, "getAuthContextStore");
694
+ function runWithAuthContext(store, fn) {
695
+ return asyncLocalStorage.run(store, fn);
696
+ }
697
+ __name(runWithAuthContext, "runWithAuthContext");
698
+
699
+ // src/database/sql-exection-context.middleware.ts
636
700
  function _ts_decorate3(decorators, target, key, desc) {
637
701
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
638
702
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -675,7 +739,7 @@ var SqlExecutionContextMiddleware = class {
675
739
  // 用户类型
676
740
  userType ? `SET LOCAL app.user_type = '${userType}'` : `SET LOCAL app.user_type = ''`
677
741
  ].filter(Boolean).join(";");
678
- session.runWithAuthContext({
742
+ runWithAuthContext({
679
743
  preSql: sqls
680
744
  }, () => {
681
745
  next();
@@ -694,6 +758,49 @@ SqlExecutionContextMiddleware = _ts_decorate3([
694
758
  // src/database/database.module.ts
695
759
  var import_nestjs_common3 = require("@lark-apaas/nestjs-common");
696
760
  var import_nestjs_observable = require("@lark-apaas/nestjs-observable");
761
+
762
+ // src/database/drizzle-monkey-patch.ts
763
+ var patched = false;
764
+ function applyDrizzleMonkeyPatch() {
765
+ if (patched) return;
766
+ const pgModule = require("drizzle-orm/postgres-js");
767
+ const PreparedQuery = pgModule.PostgresJsPreparedQuery;
768
+ if (!PreparedQuery?.prototype?.execute) {
769
+ throw new Error("drizzle-orm PostgresJsPreparedQuery.prototype.execute \u4E0D\u5B58\u5728\uFF0C\u7248\u672C\u53EF\u80FD\u4E0D\u517C\u5BB9");
770
+ }
771
+ const originalExecute = PreparedQuery.prototype.execute;
772
+ PreparedQuery.prototype.execute = async function(...args) {
773
+ const authContext = getAuthContextStore();
774
+ const preSql = authContext?.preSql;
775
+ const postSql = authContext?.postSql;
776
+ if (!preSql && !postSql) {
777
+ return originalExecute.apply(this, args);
778
+ }
779
+ const client = this.client;
780
+ if (typeof client.begin === "function") {
781
+ return client.begin(async (tx) => {
782
+ const savedClient = this.client;
783
+ this.client = tx;
784
+ try {
785
+ if (preSql) await tx.unsafe(preSql);
786
+ const result2 = await originalExecute.apply(this, args);
787
+ if (postSql) await tx.unsafe(postSql);
788
+ return result2;
789
+ } finally {
790
+ this.client = savedClient;
791
+ }
792
+ });
793
+ }
794
+ if (preSql) await client.unsafe(preSql);
795
+ const result = await originalExecute.apply(this, args);
796
+ if (postSql) await client.unsafe(postSql);
797
+ return result;
798
+ };
799
+ patched = true;
800
+ }
801
+ __name(applyDrizzleMonkeyPatch, "applyDrizzleMonkeyPatch");
802
+
803
+ // src/database/database.module.ts
697
804
  function _ts_decorate4(decorators, target, key, desc) {
698
805
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
699
806
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -701,20 +808,7 @@ function _ts_decorate4(decorators, target, key, desc) {
701
808
  return c > 3 && r && Object.defineProperty(target, key, r), r;
702
809
  }
703
810
  __name(_ts_decorate4, "_ts_decorate");
704
- function verifyDrizzlePatched() {
705
- try {
706
- const postgres2 = require("drizzle-orm/postgres-js");
707
- const ok = postgres2 && typeof postgres2.getAuthContextStore === "function" && typeof postgres2.runWithAuthContext === "function";
708
- if (!ok) {
709
- throw new Error("drizzle-orm \u672A\u68C0\u6D4B\u5230\u8865\u4E01\u5BFC\u51FA\uFF1AgetAuthContextStore/runWithAuthContext");
710
- }
711
- } catch (err) {
712
- const reason = err instanceof Error ? err.message : String(err);
713
- throw new Error(`drizzle-orm \u8865\u4E01\u672A\u751F\u6548\u6216\u7248\u672C\u4E0D\u5339\u914D\uFF1A${reason}`);
714
- }
715
- }
716
- __name(verifyDrizzlePatched, "verifyDrizzlePatched");
717
- verifyDrizzlePatched();
811
+ applyDrizzleMonkeyPatch();
718
812
  var DataPaasModule = class _DataPaasModule {
719
813
  static {
720
814
  __name(this, "DataPaasModule");