nolo-cli 0.1.13 → 0.1.14

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 (320) hide show
  1. package/README.md +9 -2
  2. package/agent-runtime/hostAdapter.ts +53 -0
  3. package/agent-runtime/index.ts +28 -0
  4. package/agent-runtime/localLoop.ts +62 -0
  5. package/agent-runtime/runtimeDecision.ts +70 -0
  6. package/agent-runtime/types.ts +87 -0
  7. package/agentRuntimeCommands.ts +139 -22
  8. package/agentRuntimeLocal.ts +7 -0
  9. package/ai/agent/_executeModel.ts +118 -0
  10. package/ai/agent/agentSlice.ts +544 -1
  11. package/ai/agent/appWorkingMemory.ts +126 -0
  12. package/ai/agent/avatarUtils.ts +24 -0
  13. package/ai/agent/buildEditingContext.ts +373 -0
  14. package/ai/agent/buildSystemPrompt.ts +532 -0
  15. package/ai/agent/cleanAgentMessages.ts +140 -0
  16. package/ai/agent/cliChatClient.ts +119 -0
  17. package/ai/agent/contextCompiler.ts +107 -0
  18. package/ai/agent/contextLayerContract.ts +44 -0
  19. package/ai/agent/createAgentSchema.ts +234 -0
  20. package/ai/agent/executeToolCall.ts +58 -0
  21. package/ai/agent/fetchAgentContexts.ts +42 -0
  22. package/ai/agent/generatePrompt.ts +3 -0
  23. package/ai/agent/getFullChatContextKeys.ts +168 -0
  24. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  25. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  26. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  27. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  28. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  29. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  30. package/ai/agent/planSlice.ts +259 -0
  31. package/ai/agent/referenceUtils.ts +229 -0
  32. package/ai/agent/runAgentBackground.ts +238 -0
  33. package/ai/agent/runAgentClientLoop.ts +138 -0
  34. package/ai/agent/runtimeGuidance.ts +97 -0
  35. package/ai/agent/runtimeServerBase.ts +37 -0
  36. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  37. package/ai/agent/startParallelAgentStreams.ts +424 -0
  38. package/ai/agent/startupProtocol.ts +53 -0
  39. package/ai/agent/streamAgentChatTurn.ts +1299 -0
  40. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  41. package/ai/agent/types.ts +71 -0
  42. package/ai/agent/utils/imageOutput.ts +39 -0
  43. package/ai/agent/utils/publicImageAgentMode.ts +26 -0
  44. package/ai/agent/utils/sortUtils.ts +250 -0
  45. package/ai/agent/web/referencePickerUtils.ts +146 -0
  46. package/ai/ai.locale.ts +1083 -0
  47. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  48. package/ai/chat/fetchUtils.native.ts +276 -0
  49. package/ai/chat/fetchUtils.ts +153 -0
  50. package/ai/chat/inlineImageUrlsForCustomProvider.ts +117 -0
  51. package/ai/chat/parseApiError.ts +64 -0
  52. package/ai/chat/parseMultilineSSE.ts +95 -0
  53. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  54. package/ai/chat/sendOpenAICompletionsRequest.ts +712 -0
  55. package/ai/chat/sendOpenAIResponseRequest.ts +512 -0
  56. package/ai/chat/shouldUseServerProxy.ts +18 -0
  57. package/ai/chat/sseClient.native.ts +91 -0
  58. package/ai/chat/sseClient.ts +67 -0
  59. package/ai/chat/streamReader.native.ts +31 -0
  60. package/ai/chat/streamReader.ts +62 -0
  61. package/ai/chat/updateTotalUsage.ts +72 -0
  62. package/ai/context/buildReferenceContext.ts +437 -0
  63. package/ai/context/calculateContextUsage.ts +133 -0
  64. package/ai/context/retention.ts +165 -0
  65. package/ai/context/tokenUtils.ts +78 -0
  66. package/ai/index.ts +1 -1
  67. package/ai/llm/agentCapabilities.ts +74 -0
  68. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  69. package/ai/llm/deepinfra.ts +28 -0
  70. package/ai/llm/fireworks.ts +68 -0
  71. package/ai/llm/generateRequestBody.ts +165 -0
  72. package/ai/llm/getModelContextWindow.ts +84 -0
  73. package/ai/llm/getNoloKey.ts +37 -0
  74. package/ai/llm/getPricing.ts +232 -0
  75. package/ai/llm/hooks/useModelPricing.ts +75 -0
  76. package/ai/llm/imagePricing.ts +66 -0
  77. package/ai/llm/isResponseAPIModel.ts +13 -0
  78. package/ai/llm/kimi.ts +18 -0
  79. package/ai/llm/mimo.ts +71 -0
  80. package/ai/llm/mistral.ts +22 -0
  81. package/ai/llm/modelAvatar.ts +427 -0
  82. package/ai/llm/models.ts +45 -0
  83. package/ai/llm/openrouterModels.ts +141 -0
  84. package/ai/llm/providers.ts +307 -0
  85. package/ai/llm/reasoningModels.ts +28 -0
  86. package/ai/llm/types.ts +59 -0
  87. package/ai/llm/usageRequestOptions.ts +59 -0
  88. package/ai/memory/capture.ts +148 -0
  89. package/ai/memory/consolidate.ts +104 -0
  90. package/ai/memory/delete.ts +147 -0
  91. package/ai/memory/overlay.ts +84 -0
  92. package/ai/memory/query.ts +38 -0
  93. package/ai/memory/queryShared.ts +160 -0
  94. package/ai/memory/rank.ts +105 -0
  95. package/ai/memory/recentRelationshipRecap.ts +247 -0
  96. package/ai/memory/remember.ts +167 -0
  97. package/ai/memory/runtime.ts +76 -0
  98. package/ai/memory/store.ts +20 -0
  99. package/ai/memory/storeShared.ts +76 -0
  100. package/ai/memory/types.ts +46 -0
  101. package/ai/memory/understanding.ts +349 -0
  102. package/ai/memory/understandingGreeting.ts +264 -0
  103. package/ai/messages/type.ts +20 -0
  104. package/ai/policy/personalizationDialog.ts +333 -0
  105. package/ai/policy/runtimePolicy.ts +440 -0
  106. package/ai/policy/selfUpdateFields.ts +48 -0
  107. package/ai/policy/types.ts +64 -0
  108. package/ai/skills/referenceRuntime.ts +274 -0
  109. package/ai/skills/skillDiagnostics.ts +251 -0
  110. package/ai/skills/skillDocBuilder.ts +139 -0
  111. package/ai/skills/skillDocProtocol.ts +434 -0
  112. package/ai/skills/skillReferenceSummary.ts +63 -0
  113. package/ai/skills/skillSummaryMarker.ts +26 -0
  114. package/ai/token/calculatePrice.ts +546 -0
  115. package/ai/token/db.ts +98 -0
  116. package/ai/token/externalToolCost.ts +321 -0
  117. package/ai/token/hooks/useRecords.ts +65 -0
  118. package/ai/token/missingUsageEstimate.ts +42 -0
  119. package/ai/token/modelUsageQuery.ts +252 -0
  120. package/ai/token/normalizeUsage.ts +84 -0
  121. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  122. package/ai/token/prepareTokenUsageData.ts +88 -0
  123. package/ai/token/query.ts +88 -0
  124. package/ai/token/queryUserTokens.ts +59 -0
  125. package/ai/token/resolveBillingTarget.ts +52 -0
  126. package/ai/token/saveTokenRecord.ts +53 -0
  127. package/ai/token/serverDialogProjection.ts +78 -0
  128. package/ai/token/serverTokenWriter.ts +143 -0
  129. package/ai/token/stats.ts +21 -0
  130. package/ai/token/tokenThunks.ts +24 -0
  131. package/ai/token/types.ts +93 -0
  132. package/ai/tools/agent/agentTools.ts +176 -0
  133. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  134. package/ai/tools/agent/callAgentTool.ts +139 -0
  135. package/ai/tools/agent/createAgentTool.ts +512 -0
  136. package/ai/tools/agent/createDialogTool.ts +69 -0
  137. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  138. package/ai/tools/agent/parallelBudget.ts +221 -0
  139. package/ai/tools/agent/presets/appBuilderPreset.ts +147 -0
  140. package/ai/tools/agent/runLlmTool.ts +96 -0
  141. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  142. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  143. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  144. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  145. package/ai/tools/agent/updateAgentTool.ts +96 -0
  146. package/ai/tools/agent/updateSelfTool.ts +113 -0
  147. package/ai/tools/amazonProductScraperTool.ts +86 -0
  148. package/ai/tools/apifyActorClient.ts +45 -0
  149. package/ai/tools/appEditGuard.ts +372 -0
  150. package/ai/tools/appReadSnapshot.ts +153 -0
  151. package/ai/tools/appTools.ts +1549 -0
  152. package/ai/tools/applyEditTool.ts +256 -0
  153. package/ai/tools/applyLineEditsTool.ts +312 -0
  154. package/ai/tools/browserTools/click.ts +33 -0
  155. package/ai/tools/browserTools/closeSession.ts +29 -0
  156. package/ai/tools/browserTools/common.ts +27 -0
  157. package/ai/tools/browserTools/openSession.ts +48 -0
  158. package/ai/tools/browserTools/readContent.ts +38 -0
  159. package/ai/tools/browserTools/selectOption.ts +46 -0
  160. package/ai/tools/browserTools/typeText.ts +42 -0
  161. package/ai/tools/category/createCategoryTool.ts +66 -0
  162. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  163. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  164. package/ai/tools/cfBrowserTools.ts +319 -0
  165. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  166. package/ai/tools/checkEnvTool.ts +65 -0
  167. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  168. package/ai/tools/codeSearchTool.ts +111 -0
  169. package/ai/tools/codeTools.ts +101 -0
  170. package/ai/tools/createDocTool.ts +132 -0
  171. package/ai/tools/createPlanTool.ts +999 -0
  172. package/ai/tools/createSkillDocTool.ts +155 -0
  173. package/ai/tools/createWorkflowTool.ts +154 -0
  174. package/ai/tools/deepseekOcrTool.ts +34 -0
  175. package/ai/tools/delayTool.ts +31 -0
  176. package/ai/tools/deleteSpacesTool.ts +325 -0
  177. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  178. package/ai/tools/devReloadUtils.ts +29 -0
  179. package/ai/tools/dialogMessageSearch.ts +137 -0
  180. package/ai/tools/doctorSkillTool.ts +72 -0
  181. package/ai/tools/ecommerceScraperTool.ts +86 -0
  182. package/ai/tools/emailTools.ts +549 -0
  183. package/ai/tools/evalSkillTool.ts +92 -0
  184. package/ai/tools/exaSearchTool.ts +64 -0
  185. package/ai/tools/execBashTool.ts +379 -0
  186. package/ai/tools/executeSqlTool.ts +192 -0
  187. package/ai/tools/fetchWebpageSupport.ts +309 -0
  188. package/ai/tools/fetchWebpageTool.ts +84 -0
  189. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  190. package/ai/tools/generateDocxTool.ts +215 -0
  191. package/ai/tools/googleSearchScraperTool.ts +106 -0
  192. package/ai/tools/importDataTool.ts +133 -0
  193. package/ai/tools/importSkillTool.ts +162 -0
  194. package/ai/tools/index.ts +1927 -0
  195. package/ai/tools/listFilesTool.ts +82 -0
  196. package/ai/tools/listUserSpacesTool.ts +113 -0
  197. package/ai/tools/modelUsageTools.ts +199 -0
  198. package/ai/tools/olmOcrTool.ts +34 -0
  199. package/ai/tools/openaiImageTool.ts +267 -0
  200. package/ai/tools/prepareTools.ts +23 -0
  201. package/ai/tools/readDocTool.ts +84 -0
  202. package/ai/tools/readFileTool.ts +211 -0
  203. package/ai/tools/readTool.ts +163 -0
  204. package/ai/tools/readXPostTool.ts +233 -0
  205. package/ai/tools/rememberMemoryTool.ts +84 -0
  206. package/ai/tools/remotionVideoTool.ts +151 -0
  207. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  208. package/ai/tools/searchRepoTool.ts +115 -0
  209. package/ai/tools/searchWorkspaceTool.ts +259 -0
  210. package/ai/tools/skillFollowup.ts +86 -0
  211. package/ai/tools/surfWeatherTool.ts +169 -0
  212. package/ai/tools/table/addTableRowTool.ts +217 -0
  213. package/ai/tools/table/createTableTool.ts +315 -0
  214. package/ai/tools/table/rowTools.ts +366 -0
  215. package/ai/tools/table/schemaTools.ts +244 -0
  216. package/ai/tools/table/shareTableTool.ts +148 -0
  217. package/ai/tools/table/toolShared.ts +129 -0
  218. package/ai/tools/toolApiClient.ts +198 -0
  219. package/ai/tools/toolNameAliases.ts +57 -0
  220. package/ai/tools/toolResultError.ts +42 -0
  221. package/ai/tools/toolRunSlice.ts +303 -0
  222. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  223. package/ai/tools/toolVisibility.ts +4 -0
  224. package/ai/tools/types.ts +20 -0
  225. package/ai/tools/uiAskChoiceTool.ts +104 -0
  226. package/ai/tools/updateContentTitleTool.ts +84 -0
  227. package/ai/tools/updateDocTool.ts +105 -0
  228. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  229. package/ai/tools/whisperTool.ts +77 -0
  230. package/ai/tools/writeFileTool.ts +210 -0
  231. package/ai/tools/youtubeScraperTool.ts +116 -0
  232. package/ai/tools/ziweiChartTool.ts +678 -0
  233. package/ai/types.ts +55 -0
  234. package/ai/workflow/workflowExecutor.ts +323 -0
  235. package/ai/workflow/workflowSlice.ts +73 -0
  236. package/ai/workflow/workflowTypes.ts +106 -0
  237. package/client/agentRun.test.ts +240 -0
  238. package/client/agentRun.ts +182 -19
  239. package/client/compactDialog.test.ts +238 -0
  240. package/client/localRuntimeAdapter.test.ts +135 -0
  241. package/client/localRuntimeAdapter.ts +244 -0
  242. package/client/profileConfig.test.ts +40 -0
  243. package/client/streamingOutput.test.ts +22 -0
  244. package/client/streamingOutput.ts +38 -0
  245. package/commandRegistry.ts +9 -2
  246. package/connector-experimental/index.ts +5 -0
  247. package/database/actions/cacheMergedUserData.ts +64 -0
  248. package/database/actions/common.ts +242 -0
  249. package/database/actions/deleteFile.ts +40 -0
  250. package/database/actions/fetchUserData.ts +16 -0
  251. package/database/actions/fileContent.ts +125 -0
  252. package/database/actions/patch.ts +155 -0
  253. package/database/actions/read.ts +337 -0
  254. package/database/actions/readAndWait.ts +224 -0
  255. package/database/actions/readRequestManager.ts +120 -0
  256. package/database/actions/remove.ts +94 -0
  257. package/database/actions/replication.ts +366 -0
  258. package/database/actions/upload.ts +174 -0
  259. package/database/actions/upsert.ts +56 -0
  260. package/database/actions/write.ts +126 -0
  261. package/database/client/db.native.ts +73 -0
  262. package/database/client/db.ts +51 -0
  263. package/database/client/fetchUserData.ts +61 -0
  264. package/database/client/handleError.ts +19 -0
  265. package/database/client/queryRequest.ts +21 -0
  266. package/database/config.ts +21 -0
  267. package/database/dbActionThunks.ts +1 -0
  268. package/database/dbSlice.ts +149 -0
  269. package/database/email.ts +42 -0
  270. package/database/fileRing.ts +51 -0
  271. package/database/fileSharding.ts +70 -0
  272. package/database/fileStorage.native.ts +92 -0
  273. package/database/fileStorage.ts +232 -0
  274. package/database/fileUrl.ts +34 -0
  275. package/database/hooks/useUserData.ts +489 -0
  276. package/database/index.ts +1 -0
  277. package/database/keys.ts +765 -0
  278. package/database/queryPrefixes.ts +14 -0
  279. package/database/requests.ts +443 -0
  280. package/database/runtimeServerContext.ts +35 -0
  281. package/database/server/MemoryDB.ts +76 -0
  282. package/database/server/actorAccess.ts +76 -0
  283. package/database/server/agentDelegation.ts +124 -0
  284. package/database/server/coreDataOwnership.ts +13 -0
  285. package/database/server/coreDataProxy.ts +76 -0
  286. package/database/server/cybotReadonly.ts +18 -0
  287. package/database/server/dataHandlers.ts +111 -0
  288. package/database/server/db.ts +118 -0
  289. package/database/server/dbPath.ts +20 -0
  290. package/database/server/delete.ts +499 -0
  291. package/database/server/emailRepository.ts +1480 -0
  292. package/database/server/ensureDbOpen.ts +12 -0
  293. package/database/server/fileRead.ts +337 -0
  294. package/database/server/fileService.ts +436 -0
  295. package/database/server/handleTransaction.ts +86 -0
  296. package/database/server/patch.ts +282 -0
  297. package/database/server/query.ts +138 -0
  298. package/database/server/read.ts +325 -0
  299. package/database/server/resourceAccess.ts +211 -0
  300. package/database/server/routes.ts +110 -0
  301. package/database/server/spaceMemberAuthority.ts +67 -0
  302. package/database/server/upload.ts +159 -0
  303. package/database/server/write.ts +494 -0
  304. package/database/server/writeAuthority.ts +133 -0
  305. package/database/sqliteDb.ts +46 -0
  306. package/database/table/deleteTable.ts +120 -0
  307. package/database/tenantPlacement.ts +57 -0
  308. package/database/tombstones.ts +52 -0
  309. package/database/userDataLoadDecision.ts +17 -0
  310. package/database/userDataMerge.ts +95 -0
  311. package/database/userPreferenceRegister.ts +108 -0
  312. package/database/utils/dbPath.ts +47 -0
  313. package/database/utils/ulid.native.ts +6 -0
  314. package/database/utils/ulid.ts +1 -0
  315. package/index.ts +25 -15
  316. package/localRuntimeDb.ts +28 -0
  317. package/package.json +16 -4
  318. package/runtimeModeArgs.ts +33 -0
  319. package/tui/readlineWorkspace.ts +1 -0
  320. package/tui/session.ts +22 -0
@@ -0,0 +1,174 @@
1
+ // 文件路径: database/actions/upload.ts
2
+
3
+ import { getRuntimeServerContext } from "database/runtimeServerContext";
4
+ import { ulid } from "../utils/ulid";
5
+ import { normalizeTimeFields, logger } from "./common";
6
+ import { toast } from "app/utils/toast";
7
+ import { saveFileToIndexedDb } from "../fileStorage";
8
+ import { fileKey } from "../keys";
9
+ import { scheduleUploadReplication, uploadToCurrentServer } from "./replication";
10
+ import { DataType } from "create/types";
11
+ import { resolveFileCategory } from "app/utils/fileUtils";
12
+
13
+ /**
14
+ * 辅助函数:保存文件元数据到客户端数据库
15
+ */
16
+ const saveToClientDb = async (
17
+ clientDb: any,
18
+ dbKey: string,
19
+ metadata: any
20
+ ): Promise<void> => {
21
+ if (!clientDb) {
22
+ logger.error({ dbKey }, "Client database is undefined in saveToClientDb");
23
+ throw new Error("Client database instance is required");
24
+ }
25
+
26
+ try {
27
+ await clientDb.put(dbKey, metadata);
28
+ logger.debug(
29
+ { dbKey },
30
+ "File metadata saved successfully to local database."
31
+ );
32
+ } catch (err: any) {
33
+ logger.error(
34
+ { err, dbKey },
35
+ "Failed to save file metadata to local database"
36
+ );
37
+ throw new Error(`Local database put failed for ${dbKey}: ${err.message}`);
38
+ }
39
+ };
40
+
41
+ /**
42
+ * Upload File Action: 上传文件并保存元数据。
43
+ *
44
+ * 设计要点:
45
+ * - 以 fileId 作为文件唯一 ID;
46
+ * - 本地 IndexedDB 完整缓存一份(离线可用);
47
+ * - 按 tenantId(通常为 userId)通过 hash ring 选择若干服务器写入完整副本;
48
+ * - 服务器只需跑单机 fileService,不感知 ring/tenant。
49
+ *
50
+ * 将来扩容到几十台服务器:
51
+ * - 只需要在设置里增加/调整 syncServers 列表;
52
+ * - getAllServers + planServersForTenant 会自动把新节点纳入分布;
53
+ * - 无需修改业务调用代码。
54
+ */
55
+ export const uploadFileAction = async (
56
+ uploadConfig: { file: File; customKey?: string; userId?: string },
57
+ thunkApi: any
58
+ ): Promise<any> => {
59
+ const { db: clientDb } = thunkApi.extra;
60
+ const state = thunkApi.getState();
61
+ const { currentServer, syncServers, currentUserId } =
62
+ getRuntimeServerContext(state);
63
+
64
+ const { file, customKey } = uploadConfig;
65
+ const userId = uploadConfig.userId || currentUserId;
66
+ // 1. 验证参数
67
+ if (!file) {
68
+ const errorMsg =
69
+ "Invalid arguments for uploadFileAction: file is required.";
70
+ logger.error(errorMsg, { uploadConfig });
71
+ toast.error(errorMsg);
72
+ throw new Error(errorMsg);
73
+ }
74
+
75
+ try {
76
+ // 2. 生成文件 ID 和文件名(fileId 将作为逻辑 ID,服务端会沿用)
77
+ const fileId = ulid();
78
+ const fileExtension = file.name.split(".").pop() || "";
79
+ const fileName = `${fileId}${fileExtension ? "." + fileExtension : ""}`;
80
+
81
+ // 决定最终的 dbKey (强制使用 file-userId-ulid 模式)
82
+ let finalDbKey = customKey;
83
+ if (!finalDbKey || !finalDbKey.startsWith("file-")) {
84
+ const actualUserId = userId || "unknown";
85
+ if (actualUserId === "unknown") {
86
+ console.warn("[uploadFileAction] User ID is unknown during upload. Key will be file-unknown.");
87
+ }
88
+ finalDbKey = fileKey.single(actualUserId, fileId);
89
+ }
90
+
91
+ // 3. 准备文件元数据(添加时间戳、dbKey、userId 等)
92
+ const fileMetadata = normalizeTimeFields({
93
+ id: fileId,
94
+ title: file.name,
95
+ originalName: file.name,
96
+ fileName,
97
+ filePath: "",
98
+ size: file.size,
99
+ mimeType: file.type || "application/octet-stream",
100
+ type: DataType.FILE,
101
+ fileCategory: resolveFileCategory({
102
+ mimeType: file.type,
103
+ fileName: file.name,
104
+ }),
105
+ dbKey: finalDbKey,
106
+ userId,
107
+ });
108
+
109
+ // 本地结构化元数据:key = finalDbKey(供 dbSlice 等使用)
110
+ await saveToClientDb(clientDb, finalDbKey, fileMetadata);
111
+
112
+ // 4. 将原始文件存入 IndexedDB / Native Storage
113
+ // 本地以 fileId 为 key 缓存内容(离线使用)
114
+ // 在 RN 环境下,saveFileToIndexedDb 实际上是存储文件路径引用
115
+ try {
116
+ await saveFileToIndexedDb(fileId, file);
117
+ } catch (err) {
118
+ logger.warn(
119
+ { err, fileId },
120
+ "[uploadFileAction] Failed to cache file locally."
121
+ );
122
+ }
123
+
124
+ // 5. 基于 tenantId(userId)为当前用户选择服务器集合,并在后台同步上传。
125
+ // - 同一个用户的数据总是落在同一组服务器上
126
+ // - 每个用户至少有固定副本数的服务器持有完整数据
127
+ const tenantId = userId || "default";
128
+ const uploadReplicationConfig = {
129
+ file,
130
+ metadata: fileMetadata,
131
+ customKey: finalDbKey,
132
+ userId,
133
+ };
134
+
135
+ const currentServerUploadSucceeded = await uploadToCurrentServer({
136
+ currentServer,
137
+ uploadConfig: uploadReplicationConfig,
138
+ state,
139
+ });
140
+ if (currentServer && !currentServerUploadSucceeded) {
141
+ throw new Error(`Primary upload failed on current server ${currentServer}`);
142
+ }
143
+
144
+ const serversToUse = scheduleUploadReplication({
145
+ currentServer,
146
+ syncServers,
147
+ tenantId,
148
+ uploadConfig: uploadReplicationConfig,
149
+ state,
150
+ excludeServers: currentServer ? [currentServer] : [],
151
+ });
152
+
153
+ if (!currentServer && !serversToUse.length) {
154
+ logger.warn(
155
+ "[uploadFileAction] No replication servers available, file metadata only saved locally.",
156
+ { finalDbKey, fileName, tenantId }
157
+ );
158
+ return fileMetadata;
159
+ }
160
+
161
+ logger.debug(
162
+ `[uploadFileAction] Uploaded primary copy for ${fileName} and scheduled background sync to ${serversToUse.length} additional servers.`,
163
+ { currentServer, serversToUse, tenantId }
164
+ );
165
+ // 8. 返回本地保存的元数据
166
+ return fileMetadata;
167
+ } catch (error: any) {
168
+ const errorMessage = `Upload action failed for ${customKey}: ${error?.message || "Unknown error"
169
+ }`;
170
+ logger.error({ error }, "[uploadFileAction] Error");
171
+ toast.error(`Failed to upload file for ${customKey}.`);
172
+ throw new Error(errorMessage);
173
+ }
174
+ };
@@ -0,0 +1,56 @@
1
+ // src/database/actions/upsert.ts
2
+ import { toast } from "app/utils/toast";
3
+ import type { AppThunkApi } from "app/store";
4
+
5
+ import { patchAction } from "./patch";
6
+ import { readAction } from "./read";
7
+ import { writeAction } from "./write";
8
+
9
+ /**
10
+ * Upsert 数据协调器:根据数据是否存在,调度 patch 或 write 操作。
11
+ * 此操作本身不执行数据库读写或网络请求,而是委托给其他 async thunks。
12
+ *
13
+ * @param upsertConfig 包含 dbKey(必需)和 data(必需)的对象。
14
+ * @param thunkApi Redux Thunk API,包含 dispatch 和 getState。
15
+ * @returns Promise<any> 来自 patch或write操作成功后的数据对象。
16
+ * @throws 如果参数无效或调度的操作失败,则抛出错误。
17
+ */
18
+ export const upsertAction = async (
19
+ upsertConfig: { dbKey: string; data: any },
20
+ thunkApi: AppThunkApi
21
+ ): Promise<any> => {
22
+ const { dbKey, data } = upsertConfig;
23
+
24
+ // 1. 参数验证
25
+ if (!dbKey || !data || typeof data !== "object") {
26
+ const errorMsg = "upsertAction 参数无效:dbKey 和 data 对象是必需的。";
27
+ toast.error(errorMsg);
28
+ throw new Error(errorMsg);
29
+ }
30
+
31
+ try {
32
+ // 2. 调用 readAction 检查数据是否存在于本地(通过 Redux Store 或 DB)
33
+ // 我们不关心 readAction 是否真的从网络读取,只关心它最终返回的结果。
34
+ const existingData = await readAction({ dbKey }, thunkApi);
35
+ let finalResult;
36
+
37
+ // 3. 根据是否存在数据,决定调度 patch 还是 write
38
+ if (existingData && Object.keys(existingData).length > 0) {
39
+ // **更新路径**:数据已存在,调度 patch action
40
+ // patch action 内部会处理 updatedAt、userId、本地写入和服务器同步
41
+ finalResult = await patchAction({ dbKey, changes: data }, thunkApi);
42
+ } else {
43
+ // **插入路径**:数据不存在,调度 write action
44
+ // write action 内部会处理 createdAt/updatedAt、userId、本地写入和服务器同步
45
+ finalResult = await writeAction({ data, customKey: dbKey }, thunkApi);
46
+ }
47
+
48
+ // 4. 返回最终执行成功的数据
49
+ return finalResult;
50
+ } catch (error: any) {
51
+ const errorMessage = `Upsert 协调操作失败 (dbKey: ${dbKey}): ${error.message || "未知错误"}`;
52
+ toast.error("数据保存失败,请稍后重试。");
53
+ // 重新抛出错误,以便 create.asyncThunk 能捕获到 rejected 状态
54
+ throw new Error(errorMessage, { cause: error });
55
+ }
56
+ };
@@ -0,0 +1,126 @@
1
+ // 文件路径: database/actions/write.ts
2
+
3
+ import type { AppThunkApi } from "app/store";
4
+ import { getRuntimeServerContext } from "database/runtimeServerContext";
5
+ import { DataType } from "create/types";
6
+ import { normalizeTimeFields, logger } from "./common";
7
+ import {
8
+ resolveReplicationServers,
9
+ scheduleWriteReplication,
10
+ } from "./replication";
11
+ import { toast } from "app/utils/toast";
12
+
13
+ // 辅助函数:保存到本地 DB
14
+ const saveToClientDb = async (
15
+ clientDb: any,
16
+ dbKey: string,
17
+ data: any
18
+ ): Promise<void> => {
19
+ if (!clientDb) {
20
+ logger.error({ dbKey }, "Client database is undefined in saveToClientDb");
21
+ throw new Error("Client database instance is required");
22
+ }
23
+ try {
24
+ await clientDb.put(dbKey, data);
25
+ logger.debug({ dbKey }, "Data saved successfully to local database.");
26
+ } catch (err: any) {
27
+ logger.error({ err, dbKey }, "Failed to save data to local database");
28
+ throw new Error(`Local database put failed for ${dbKey}: ${err.message}`);
29
+ }
30
+ };
31
+
32
+ /**
33
+ * Write Action: 写入新数据项。
34
+ * 1. 验证数据类型。
35
+ * 2. 规范化数据(添加时间戳、dbKey、userId)。
36
+ * 3. 保存数据到本地数据库。
37
+ * 4. 若在线,异步将完整数据写入所有服务器。
38
+ */
39
+ export const writeAction = async (
40
+ writeConfig: { data: any; customKey: string; userId?: string },
41
+ thunkApi: AppThunkApi
42
+ ): Promise<any> => {
43
+ const { db: clientDb } = thunkApi.extra;
44
+ if (!clientDb) {
45
+ throw new Error("Client database instance is required in writeAction");
46
+ }
47
+
48
+ const state = thunkApi.getState();
49
+ const { currentServer, syncServers, currentUserId } =
50
+ getRuntimeServerContext(state);
51
+
52
+ const { data, customKey } = writeConfig;
53
+ const userId = writeConfig.userId || currentUserId;
54
+
55
+ // 1. 基础参数校验
56
+ if (!data || !customKey) {
57
+ const errorMsg =
58
+ "Invalid arguments for writeAction: data and customKey are required.";
59
+ logger.error(errorMsg, { writeConfig });
60
+ toast.error(errorMsg);
61
+ throw new Error(errorMsg);
62
+ }
63
+
64
+ // 2. 类型校验(保持原有行为:非法类型只告警,不阻塞)
65
+ const VALID_TYPES = [
66
+ DataType.MSG,
67
+ DataType.CYBOT,
68
+ DataType.DOC,
69
+ DataType.DIALOG,
70
+ DataType.NOTIFICATION,
71
+ DataType.TOKEN,
72
+ DataType.TRANSACTION,
73
+ DataType.SPACE,
74
+ DataType.SETTING,
75
+ DataType.TABLE,
76
+ DataType.TABLE_ROW,
77
+ DataType.EMAIL,
78
+ ];
79
+ if (!data.type || !VALID_TYPES.includes(data.type)) {
80
+ logger.warn(
81
+ `Invalid data type "${data.type}" for writeAction with key ${customKey}. Proceeding anyway.`
82
+ );
83
+ }
84
+
85
+ try {
86
+ // 3. 规范化数据(时间字段 / dbKey / userId)
87
+ const willSaveData = normalizeTimeFields({
88
+ ...data,
89
+ dbKey: customKey,
90
+ userId,
91
+ });
92
+
93
+ // 4. 本地保存
94
+ await saveToClientDb(clientDb, customKey, willSaveData);
95
+
96
+ // 5. 计算远程服务器列表(currentServer + syncServers,带去重 + 离线判断)
97
+ const servers = resolveReplicationServers(currentServer, syncServers);
98
+
99
+ const serverWriteConfig = {
100
+ data: willSaveData,
101
+ customKey,
102
+ userId,
103
+ };
104
+
105
+ // 6. 后台异步同步到远程(若在线且有可用服务器)
106
+ if (servers.length > 0) {
107
+ logger.debug(
108
+ `[writeAction] Initiating background sync for key: ${customKey} to ${servers.length} servers.`
109
+ );
110
+ scheduleWriteReplication(servers, serverWriteConfig, state);
111
+ } else {
112
+ logger.warn(
113
+ "[writeAction] No available servers, data only saved locally.",
114
+ { customKey }
115
+ );
116
+ }
117
+
118
+ return willSaveData;
119
+ } catch (error: any) {
120
+ const errorMessage = `Write action failed for ${customKey}: ${error?.message || "Unknown error"
121
+ }`;
122
+ logger.error("[writeAction] Error:", error);
123
+ toast.error(`Failed to save data for ${customKey}.`);
124
+ throw new Error(errorMessage);
125
+ }
126
+ };
@@ -0,0 +1,73 @@
1
+ // database/client/db.native.ts
2
+ // React Native 版数据库 - 使用 @nolo/leveldb (monorepo 内部包)
3
+
4
+ import Level from "@nolo/leveldb";
5
+
6
+ // 扩展全局对象类型
7
+ declare global {
8
+ var noloDbInstance: Level | null;
9
+ }
10
+
11
+ /**
12
+ * 获取或创建数据库实例
13
+ * 使用全局变量存储实例,防止 Fast Refresh 导致模块重载时丢失引用而重复打开数据库
14
+ */
15
+ export function getDb(): Level {
16
+ if (!global.noloDbInstance) {
17
+ console.log('[DB Native] Creating new LevelDB instance');
18
+ try {
19
+ global.noloDbInstance = new Level("nolo", { valueEncoding: "json" });
20
+ } catch (e: any) {
21
+ console.error('[DB Native] Failed to open database:', e?.message ?? e);
22
+ throw new Error(
23
+ `[DB Native] 数据库启动失败,请尝试重启应用。原因: ${e?.message ?? '未知错误'}`
24
+ );
25
+ }
26
+ }
27
+ return global.noloDbInstance;
28
+ }
29
+
30
+ /**
31
+ * 创建数据库实例(用于 store 初始化)
32
+ */
33
+ export function createDb(): Level {
34
+ return getDb();
35
+ }
36
+
37
+ /**
38
+ * 关闭当前数据库实例
39
+ */
40
+ export async function closeDb(): Promise<void> {
41
+ if (global.noloDbInstance) {
42
+ try {
43
+ await global.noloDbInstance.close();
44
+ console.log('[DB Native] Database closed');
45
+ } catch (e) {
46
+ console.warn('[DB Native] Error closing database:', e);
47
+ }
48
+ global.noloDbInstance = null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 关闭所有数据库(用于热重载清理)
54
+ */
55
+ export async function closeAllDatabases(): Promise<void> {
56
+ try {
57
+ global.noloDbInstance = null;
58
+ await Level.closeAll();
59
+ console.log('[DB Native] All databases closed');
60
+ } catch (e) {
61
+ console.warn('[DB Native] Failed to close all databases:', e);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * 检查是否为 React Native 环境
67
+ */
68
+ export const isNative = true;
69
+
70
+ // 导出数据库实例(兼容旧代码)
71
+ export const browserDb = getDb();
72
+
73
+ export default Level;
@@ -0,0 +1,51 @@
1
+ // database/client/db.ts
2
+ // Web 版数据库 - 使用 level 包 (基于 IndexedDB)
3
+
4
+ import { Level } from "level";
5
+
6
+ // 数据库单例
7
+ let dbInstance: Level<string, any> | null = null;
8
+
9
+ /**
10
+ * 获取或创建数据库实例
11
+ */
12
+ export function getDb(): Level<string, any> {
13
+ if (!dbInstance) {
14
+ dbInstance = new Level("nolo", { valueEncoding: "json" });
15
+ }
16
+ return dbInstance;
17
+ }
18
+
19
+ /**
20
+ * 创建数据库实例(用于 store 初始化)
21
+ */
22
+ export function createDb(): Level<string, any> {
23
+ return getDb();
24
+ }
25
+
26
+ /**
27
+ * 关闭数据库
28
+ */
29
+ export async function closeDb(): Promise<void> {
30
+ if (dbInstance) {
31
+ await dbInstance.close();
32
+ dbInstance = null;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 关闭所有数据库(Web 版只有一个实例)
38
+ */
39
+ export async function closeAllDatabases(): Promise<void> {
40
+ await closeDb();
41
+ }
42
+
43
+ /**
44
+ * 检查是否为 React Native 环境
45
+ */
46
+ export const isNative = false;
47
+
48
+ // 导出数据库实例(兼容旧代码)
49
+ export const browserDb = getDb();
50
+
51
+ export default Level;
@@ -0,0 +1,61 @@
1
+ import { getUserDataPrefixes } from "../queryPrefixes";
2
+
3
+ interface FetchUserDataOptions {
4
+ includeDeleted?: boolean;
5
+ }
6
+
7
+ const attachQueriedKey = (key: string, value: any) => {
8
+ if (!value || typeof value !== "object") return value;
9
+ if (
10
+ typeof value.dbKey === "string" && value.dbKey.trim().length > 0
11
+ ) {
12
+ return value;
13
+ }
14
+ return {
15
+ ...value,
16
+ dbKey: key,
17
+ };
18
+ };
19
+
20
+ // 支持单类型或多类型查询
21
+ // db should be passed from caller (e.g. thunk extra)
22
+ export async function fetchUserData(
23
+ db: any,
24
+ types: string | string[],
25
+ userId: string,
26
+ options: FetchUserDataOptions = {}
27
+ ) {
28
+ const results: Record<string, any[]> = {};
29
+ const typeArray = Array.isArray(types) ? types : [types];
30
+ const includeDeleted = options.includeDeleted === true;
31
+
32
+ try {
33
+ for (const type of typeArray) {
34
+ const prefixes = getUserDataPrefixes(type, userId);
35
+ results[type] = [];
36
+ for (const prefix of prefixes) {
37
+ let iterator = db.iterator({
38
+ gte: prefix,
39
+ lte: `${prefix}\uffff`,
40
+ });
41
+
42
+ if (iterator && typeof iterator.then === "function") {
43
+ iterator = await iterator;
44
+ }
45
+
46
+ // @ts-ignore - iterator compatibility
47
+ for await (const [key, value] of iterator) {
48
+ const hydrated = attachQueriedKey(String(key), value);
49
+ if (!hydrated) continue;
50
+ if (!includeDeleted && hydrated.deletedAt) continue;
51
+ results[type].push(hydrated);
52
+ }
53
+ }
54
+ }
55
+
56
+ return Array.isArray(types) ? results : results[types] ?? [];
57
+ } catch (error) {
58
+ console.error("Query error:", error);
59
+ throw error;
60
+ }
61
+ }
@@ -0,0 +1,19 @@
1
+ import i18n from "app/i18n";
2
+ export const handleError = (error, handleUnauthorized?) => {
3
+ let message;
4
+ switch (error.message) {
5
+ case "400":
6
+ message = i18n.t("errors.validationError");
7
+ break;
8
+ case "401":
9
+ message = i18n.t("errors.unauthorized");
10
+ handleUnauthorized && handleUnauthorized();
11
+ break;
12
+ case "500":
13
+ default:
14
+ message = i18n.t("errors.serverError");
15
+ break;
16
+ }
17
+
18
+ return message;
19
+ };
@@ -0,0 +1,21 @@
1
+ import { API_ENDPOINTS } from "database/config";
2
+
3
+ export const noloQueryRequest = async (queryConfig: any) => {
4
+ const { server } = queryConfig;
5
+ const { queryUserId, options } = queryConfig;
6
+
7
+ const queryParams = new URLSearchParams({
8
+ limit: options.limit?.toString() ?? "",
9
+ });
10
+ const url = `${API_ENDPOINTS.DATABASE}/query/${queryUserId}?${queryParams}`;
11
+ const fullUrl = server + url;
12
+ let headers = {
13
+ "Content-Type": "application/json",
14
+ };
15
+ const body = JSON.stringify(options.condition);
16
+ return fetch(fullUrl, {
17
+ method: "POST",
18
+ headers,
19
+ body,
20
+ });
21
+ };
@@ -0,0 +1,21 @@
1
+ // packages/database/config.ts
2
+
3
+ // 这个文件现在是前后端通用的,不包含任何后端模块如 'fs' 或 'path'。
4
+ export const API_VERSION = "/api/v1";
5
+ export const SERVERS = {
6
+ MAIN: "https://nolo.chat",
7
+ US: "https://us.nolo.chat",
8
+ } as const;
9
+ export const NOLO_CLUSTER_SERVERS = Object.values(SERVERS);
10
+
11
+ export const API_ENDPOINTS = {
12
+ DATABASE: `${API_VERSION}/db`,
13
+ SHARE: `${API_VERSION}/share`,
14
+ USERS: `${API_VERSION}/users`,
15
+ WEATHER: `${API_VERSION}/weather`,
16
+ HI: `${API_VERSION}/hi`,
17
+ CHAT: `${API_VERSION}/chat`,
18
+ EXECUTE_SQL: `${API_VERSION}/sqlite/execute_sql`,
19
+ // --- 新增端点 ---
20
+ TRANSACTIONS: `${API_VERSION}/transactions`,
21
+ };
@@ -0,0 +1 @@
1
+ export { readAndWait, patch, upsert, write } from "./dbSlice";