@lobehub/lobehub 2.0.0-next.190 → 2.0.0-next.192

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/e2e/docs/testing-tips.md +30 -0
  4. package/e2e/src/steps/discover/smoke.steps.ts +11 -33
  5. package/locales/zh-CN/auth.json +1 -1
  6. package/package.json +1 -2
  7. package/packages/const/src/utils/merge.test.ts +679 -0
  8. package/packages/context-engine/src/processors/__tests__/AgentCouncilFlatten.test.ts +0 -20
  9. package/packages/context-engine/src/processors/__tests__/MessageContent.test.ts +5 -23
  10. package/packages/context-engine/src/providers/SystemRoleInjector.ts +0 -1
  11. package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentCouncil/simple.json +4 -19
  12. package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentCouncil/with-supervisor-reply.json +4 -23
  13. package/packages/conversation-flow/src/__tests__/fixtures/inputs/agentGroup/speak-different-agent.json +3 -13
  14. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-chain-with-followup.json +3 -8
  15. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/assistant-with-tools.json +21 -17
  16. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/tools-with-branches.json +15 -15
  17. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/active-index-1.json +3 -13
  18. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-branch.json +2 -9
  19. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-group-branches.json +0 -11
  20. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-user-branch.json +5 -15
  21. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/conversation.json +4 -14
  22. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/multi-assistant-group.json +0 -13
  23. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/nested.json +2 -15
  24. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/simple.json +4 -9
  25. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/with-tools.json +8 -17
  26. package/packages/conversation-flow/src/__tests__/fixtures/inputs/linear-conversation.json +3 -7
  27. package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/simple.json +1 -7
  28. package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/with-summary.json +10 -11
  29. package/packages/conversation-flow/src/__tests__/fixtures/outputs/agentCouncil/simple.json +2 -32
  30. package/packages/conversation-flow/src/__tests__/fixtures/outputs/agentCouncil/with-supervisor-reply.json +8 -46
  31. package/packages/conversation-flow/src/__tests__/fixtures/outputs/agentGroup/speak-different-agent.json +5 -24
  32. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-chain-with-followup.json +5 -13
  33. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/assistant-with-tools.json +6 -16
  34. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/tools-with-branches.json +6 -17
  35. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +4 -18
  36. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-branch.json +4 -16
  37. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-group-branches.json +0 -19
  38. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-user-branch.json +8 -24
  39. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +7 -23
  40. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/multi-assistant-group.json +0 -15
  41. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +4 -25
  42. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/simple.json +2 -13
  43. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/with-tools.json +4 -20
  44. package/packages/conversation-flow/src/__tests__/fixtures/outputs/linear-conversation.json +4 -12
  45. package/packages/conversation-flow/src/__tests__/fixtures/outputs/tasks/simple.json +2 -14
  46. package/packages/conversation-flow/src/__tests__/fixtures/outputs/tasks/with-summary.json +20 -22
  47. package/packages/conversation-flow/src/__tests__/indexing.test.ts +0 -35
  48. package/packages/conversation-flow/src/__tests__/structuring.test.ts +0 -41
  49. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +0 -4
  50. package/packages/conversation-flow/src/transformation/__tests__/BranchResolver.test.ts +0 -10
  51. package/packages/conversation-flow/src/transformation/__tests__/ContextTreeBuilder.test.ts +0 -19
  52. package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +0 -37
  53. package/packages/conversation-flow/src/transformation/__tests__/MessageCollector.test.ts +0 -12
  54. package/packages/conversation-flow/src/transformation/__tests__/MessageTransformer.test.ts +0 -2
  55. package/packages/database/package.json +1 -1
  56. package/packages/database/src/client/db.test.ts +19 -144
  57. package/packages/database/src/client/db.ts +26 -234
  58. package/packages/database/src/models/__tests__/_util.ts +19 -3
  59. package/packages/database/src/models/message.ts +0 -1
  60. package/packages/prompts/src/prompts/chatMessages/index.test.ts +0 -1
  61. package/packages/prompts/src/prompts/groupChat/__snapshots__/index.test.ts.snap +0 -21
  62. package/packages/prompts/src/prompts/groupChat/index.test.ts +0 -3
  63. package/packages/types/src/message/ui/chat.ts +0 -2
  64. package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelItem.tsx +8 -7
  65. package/src/features/Conversation/store/slices/data/action.test.ts +0 -14
  66. package/src/features/Conversation/store/slices/data/reducer.test.ts +0 -21
  67. package/src/features/Conversation/store/slices/data/reducer.ts +1 -1
  68. package/src/features/Conversation/store/slices/message/action/crud.test.ts +0 -10
  69. package/src/libs/next/config/define-config.ts +1 -1
  70. package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +3 -5
  71. package/src/server/routers/lambda/__tests__/message.test.ts +1 -2
  72. package/src/server/services/agentRuntime/AgentRuntimeService.ts +109 -109
  73. package/src/server/services/agentRuntime/types.ts +8 -8
  74. package/src/server/services/doc/index.tsx +2 -2
  75. package/src/server/services/generation/index.ts +2 -2
  76. package/src/server/services/message/index.ts +3 -3
  77. package/src/server/services/usage/index.ts +4 -4
  78. package/src/services/chat/chat.test.ts +0 -6
  79. package/src/services/chat/mecha/contextEngineering.test.ts +3 -29
  80. package/src/services/chat/mecha/modelParamsResolver.test.ts +803 -0
  81. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +0 -2
  82. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +0 -6
  83. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +0 -3
  84. package/src/store/chat/agents/createAgentExecutors.ts +4 -4
  85. package/src/store/chat/slices/aiAgent/actions/__tests__/agentGroup.test.ts +0 -2
  86. package/src/store/chat/slices/aiAgent/actions/__tests__/runAgent.test.ts +0 -3
  87. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +0 -1
  88. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +0 -4
  89. package/src/store/chat/slices/message/reducer.test.ts +0 -5
  90. package/src/store/chat/slices/message/reducer.ts +1 -1
  91. package/src/store/chat/slices/message/selectors/displayMessage.test.ts +0 -13
  92. package/src/store/chat/slices/message/selectors/displayMessage.ts +3 -34
  93. package/src/store/chat/slices/portal/selectors.test.ts +0 -7
  94. package/src/store/chat/slices/thread/action.test.ts +0 -1
  95. package/src/store/chat/slices/translate/action.test.ts +0 -1
  96. package/src/store/tool/slices/oldStore/action.test.ts +0 -1
  97. package/packages/database/src/client/pglite.ts +0 -17
  98. package/packages/database/src/client/pglite.worker.ts +0 -25
@@ -1,18 +1,11 @@
1
- import { ClientDBLoadingProgress, DatabaseLoadingState } from '@lobechat/types';
1
+ import { PGlite } from '@electric-sql/pglite';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { DatabaseManager } from './db';
5
-
6
- // Mock 所有外部依赖
7
4
  vi.mock('@electric-sql/pglite', () => ({
8
- default: vi.fn(),
9
- IdbFs: vi.fn(),
10
- PGlite: vi.fn(),
11
- MemoryFS: vi.fn(),
5
+ PGlite: vi.fn(() => ({})),
12
6
  }));
13
7
 
14
8
  vi.mock('@electric-sql/pglite/vector', () => ({
15
- default: vi.fn(),
16
9
  vector: vi.fn(),
17
10
  }));
18
11
 
@@ -24,154 +17,36 @@ vi.mock('drizzle-orm/pglite', () => ({
24
17
  })),
25
18
  }));
26
19
 
27
- let manager: DatabaseManager;
28
- let progressEvents: ClientDBLoadingProgress[] = [];
29
- let stateChanges: DatabaseLoadingState[] = [];
30
-
31
- let callbacks = {
32
- onProgress: vi.fn((progress: ClientDBLoadingProgress) => {
33
- progressEvents.push(progress);
34
- }),
35
- onStateChange: vi.fn((state: DatabaseLoadingState) => {
36
- stateChanges.push(state);
37
- }),
38
- };
39
-
40
20
  beforeEach(() => {
41
21
  vi.clearAllMocks();
42
- progressEvents = [];
43
- stateChanges = [];
44
-
45
- callbacks = {
46
- onProgress: vi.fn((progress: ClientDBLoadingProgress) => {
47
- progressEvents.push(progress);
48
- }),
49
- onStateChange: vi.fn((state: DatabaseLoadingState) => {
50
- stateChanges.push(state);
51
- }),
52
- };
53
- // @ts-expect-error
54
- DatabaseManager['instance'] = undefined;
55
- manager = DatabaseManager.getInstance();
22
+ vi.resetModules();
56
23
  });
57
24
 
58
25
  describe('DatabaseManager', () => {
59
- describe('Callback Handling', () => {
60
- it(
61
- 'should properly track loading states',
62
- async () => {
63
- await manager.initialize(callbacks);
64
-
65
- // 验证状态转换顺序
66
- expect(stateChanges).toEqual([
67
- DatabaseLoadingState.Initializing,
68
- DatabaseLoadingState.LoadingDependencies,
69
- DatabaseLoadingState.LoadingWasm,
70
- DatabaseLoadingState.Migrating,
71
- DatabaseLoadingState.Finished,
72
- DatabaseLoadingState.Ready,
73
- ]);
74
- },
75
- {
76
- timeout: 15000,
77
- },
78
- );
79
-
80
- it('should report dependencies loading progress', async () => {
81
- await manager.initialize(callbacks);
82
-
83
- // 验证依赖加载进度回调
84
- const dependencyProgress = progressEvents.filter((e) => e.phase === 'dependencies');
85
- expect(dependencyProgress.length).toBeGreaterThan(0);
86
- expect(dependencyProgress[dependencyProgress.length - 1]).toEqual(
87
- expect.objectContaining({
88
- phase: 'dependencies',
89
- progress: 100,
90
- costTime: expect.any(Number),
91
- }),
92
- );
93
- });
94
-
95
- it('should report WASM loading progress', async () => {
96
- await manager.initialize(callbacks);
97
-
98
- // 验证 WASM 加载进度回调
99
- const wasmProgress = progressEvents.filter((e) => e.phase === 'wasm');
100
- // expect(wasmProgress.length).toBeGreaterThan(0);
101
- expect(wasmProgress[wasmProgress.length - 1]).toEqual(
102
- expect.objectContaining({
103
- phase: 'wasm',
104
- progress: 100,
105
- costTime: expect.any(Number),
106
- }),
107
- );
108
- });
109
-
110
- it('should handle initialization errors', async () => {
111
- // 模拟加载失败
112
- vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Network error'));
113
-
114
- await expect(manager.initialize(callbacks)).rejects.toThrow();
115
- expect(stateChanges).toContain(DatabaseLoadingState.Error);
116
- });
117
-
118
- it('should only initialize once when called multiple times', async () => {
119
- const firstInit = manager.initialize(callbacks);
120
- const secondInit = manager.initialize(callbacks);
121
-
122
- await Promise.all([firstInit, secondInit]);
123
-
124
- // 验证回调只被调用一次
125
- const readyStateCount = stateChanges.filter(
126
- (state) => state === DatabaseLoadingState.Ready,
127
- ).length;
128
- expect(readyStateCount).toBe(1);
129
- });
130
- });
131
-
132
- describe('Progress Calculation', () => {
133
- it('should report progress between 0 and 100', async () => {
134
- await manager.initialize(callbacks);
135
-
136
- // 验证所有进度值都在有效范围内
137
- progressEvents.forEach((event) => {
138
- expect(event.progress).toBeGreaterThanOrEqual(0);
139
- expect(event.progress).toBeLessThanOrEqual(100);
26
+ describe('initializeDB', () => {
27
+ it('should initialize database with PGlite', async () => {
28
+ const { initializeDB } = await import('./db');
29
+ await initializeDB();
30
+
31
+ expect(PGlite).toHaveBeenCalledWith('idb://lobechat', {
32
+ extensions: { vector: expect.any(Function) },
33
+ relaxedDurability: true,
140
34
  });
141
35
  });
142
36
 
143
- it('should include timing information', async () => {
144
- await manager.initialize(callbacks);
145
-
146
- // 验证最终进度回调包含耗时信息
147
- const finalProgress = progressEvents[progressEvents.length - 1];
148
- expect(finalProgress.costTime).toBeGreaterThan(0);
149
- });
150
- });
151
-
152
- describe('Error Handling', () => {
153
- it('should handle missing callbacks gracefully', async () => {
154
- // 测试没有提供回调的情况
155
- await expect(manager.initialize()).resolves.toBeDefined();
156
- });
37
+ it('should only initialize once when called multiple times', async () => {
38
+ const { initializeDB } = await import('./db');
39
+ await Promise.all([initializeDB(), initializeDB()]);
157
40
 
158
- it('should handle partial callbacks', async () => {
159
- // 只提供部分回调
160
- await expect(manager.initialize({ onProgress: callbacks.onProgress })).resolves.toBeDefined();
161
- await expect(
162
- manager.initialize({ onStateChange: callbacks.onStateChange }),
163
- ).resolves.toBeDefined();
41
+ expect(PGlite).toHaveBeenCalledTimes(1);
164
42
  });
165
43
  });
166
44
 
167
- describe('Database Access', () => {
168
- it('should throw error when accessing database before initialization', () => {
169
- expect(() => manager.db).toThrow('Database not initialized');
170
- });
171
-
45
+ describe('clientDB proxy', () => {
172
46
  it('should provide access to database after initialization', async () => {
173
- await manager.initialize();
174
- expect(() => manager.db).not.toThrow();
47
+ const { clientDB, initializeDB } = await import('./db');
48
+ await initializeDB();
49
+ expect(clientDB).toBeDefined();
175
50
  });
176
51
  });
177
52
  });
@@ -1,53 +1,24 @@
1
- import {
2
- type ClientDBLoadingProgress,
3
- DatabaseLoadingState,
4
- type MigrationSQL,
5
- type MigrationTableItem,
6
- } from '@lobechat/types';
1
+ import { PGlite } from '@electric-sql/pglite';
2
+ import { vector } from '@electric-sql/pglite/vector';
7
3
  import { sql } from 'drizzle-orm';
8
4
  import { PgliteDatabase, drizzle } from 'drizzle-orm/pglite';
9
5
  import { Md5 } from 'ts-md5';
10
6
 
11
- import { sleep } from '@/utils/sleep';
12
-
13
7
  import migrations from '../core/migrations.json';
14
8
  import { DrizzleMigrationModel } from '../models/drizzleMigration';
15
9
  import * as schema from '../schemas';
16
10
 
17
11
  const pgliteSchemaHashCache = 'LOBE_CHAT_PGLITE_SCHEMA_HASH';
18
-
19
12
  const DB_NAME = 'lobechat';
20
- type DrizzleInstance = PgliteDatabase<typeof schema>;
21
13
 
22
- interface onErrorState {
23
- error: Error;
24
- migrationTableItems: MigrationTableItem[];
25
- migrationsSQL: MigrationSQL[];
26
- }
27
-
28
- export interface DatabaseLoadingCallbacks {
29
- onError?: (error: onErrorState) => void;
30
- onProgress?: (progress: ClientDBLoadingProgress) => void;
31
- onStateChange?: (state: DatabaseLoadingState) => void;
32
- }
14
+ type DrizzleInstance = PgliteDatabase<typeof schema>;
33
15
 
34
- export class DatabaseManager {
16
+ class DatabaseManager {
35
17
  private static instance: DatabaseManager;
36
18
  private dbInstance: DrizzleInstance | null = null;
37
19
  private initPromise: Promise<DrizzleInstance> | null = null;
38
- private callbacks?: DatabaseLoadingCallbacks;
39
20
  private isLocalDBSchemaSynced = false;
40
21
 
41
- // CDN configuration
42
- private static WASM_CDN_URL =
43
- 'https://registry.npmmirror.com/@electric-sql/pglite/0.2.17/files/dist/postgres.wasm';
44
-
45
- private static FSBUNDLER_CDN_URL =
46
- 'https://registry.npmmirror.com/@electric-sql/pglite/0.2.17/files/dist/postgres.data';
47
-
48
- private static VECTOR_CDN_URL =
49
- 'https://registry.npmmirror.com/@electric-sql/pglite/0.2.17/files/dist/vector.tar.gz';
50
-
51
22
  private constructor() {}
52
23
 
53
24
  static getInstance() {
@@ -57,108 +28,8 @@ export class DatabaseManager {
57
28
  return DatabaseManager.instance;
58
29
  }
59
30
 
60
- // Load and compile WASM module
61
- private async loadWasmModule(): Promise<WebAssembly.Module> {
62
- const start = Date.now();
63
- this.callbacks?.onStateChange?.(DatabaseLoadingState.LoadingWasm);
64
-
65
- const response = await fetch(DatabaseManager.WASM_CDN_URL);
66
-
67
- const contentLength = Number(response.headers.get('Content-Length')) || 0;
68
- const reader = response.body?.getReader();
69
-
70
- if (!reader) throw new Error('Failed to start WASM download');
71
-
72
- let receivedLength = 0;
73
- const chunks: Uint8Array[] = [];
74
-
75
- // Read data stream
76
- // eslint-disable-next-line no-constant-condition
77
- while (true) {
78
- const { done, value } = await reader.read();
79
-
80
- if (done) break;
81
-
82
- chunks.push(value);
83
- receivedLength += value.length;
84
-
85
- // Calculate and report progress
86
- const progress = Math.min(Math.round((receivedLength / contentLength) * 100), 100);
87
- this.callbacks?.onProgress?.({
88
- phase: 'wasm',
89
- progress,
90
- });
91
- }
92
-
93
- // Merge data chunks
94
- const wasmBytes = new Uint8Array(receivedLength);
95
- let position = 0;
96
- for (const chunk of chunks) {
97
- wasmBytes.set(chunk, position);
98
- position += chunk.length;
99
- }
100
-
101
- this.callbacks?.onProgress?.({
102
- costTime: Date.now() - start,
103
- phase: 'wasm',
104
- progress: 100,
105
- });
106
-
107
- // Compile WASM module
108
- return WebAssembly.compile(wasmBytes);
109
- }
110
-
111
- private fetchFsBundle = async () => {
112
- const res = await fetch(DatabaseManager.FSBUNDLER_CDN_URL);
113
-
114
- return await res.blob();
115
- };
116
-
117
- // Asynchronously load PGlite related dependencies
118
- private async loadDependencies() {
119
- const start = Date.now();
120
- this.callbacks?.onStateChange?.(DatabaseLoadingState.LoadingDependencies);
121
-
122
- const imports = [
123
- import('@electric-sql/pglite').then((m) => ({
124
- IdbFs: m.IdbFs,
125
- MemoryFS: m.MemoryFS,
126
- PGlite: m.PGlite,
127
- })),
128
- import('@electric-sql/pglite/vector'),
129
- this.fetchFsBundle(),
130
- ];
131
-
132
- let loaded = 0;
133
- const results = await Promise.all(
134
- imports.map(async (importPromise) => {
135
- const result = await importPromise;
136
- loaded += 1;
137
-
138
- // Calculate loading progress
139
- this.callbacks?.onProgress?.({
140
- phase: 'dependencies',
141
- progress: Math.min(Math.round((loaded / imports.length) * 100), 100),
142
- });
143
- return result;
144
- }),
145
- );
146
-
147
- this.callbacks?.onProgress?.({
148
- costTime: Date.now() - start,
149
- phase: 'dependencies',
150
- progress: 100,
151
- });
152
-
153
- // @ts-ignore
154
- const [{ PGlite, IdbFs, MemoryFS }, { vector }, fsBundle] = results;
155
-
156
- return { IdbFs, MemoryFS, PGlite, fsBundle, vector };
157
- }
158
-
159
- // Database migration method
160
- private async migrate(skipMultiRun = false): Promise<DrizzleInstance> {
161
- if (this.isLocalDBSchemaSynced && skipMultiRun) return this.db;
31
+ private async migrate(): Promise<DrizzleInstance> {
32
+ if (this.isLocalDBSchemaSynced) return this.db;
162
33
 
163
34
  let hash: string | undefined;
164
35
  if (typeof localStorage !== 'undefined') {
@@ -179,17 +50,13 @@ export class DatabaseManager {
179
50
  }
180
51
  } catch (error) {
181
52
  console.warn('Error checking table existence, proceeding with migration', error);
182
- // If query fails, continue migration to ensure safety
183
53
  }
184
54
  }
185
55
  }
186
56
 
187
57
  const start = Date.now();
188
58
  try {
189
- this.callbacks?.onStateChange?.(DatabaseLoadingState.Migrating);
190
-
191
- // refs: https://github.com/drizzle-team/drizzle-orm/discussions/2532
192
- // @ts-expect-error
59
+ // @ts-expect-error - migrate internal API
193
60
  await this.db.dialect.migrate(migrations, this.db.session, {});
194
61
 
195
62
  if (typeof localStorage !== 'undefined' && hash) {
@@ -197,7 +64,6 @@ export class DatabaseManager {
197
64
  }
198
65
 
199
66
  this.isLocalDBSchemaSynced = true;
200
-
201
67
  console.info(`🗂 Migration success, take ${Date.now() - start}ms`);
202
68
  } catch (cause) {
203
69
  console.error('❌ Local database schema migration failed', cause);
@@ -207,95 +73,32 @@ export class DatabaseManager {
207
73
  return this.db;
208
74
  }
209
75
 
210
- // Initialize database
211
- async initialize(callbacks?: DatabaseLoadingCallbacks): Promise<DrizzleInstance> {
76
+ async initialize(): Promise<DrizzleInstance> {
212
77
  if (this.initPromise) return this.initPromise;
213
78
 
214
- this.callbacks = callbacks;
215
-
216
79
  this.initPromise = (async () => {
217
- try {
218
- if (this.dbInstance) return this.dbInstance;
219
-
220
- const time = Date.now();
221
- // Initialize database
222
- this.callbacks?.onStateChange?.(DatabaseLoadingState.Initializing);
223
-
224
- // Load dependencies
225
- const { fsBundle, PGlite, MemoryFS, IdbFs, vector } = await this.loadDependencies();
226
-
227
- // Load and compile WASM module
228
- const wasmModule = await this.loadWasmModule();
229
-
230
- const { initPgliteWorker } = await import('./pglite');
231
-
232
- let db: typeof PGlite;
233
-
234
- // make db as web worker if worker is available
235
- // https://github.com/lobehub/lobe-chat/issues/5785
236
- if (typeof Worker !== 'undefined' && typeof navigator.locks !== 'undefined') {
237
- db = await initPgliteWorker({
238
- dbName: DB_NAME,
239
- fsBundle: fsBundle as Blob,
240
- vectorBundlePath: DatabaseManager.VECTOR_CDN_URL,
241
- wasmModule,
242
- });
243
- } else {
244
- // in edge runtime or test runtime, we don't have worker
245
- db = new PGlite({
246
- extensions: { vector },
247
- fs: typeof window === 'undefined' ? new MemoryFS(DB_NAME) : new IdbFs(DB_NAME),
248
- relaxedDurability: true,
249
- wasmModule,
250
- });
251
- }
252
-
253
- this.dbInstance = drizzle({ client: db, schema });
80
+ if (this.dbInstance) return this.dbInstance;
254
81
 
255
- await this.migrate(true);
82
+ const time = Date.now();
256
83
 
257
- this.callbacks?.onStateChange?.(DatabaseLoadingState.Finished);
258
- console.log(`✅ Database initialized in ${Date.now() - time}ms`);
259
-
260
- await sleep(50);
84
+ // 直接使用 pglite,自动处理 wasm 加载
85
+ const pglite = new PGlite(`idb://${DB_NAME}`, {
86
+ extensions: { vector },
87
+ relaxedDurability: true,
88
+ });
261
89
 
262
- this.callbacks?.onStateChange?.(DatabaseLoadingState.Ready);
90
+ this.dbInstance = drizzle({ client: pglite, schema });
263
91
 
264
- return this.dbInstance as DrizzleInstance;
265
- } catch (e) {
266
- this.initPromise = null;
267
- this.callbacks?.onStateChange?.(DatabaseLoadingState.Error);
268
- const error = e as Error;
92
+ await this.migrate();
269
93
 
270
- // Query migration table data
271
- let migrationsTableData: MigrationTableItem[] = [];
272
- try {
273
- // Attempt to query migration table
274
- const drizzleMigration = new DrizzleMigrationModel(this.db as any);
275
- migrationsTableData = await drizzleMigration.getMigrationList();
276
- } catch (queryError) {
277
- console.error('Failed to query migrations table:', queryError);
278
- }
94
+ console.log(`✅ Database initialized in ${Date.now() - time}ms`);
279
95
 
280
- this.callbacks?.onError?.({
281
- error: {
282
- message: error.message,
283
- name: error.name,
284
- stack: error.stack,
285
- },
286
- migrationTableItems: migrationsTableData,
287
- migrationsSQL: migrations,
288
- });
289
-
290
- console.error(error);
291
- throw error;
292
- }
96
+ return this.dbInstance;
293
97
  })();
294
98
 
295
99
  return this.initPromise;
296
100
  }
297
101
 
298
- // Get database instance
299
102
  get db(): DrizzleInstance {
300
103
  if (!this.dbInstance) {
301
104
  throw new Error('Database not initialized. Please call initialize() first.');
@@ -303,7 +106,6 @@ export class DatabaseManager {
303
106
  return this.dbInstance;
304
107
  }
305
108
 
306
- // Create proxy object
307
109
  createProxy(): DrizzleInstance {
308
110
  return new Proxy({} as DrizzleInstance, {
309
111
  get: (target, prop) => {
@@ -313,7 +115,7 @@ export class DatabaseManager {
313
115
  }
314
116
 
315
117
  async resetDatabase(): Promise<void> {
316
- // 1. Close existing PGlite connection (if exists)
118
+ // 1. Close existing PGlite connection
317
119
  if (this.dbInstance) {
318
120
  try {
319
121
  // @ts-ignore
@@ -321,31 +123,28 @@ export class DatabaseManager {
321
123
  console.log('PGlite instance closed successfully.');
322
124
  } catch (e) {
323
125
  console.error('Error closing PGlite instance:', e);
324
- // Even if closing fails, continue with deletion attempt; IndexedDB onblocked or onerror will handle subsequent issues
325
126
  }
326
127
  }
327
128
 
328
129
  // 2. Reset database instance and initialization state
329
130
  this.dbInstance = null;
330
131
  this.initPromise = null;
331
- this.isLocalDBSchemaSynced = false; // Reset sync state
132
+ this.isLocalDBSchemaSynced = false;
332
133
 
333
134
  // 3. Delete IndexedDB database
334
135
  return new Promise<void>((resolve, reject) => {
335
- // Check if IndexedDB is available
336
136
  if (typeof indexedDB === 'undefined') {
337
137
  console.warn('IndexedDB is not available, cannot delete database');
338
- resolve(); // Cannot delete in this environment, resolve directly
138
+ resolve();
339
139
  return;
340
140
  }
341
141
 
342
- const dbName = `/pglite/${DB_NAME}`; // Path used by PGlite IdbFs
142
+ const dbName = `/pglite/${DB_NAME}`;
343
143
  const request = indexedDB.deleteDatabase(dbName);
344
144
 
345
145
  request.onsuccess = () => {
346
146
  console.log(`✅ Database '${dbName}' reset successfully`);
347
147
 
348
- // Clear locally stored schema hash
349
148
  if (typeof localStorage !== 'undefined') {
350
149
  localStorage.removeItem(pgliteSchemaHashCache);
351
150
  }
@@ -365,14 +164,10 @@ export class DatabaseManager {
365
164
  };
366
165
 
367
166
  request.onblocked = (event) => {
368
- // This event is triggered when other open connections block database deletion
369
- console.warn(
370
- `Deletion of database '${dbName}' is blocked. This usually means other connections (e.g., in other tabs) are still open. Event:`,
371
- event,
372
- );
167
+ console.warn(`Deletion of database '${dbName}' is blocked.`, event);
373
168
  reject(
374
169
  new Error(
375
- `Failed to reset database '${dbName}' because it is blocked by other open connections. Please close other tabs or applications using this database and try again.`,
170
+ `Failed to reset database '${dbName}' because it is blocked by other open connections.`,
376
171
  ),
377
172
  );
378
173
  };
@@ -383,12 +178,9 @@ export class DatabaseManager {
383
178
  // Export singleton
384
179
  const dbManager = DatabaseManager.getInstance();
385
180
 
386
- // Keep original clientDB export unchanged
387
181
  export const clientDB = dbManager.createProxy();
388
182
 
389
- // Export initialization method for application startup
390
- export const initializeDB = (callbacks?: DatabaseLoadingCallbacks) =>
391
- dbManager.initialize(callbacks);
183
+ export const initializeDB = () => dbManager.initialize();
392
184
 
393
185
  export const resetClientDatabase = async () => {
394
186
  await dbManager.resetDatabase();
@@ -1,14 +1,30 @@
1
- import { clientDB, initializeDB } from '../../client/db';
1
+ import { PGlite } from '@electric-sql/pglite';
2
+ import { vector } from '@electric-sql/pglite/vector';
3
+ import { drizzle } from 'drizzle-orm/pglite';
4
+
5
+ import migrations from '../../core/migrations.json';
6
+ import * as schema from '../../schemas';
2
7
  import { LobeChatDatabase } from '../../type';
3
8
 
4
9
  const isServerDBMode = process.env.TEST_SERVER_DB === '1';
5
10
 
11
+ let testClientDB: ReturnType<typeof drizzle<typeof schema>> | null = null;
12
+
6
13
  export const getTestDB = async () => {
7
14
  if (isServerDBMode) {
8
15
  const { getTestDBInstance } = await import('../../core/dbForTest');
9
16
  return await getTestDBInstance();
10
17
  }
11
18
 
12
- await initializeDB();
13
- return clientDB as LobeChatDatabase;
19
+ if (testClientDB) return testClientDB as unknown as LobeChatDatabase;
20
+
21
+ // 直接使用 pglite 内置资源,不需要从 CDN 下载
22
+ const pglite = new PGlite({ extensions: { vector } });
23
+
24
+ testClientDB = drizzle({ client: pglite, schema });
25
+
26
+ // @ts-expect-error - migrate internal API
27
+ await testClientDB.dialect.migrate(migrations, testClientDB.session, {});
28
+
29
+ return testClientDB as unknown as LobeChatDatabase;
14
30
  };
@@ -485,7 +485,6 @@ export class MessageModel {
485
485
  .filter((relation) => relation.messageId === item.id)
486
486
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
487
487
  .map<ChatImageItem>(({ id, url, name }) => ({ alt: name!, id, url })),
488
- meta: {},
489
488
 
490
489
  model,
491
490
 
@@ -15,7 +15,6 @@ const createMessage = (
15
15
  id: overrides.id ?? `msg-${++messageCounter}`,
16
16
  createdAt: overrides.createdAt ?? 0,
17
17
  updatedAt: overrides.updatedAt ?? 0,
18
- meta: overrides.meta ?? {},
19
18
  ...overrides,
20
19
  });
21
20