@lobehub/lobehub 2.0.0-next.126 → 2.0.0-next.128

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 (48) hide show
  1. package/.env.example +23 -3
  2. package/.env.example.development +5 -0
  3. package/CHANGELOG.md +50 -0
  4. package/changelog/v1.json +18 -0
  5. package/docker-compose/local/docker-compose.yml +24 -1
  6. package/docker-compose/local/logto/docker-compose.yml +25 -2
  7. package/docker-compose.development.yml +6 -0
  8. package/locales/ar/auth.json +114 -1
  9. package/locales/bg-BG/auth.json +114 -1
  10. package/locales/de-DE/auth.json +114 -1
  11. package/locales/en-US/auth.json +42 -22
  12. package/locales/es-ES/auth.json +114 -1
  13. package/locales/fa-IR/auth.json +114 -1
  14. package/locales/fr-FR/auth.json +114 -1
  15. package/locales/it-IT/auth.json +114 -1
  16. package/locales/ja-JP/auth.json +114 -1
  17. package/locales/ko-KR/auth.json +114 -1
  18. package/locales/nl-NL/auth.json +114 -1
  19. package/locales/pl-PL/auth.json +114 -1
  20. package/locales/pt-BR/auth.json +114 -1
  21. package/locales/ru-RU/auth.json +114 -1
  22. package/locales/tr-TR/auth.json +114 -1
  23. package/locales/vi-VN/auth.json +114 -1
  24. package/locales/zh-CN/auth.json +36 -29
  25. package/locales/zh-TW/auth.json +114 -1
  26. package/package.json +4 -1
  27. package/packages/database/src/client/db.ts +21 -21
  28. package/packages/database/src/repositories/dataImporter/deprecated/index.ts +5 -5
  29. package/packages/database/src/repositories/dataImporter/index.ts +59 -59
  30. package/packages/database/src/schemas/generation.ts +16 -16
  31. package/packages/database/src/schemas/oidc.ts +36 -36
  32. package/packages/model-runtime/src/providers/newapi/index.ts +61 -18
  33. package/packages/model-runtime/src/runtimeMap.ts +1 -0
  34. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/UpdateProviderInfo/SettingModal.tsx +10 -6
  35. package/src/envs/auth.test.ts +60 -0
  36. package/src/envs/auth.ts +3 -3
  37. package/src/envs/redis.ts +106 -0
  38. package/src/libs/redis/index.ts +5 -0
  39. package/src/libs/redis/manager.test.ts +107 -0
  40. package/src/libs/redis/manager.ts +56 -0
  41. package/src/libs/redis/redis.test.ts +158 -0
  42. package/src/libs/redis/redis.ts +117 -0
  43. package/src/libs/redis/types.ts +71 -0
  44. package/src/libs/redis/upstash.test.ts +154 -0
  45. package/src/libs/redis/upstash.ts +109 -0
  46. package/src/libs/redis/utils.test.ts +46 -0
  47. package/src/libs/redis/utils.ts +53 -0
  48. package/.github/workflows/check-console-log.yml +0 -117
@@ -207,7 +207,7 @@ export class DatabaseManager {
207
207
  return this.db;
208
208
  }
209
209
 
210
- // 初始化数据库
210
+ // Initialize database
211
211
  async initialize(callbacks?: DatabaseLoadingCallbacks): Promise<DrizzleInstance> {
212
212
  if (this.initPromise) return this.initPromise;
213
213
 
@@ -218,13 +218,13 @@ export class DatabaseManager {
218
218
  if (this.dbInstance) return this.dbInstance;
219
219
 
220
220
  const time = Date.now();
221
- // 初始化数据库
221
+ // Initialize database
222
222
  this.callbacks?.onStateChange?.(DatabaseLoadingState.Initializing);
223
223
 
224
- // 加载依赖
224
+ // Load dependencies
225
225
  const { fsBundle, PGlite, MemoryFS, IdbFs, vector } = await this.loadDependencies();
226
226
 
227
- // 加载并编译 WASM 模块
227
+ // Load and compile WASM module
228
228
  const wasmModule = await this.loadWasmModule();
229
229
 
230
230
  const { initPgliteWorker } = await import('./pglite');
@@ -267,10 +267,10 @@ export class DatabaseManager {
267
267
  this.callbacks?.onStateChange?.(DatabaseLoadingState.Error);
268
268
  const error = e as Error;
269
269
 
270
- // 查询迁移表数据
270
+ // Query migration table data
271
271
  let migrationsTableData: MigrationTableItem[] = [];
272
272
  try {
273
- // 尝试查询迁移表
273
+ // Attempt to query migration table
274
274
  const drizzleMigration = new DrizzleMigrationModel(this.db as any);
275
275
  migrationsTableData = await drizzleMigration.getMigrationList();
276
276
  } catch (queryError) {
@@ -295,7 +295,7 @@ export class DatabaseManager {
295
295
  return this.initPromise;
296
296
  }
297
297
 
298
- // 获取数据库实例
298
+ // Get database instance
299
299
  get db(): DrizzleInstance {
300
300
  if (!this.dbInstance) {
301
301
  throw new Error('Database not initialized. Please call initialize() first.');
@@ -303,7 +303,7 @@ export class DatabaseManager {
303
303
  return this.dbInstance;
304
304
  }
305
305
 
306
- // 创建代理对象
306
+ // Create proxy object
307
307
  createProxy(): DrizzleInstance {
308
308
  return new Proxy({} as DrizzleInstance, {
309
309
  get: (target, prop) => {
@@ -313,7 +313,7 @@ export class DatabaseManager {
313
313
  }
314
314
 
315
315
  async resetDatabase(): Promise<void> {
316
- // 1. 关闭现有的 PGlite 连接(如果存在)
316
+ // 1. Close existing PGlite connection (if exists)
317
317
  if (this.dbInstance) {
318
318
  try {
319
319
  // @ts-ignore
@@ -321,31 +321,31 @@ export class DatabaseManager {
321
321
  console.log('PGlite instance closed successfully.');
322
322
  } catch (e) {
323
323
  console.error('Error closing PGlite instance:', e);
324
- // 即使关闭失败,也尝试继续删除,IndexedDB onblocked onerror 会处理后续问题
324
+ // Even if closing fails, continue with deletion attempt; IndexedDB onblocked or onerror will handle subsequent issues
325
325
  }
326
326
  }
327
327
 
328
- // 2. 重置数据库实例和初始化状态
328
+ // 2. Reset database instance and initialization state
329
329
  this.dbInstance = null;
330
330
  this.initPromise = null;
331
- this.isLocalDBSchemaSynced = false; // 重置同步状态
331
+ this.isLocalDBSchemaSynced = false; // Reset sync state
332
332
 
333
- // 3. 删除 IndexedDB 数据库
333
+ // 3. Delete IndexedDB database
334
334
  return new Promise<void>((resolve, reject) => {
335
- // 检查 IndexedDB 是否可用
335
+ // Check if IndexedDB is available
336
336
  if (typeof indexedDB === 'undefined') {
337
337
  console.warn('IndexedDB is not available, cannot delete database');
338
- resolve(); // 在此环境下无法删除,直接解决
338
+ resolve(); // Cannot delete in this environment, resolve directly
339
339
  return;
340
340
  }
341
341
 
342
- const dbName = `/pglite/${DB_NAME}`; // PGlite IdbFs 使用的路径
342
+ const dbName = `/pglite/${DB_NAME}`; // Path used by PGlite IdbFs
343
343
  const request = indexedDB.deleteDatabase(dbName);
344
344
 
345
345
  request.onsuccess = () => {
346
346
  console.log(`✅ Database '${dbName}' reset successfully`);
347
347
 
348
- // 清除本地存储的模式哈希
348
+ // Clear locally stored schema hash
349
349
  if (typeof localStorage !== 'undefined') {
350
350
  localStorage.removeItem(pgliteSchemaHashCache);
351
351
  }
@@ -365,7 +365,7 @@ export class DatabaseManager {
365
365
  };
366
366
 
367
367
  request.onblocked = (event) => {
368
- // 当其他打开的连接阻止数据库删除时,会触发此事件
368
+ // This event is triggered when other open connections block database deletion
369
369
  console.warn(
370
370
  `Deletion of database '${dbName}' is blocked. This usually means other connections (e.g., in other tabs) are still open. Event:`,
371
371
  event,
@@ -380,13 +380,13 @@ export class DatabaseManager {
380
380
  }
381
381
  }
382
382
 
383
- // 导出单例
383
+ // Export singleton
384
384
  const dbManager = DatabaseManager.getInstance();
385
385
 
386
- // 保持原有的 clientDB 导出不变
386
+ // Keep original clientDB export unchanged
387
387
  export const clientDB = dbManager.createProxy();
388
388
 
389
- // 导出初始化方法,供应用启动时使用
389
+ // Export initialization method for application startup
390
390
  export const initializeDB = (callbacks?: DatabaseLoadingCallbacks) =>
391
391
  dbManager.initialize(callbacks);
392
392
 
@@ -127,7 +127,7 @@ export class DeprecatedDataImporterRepos {
127
127
  // filter out existing session, only insert new ones
128
128
  .filter((s) => query.every((q) => q.clientId !== s.id));
129
129
 
130
- // 只有当需要有新的 session 时,才会插入 agent
130
+ // Only insert agent when new sessions are needed
131
131
  if (shouldInsertSessionAgents.length > 0) {
132
132
  const agentMapArray = await trx
133
133
  .insert(agents)
@@ -221,14 +221,14 @@ export class DeprecatedDataImporterRepos {
221
221
  parentId: null,
222
222
  provider: extra?.fromProvider,
223
223
  sessionId: sessionId ? sessionIdMap[sessionId] : null,
224
- topicId: topicId ? topicIdMap[topicId] : null, // 暂时设为 NULL
224
+ topicId: topicId ? topicIdMap[topicId] : null, // Temporarily set to NULL
225
225
  updatedAt: new Date(updatedAt),
226
226
  userId: this.userId,
227
227
  }),
228
228
  );
229
229
 
230
230
  console.time('insert messages');
231
- const BATCH_SIZE = 100; // 每批次插入的记录数
231
+ const BATCH_SIZE = 100; // Number of records to insert per batch
232
232
 
233
233
  for (let i = 0; i < inertValues.length; i += BATCH_SIZE) {
234
234
  const batch = inertValues.slice(i, i + BATCH_SIZE);
@@ -257,7 +257,7 @@ export class DeprecatedDataImporterRepos {
257
257
  // 3. update parentId for messages
258
258
  console.time('execute updates parentId');
259
259
  const parentIdUpdates = shouldInsertMessages
260
- .filter((msg) => msg.parentId) // 只处理有 parentId 的消息
260
+ .filter((msg) => msg.parentId) // Only process messages with parentId
261
261
  .map((msg) => {
262
262
  if (messageIdMap[msg.parentId as string])
263
263
  return sql`WHEN ${messages.clientId} = ${msg.id} THEN ${messageIdMap[msg.parentId as string]} `;
@@ -315,7 +315,7 @@ export class DeprecatedDataImporterRepos {
315
315
  );
316
316
  }
317
317
 
318
- // TODO: 未来需要处理 TTS 和图片的插入 (目前存在 file 的部分,不方便处理)
318
+ // TODO: Need to handle TTS and image insertion in the future (currently difficult to handle due to file-related parts)
319
319
  }
320
320
 
321
321
  messageResult.added = shouldInsertMessages.length;
@@ -67,7 +67,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
67
67
  },
68
68
  {
69
69
  conflictStrategy: 'skip',
70
- preserveId: true, // 需要保留原始ID
70
+ preserveId: true, // Need to preserve original ID
71
71
  relations: [
72
72
  {
73
73
  field: 'providerId',
@@ -89,7 +89,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
89
89
  uniqueConstraints: ['slug'],
90
90
  },
91
91
  {
92
- // slug字段进行特殊处理
92
+ // Special processing for slug field
93
93
  fieldProcessors: {
94
94
  slug: (value) => `${value}-${uuid().slice(0, 8)}`,
95
95
  },
@@ -113,7 +113,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
113
113
  },
114
114
  {
115
115
  conflictStrategy: 'skip',
116
- isCompositeKey: true, // 使用复合主键 [agentId, sessionId]
116
+ isCompositeKey: true, // Uses composite primary key [agentId, sessionId]
117
117
  relations: [
118
118
  {
119
119
  field: 'agentId',
@@ -172,7 +172,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
172
172
  },
173
173
  {
174
174
  conflictStrategy: 'skip',
175
- preserveId: true, // 使用消息ID作为主键
175
+ preserveId: true, // Uses message ID as primary key
176
176
  relations: [
177
177
  {
178
178
  field: 'id',
@@ -182,7 +182,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
182
182
  table: 'messagePlugins',
183
183
  },
184
184
  {
185
- isCompositeKey: true, // 使用复合主键 [messageId, chunkId]
185
+ isCompositeKey: true, // Uses composite primary key [messageId, chunkId]
186
186
  relations: [
187
187
  {
188
188
  field: 'messageId',
@@ -196,7 +196,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
196
196
  table: 'messageChunks',
197
197
  },
198
198
  {
199
- isCompositeKey: true, // 使用复合主键 [id, queryId, chunkId]
199
+ isCompositeKey: true, // Uses composite primary key [id, queryId, chunkId]
200
200
  relations: [
201
201
  {
202
202
  field: 'id',
@@ -228,7 +228,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
228
228
  // },
229
229
  {
230
230
  conflictStrategy: 'skip',
231
- preserveId: true, // 使用消息ID作为主键
231
+ preserveId: true, // Uses message ID as primary key
232
232
  relations: [
233
233
  {
234
234
  field: 'id',
@@ -239,7 +239,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
239
239
  },
240
240
  // {
241
241
  // conflictStrategy: 'skip',
242
- // preserveId: true, // 使用消息ID作为主键
242
+ // preserveId: true, // Uses message ID as primary key
243
243
  // relations: [
244
244
  // {
245
245
  // field: 'id',
@@ -273,7 +273,7 @@ export class DataImporterRepos {
273
273
  };
274
274
 
275
275
  /**
276
- * 导入PostgreSQL数据
276
+ * Import PostgreSQL data
277
277
  */
278
278
  async importPgData(
279
279
  dbData: ImportPgDataStructure,
@@ -282,13 +282,13 @@ export class DataImporterRepos {
282
282
  const results: Record<string, ImportResult> = {};
283
283
  const { data } = dbData;
284
284
 
285
- // 初始化ID映射表和冲突记录
285
+ // Initialize ID mapping table and conflict records
286
286
  this.idMaps = {};
287
287
  this.conflictRecords = {};
288
288
 
289
289
  try {
290
290
  await this.db.transaction(async (trx) => {
291
- // 按配置顺序导入表
291
+ // Import tables in configuration order
292
292
  for (const config of IMPORT_TABLE_CONFIG) {
293
293
  const { table: tableName } = config;
294
294
 
@@ -299,7 +299,7 @@ export class DataImporterRepos {
299
299
  continue;
300
300
  }
301
301
 
302
- // 使用统一的导入方法
302
+ // Use unified import method
303
303
  const result = await this.importTableData(trx, config, tableData, conflictStrategy);
304
304
  console.log(`imported table: ${tableName}, records: ${tableData.length}`);
305
305
 
@@ -325,11 +325,11 @@ export class DataImporterRepos {
325
325
  }
326
326
 
327
327
  /**
328
- * 从错误中提取详细信息
328
+ * Extract detailed information from error
329
329
  */
330
330
  private extractErrorDetails(error: any) {
331
331
  if (error.code === '23505') {
332
- // PostgreSQL 唯一约束错误码
332
+ // PostgreSQL unique constraint error code
333
333
  const match = error.detail?.match(/Key \((.+?)\)=\((.+?)\) already exists/);
334
334
  if (match) {
335
335
  return {
@@ -344,7 +344,7 @@ export class DataImporterRepos {
344
344
  }
345
345
 
346
346
  /**
347
- * 统一的表数据导入函数 - 处理所有类型的表
347
+ * Unified table data import function - Handles all types of tables
348
348
  */
349
349
  private async importTableData(
350
350
  trx: any,
@@ -368,13 +368,13 @@ export class DataImporterRepos {
368
368
  const table = EXPORT_TABLES[tableName];
369
369
  const result: ImportResult = { added: 0, errors: 0, skips: 0, updated: 0 };
370
370
 
371
- // 初始化该表的ID映射
371
+ // Initialize ID mapping for this table
372
372
  if (!this.idMaps[tableName]) {
373
373
  this.idMaps[tableName] = {};
374
374
  }
375
375
 
376
376
  try {
377
- // 1. 查找已存在的记录(基于clientIduserId
377
+ // 1. Find existing records (based on clientId and userId)
378
378
  let existingRecords: any[] = [];
379
379
 
380
380
  if ('clientId' in table && 'userId' in table) {
@@ -387,7 +387,7 @@ export class DataImporterRepos {
387
387
  }
388
388
  }
389
389
 
390
- // 如果需要保留原始ID,还需要检查ID是否已存在
390
+ // If need to preserve original ID, also check if ID already exists
391
391
  if (preserveId && !isCompositeKey) {
392
392
  const ids = tableData.map((item) => item.id).filter(Boolean);
393
393
  if (ids.length > 0) {
@@ -395,7 +395,7 @@ export class DataImporterRepos {
395
395
  where: inArray(table.id, ids),
396
396
  });
397
397
 
398
- // 合并到已存在记录集合中
398
+ // Merge into existing records set
399
399
  existingRecords = [
400
400
  ...existingRecords,
401
401
  ...idExistingRecords.filter(
@@ -407,28 +407,28 @@ export class DataImporterRepos {
407
407
 
408
408
  result.skips = existingRecords.length;
409
409
 
410
- // 2. 为已存在的记录建立ID映射
410
+ // 2. Establish ID mapping for existing records
411
411
  for (const record of existingRecords) {
412
- // 只有非复合主键表才需要ID映射
412
+ // Only non-composite key tables need ID mapping
413
413
  if (!isCompositeKey) {
414
414
  this.idMaps[tableName][record.id] = record.id;
415
415
  if (record.clientId) {
416
416
  this.idMaps[tableName][record.clientId] = record.id;
417
417
  }
418
418
 
419
- // 记录中可能使用的任何其他ID标识符
419
+ // Any other ID identifiers that may be used in records
420
420
  const originalRecord = tableData.find(
421
421
  (item) => item.id === record.id || item.clientId === record.clientId,
422
422
  );
423
423
 
424
424
  if (originalRecord) {
425
- // 确保原始记录ID也映射到数据库记录ID
425
+ // Ensure original record ID also maps to database record ID
426
426
  this.idMaps[tableName][originalRecord.id] = record.id;
427
427
  }
428
428
  }
429
429
  }
430
430
 
431
- // 3. 筛选出需要插入的记录
431
+ // 3. Filter out records that need to be inserted
432
432
  const recordsToInsert = tableData.filter(
433
433
  (item) =>
434
434
  !existingRecords.some(
@@ -442,22 +442,22 @@ export class DataImporterRepos {
442
442
  return result;
443
443
  }
444
444
 
445
- // 4. 准备导入数据
445
+ // 4. Prepare import data
446
446
  const preparedData = recordsToInsert.map((item) => {
447
447
  const originalId = item.id;
448
448
 
449
- // 处理日期字段
449
+ // Process date fields
450
450
  const dateFields: any = {};
451
451
  if (item.createdAt) dateFields.createdAt = new Date(item.createdAt);
452
452
  if (item.updatedAt) dateFields.updatedAt = new Date(item.updatedAt);
453
453
  if (item.accessedAt) dateFields.accessedAt = new Date(item.accessedAt);
454
454
 
455
- // 创建新记录对象
455
+ // Create new record object
456
456
  let newRecord: any = {};
457
457
 
458
- // 根据是否复合主键和是否保留ID决定如何处理
458
+ // Decide how to process based on whether it's composite key and whether to preserve ID
459
459
  if (isCompositeKey) {
460
- // 对于复合主键表,不包含id字段
460
+ // For composite key tables, don't include id field
461
461
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
462
462
  const { id: _, ...rest } = item;
463
463
  newRecord = {
@@ -467,7 +467,7 @@ export class DataImporterRepos {
467
467
  userId: this.userId,
468
468
  };
469
469
  } else {
470
- // 非复合主键表处理
470
+ // Non-composite key table processing
471
471
  newRecord = {
472
472
  ...(preserveId ? item : { ...item, id: undefined }),
473
473
  ...dateFields,
@@ -476,19 +476,19 @@ export class DataImporterRepos {
476
476
  };
477
477
  }
478
478
 
479
- // 应用字段处理器
479
+ // Apply field processors
480
480
  for (const field in fieldProcessors) {
481
481
  if (newRecord[field] !== undefined) {
482
482
  newRecord[field] = fieldProcessors[field](newRecord[field]);
483
483
  }
484
484
  }
485
485
 
486
- // 特殊表处理
486
+ // Special table processing
487
487
  if (tableName === 'userSettings') {
488
488
  newRecord.id = this.userId;
489
489
  }
490
490
 
491
- // 处理关系字段(外键引用)
491
+ // Process relation fields (foreign key references)
492
492
  for (const relation of relations) {
493
493
  const { field, sourceTable } = relation;
494
494
 
@@ -498,7 +498,7 @@ export class DataImporterRepos {
498
498
  if (mappedId) {
499
499
  newRecord[field] = mappedId;
500
500
  } else {
501
- // 找不到映射,设为null
501
+ // Cannot find mapping, set to null
502
502
  console.warn(
503
503
  `Could not find mapped ID for ${field}=${newRecord[field]} in table ${sourceTable}`,
504
504
  );
@@ -507,7 +507,7 @@ export class DataImporterRepos {
507
507
  }
508
508
  }
509
509
 
510
- // 简化处理自引用字段 - 直接设为null
510
+ // Simplified processing of self-reference fields - directly set to null
511
511
  for (const selfRef of selfReferences) {
512
512
  const { field } = selfRef;
513
513
  if (newRecord[field] !== undefined) {
@@ -518,15 +518,15 @@ export class DataImporterRepos {
518
518
  return { newRecord, originalId };
519
519
  });
520
520
 
521
- // 5. 检查唯一约束并应用冲突策略
521
+ // 5. Check unique constraints and apply conflict strategy
522
522
  for (const record of preparedData) {
523
523
  if (isCompositeKey && uniqueConstraints.length > 0) {
524
- // 对于复合主键表,将所有唯一约束字段作为一个组合条件
524
+ // For composite key tables, treat all unique constraint fields as a combined condition
525
525
  const whereConditions = uniqueConstraints
526
526
  .filter((field) => record.newRecord[field] !== undefined)
527
527
  .map((field) => eq(table[field], record.newRecord[field]));
528
528
 
529
- // 添加userId条件(如果表有userId字段)
529
+ // Add userId condition (if table has userId field)
530
530
  if ('userId' in table) {
531
531
  whereConditions.push(eq(table.userId, this.userId));
532
532
  }
@@ -537,7 +537,7 @@ export class DataImporterRepos {
537
537
  });
538
538
 
539
539
  if (exists) {
540
- // 记录冲突
540
+ // Record conflict
541
541
  if (!this.conflictRecords[tableName]) this.conflictRecords[tableName] = [];
542
542
  this.conflictRecords[tableName].push({
543
543
  field: uniqueConstraints.join(','),
@@ -546,13 +546,13 @@ export class DataImporterRepos {
546
546
  .join(','),
547
547
  });
548
548
 
549
- // 应用冲突策略
549
+ // Apply conflict strategy
550
550
  switch (conflictStrategy) {
551
551
  case 'skip': {
552
552
  record.newRecord._skip = true;
553
553
  result.skips++;
554
554
 
555
- // 关键改进:即使跳过,也建立ID映射关系
555
+ // Key improvement: establish ID mapping even if skipped
556
556
  if (!isCompositeKey) {
557
557
  this.idMaps[tableName][record.originalId] = exists.id;
558
558
  if (record.newRecord.clientId) {
@@ -562,11 +562,11 @@ export class DataImporterRepos {
562
562
  break;
563
563
  }
564
564
  case 'override': {
565
- // 不需要额外操作,插入时会覆盖
565
+ // No additional operation needed, will be overridden on insert
566
566
  break;
567
567
  }
568
568
  case 'merge': {
569
- // 合并数据
569
+ // Merge data
570
570
  await trx
571
571
  .update(table)
572
572
  .set(record.newRecord)
@@ -582,30 +582,30 @@ export class DataImporterRepos {
582
582
  }
583
583
  }
584
584
  } else {
585
- // 处理唯一约束
585
+ // Process unique constraints
586
586
  for (const field of uniqueConstraints) {
587
587
  if (!record.newRecord[field]) continue;
588
588
 
589
- // 检查字段值是否已存在
589
+ // Check if field value already exists
590
590
  const exists = await trx.query[tableName].findFirst({
591
591
  where: eq(table[field], record.newRecord[field]),
592
592
  });
593
593
 
594
594
  if (exists) {
595
- // 记录冲突
595
+ // Record conflict
596
596
  if (!this.conflictRecords[tableName]) this.conflictRecords[tableName] = [];
597
597
  this.conflictRecords[tableName].push({
598
598
  field,
599
599
  value: record.newRecord[field],
600
600
  });
601
601
 
602
- // 应用冲突策略
602
+ // Apply conflict strategy
603
603
  switch (conflictStrategy) {
604
604
  case 'skip': {
605
605
  record.newRecord._skip = true;
606
606
  result.skips++;
607
607
 
608
- // 关键改进:即使跳过,也建立ID映射关系
608
+ // Key improvement: establish ID mapping even if skipped
609
609
  if (!isCompositeKey) {
610
610
  this.idMaps[tableName][record.originalId] = exists.id;
611
611
  if (record.newRecord.clientId) {
@@ -615,7 +615,7 @@ export class DataImporterRepos {
615
615
  break;
616
616
  }
617
617
  case 'override': {
618
- // 应用字段处理器
618
+ // Apply field processor
619
619
  if (field in fieldProcessors) {
620
620
  record.newRecord[field] = fieldProcessors[field](record.newRecord[field]);
621
621
  }
@@ -623,7 +623,7 @@ export class DataImporterRepos {
623
623
  }
624
624
 
625
625
  case 'merge': {
626
- // 合并数据
626
+ // Merge data
627
627
  await trx
628
628
  .update(table)
629
629
  .set(record.newRecord)
@@ -641,13 +641,13 @@ export class DataImporterRepos {
641
641
  }
642
642
  }
643
643
 
644
- // 过滤掉标记为跳过的记录
644
+ // Filter out records marked to be skipped
645
645
  const filteredData = preparedData.filter((record) => !record.newRecord._skip);
646
646
 
647
- // 清除临时标记
647
+ // Clear temporary markers
648
648
  filteredData.forEach((record) => delete record.newRecord._skip);
649
649
 
650
- // 6. 批量插入数据
650
+ // 6. Batch insert data
651
651
  const BATCH_SIZE = 100;
652
652
 
653
653
  for (let i = 0; i < filteredData.length; i += BATCH_SIZE) {
@@ -657,12 +657,12 @@ export class DataImporterRepos {
657
657
  const originalIds = batch.map((item) => item.originalId);
658
658
 
659
659
  try {
660
- // 插入并返回结果
660
+ // Insert and return result
661
661
  const insertQuery = trx.insert(table).values(itemsToInsert);
662
662
 
663
663
  let insertResult;
664
664
 
665
- // 只对非复合主键表需要返回ID
665
+ // Only non-composite key tables need to return ID
666
666
  if (!isCompositeKey) {
667
667
  const res = await insertQuery.returning();
668
668
  insertResult = res.map((item: any) => ({
@@ -671,18 +671,18 @@ export class DataImporterRepos {
671
671
  }));
672
672
  } else {
673
673
  await insertQuery;
674
- insertResult = itemsToInsert.map(() => ({})); // 创建空结果以维持计数
674
+ insertResult = itemsToInsert.map(() => ({})); // Create empty result to maintain count
675
675
  }
676
676
 
677
677
  result.added += insertResult.length;
678
678
 
679
- // 建立ID映射关系 (只对非复合主键表)
679
+ // Establish ID mapping relationship (only for non-composite key tables)
680
680
  if (!isCompositeKey) {
681
681
  for (const [j, newRecord] of insertResult.entries()) {
682
682
  const originalId = originalIds[j];
683
683
  this.idMaps[tableName][originalId] = newRecord.id;
684
684
 
685
- // 同时确保clientId也能映射到正确的ID
685
+ // Also ensure clientId can map to the correct ID
686
686
  const originalRecord = tableData.find((item) => item.id === originalId);
687
687
  if (originalRecord && originalRecord.clientId) {
688
688
  this.idMaps[tableName][originalRecord.clientId] = newRecord.id;
@@ -692,7 +692,7 @@ export class DataImporterRepos {
692
692
  } catch (error) {
693
693
  console.error(`Error batch inserting ${tableName}:`, error);
694
694
 
695
- // 处理错误并记录
695
+ // Handle error and record
696
696
  if ((error as any).code === '23505') {
697
697
  const match = (error as any).detail?.match(/Key \((.+?)\)=\((.+?)\) already exists/);
698
698
  if (match) {