nolo-cli 0.1.39 → 0.1.41
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/README.md +4 -4
- package/agent-runtime/dialogWritePlan.ts +39 -0
- package/agent-runtime/hostAdapter.ts +4 -0
- package/agent-runtime/hybridRecordStore.ts +5 -3
- package/agent-runtime/index.ts +15 -0
- package/agent-runtime/localLoop.ts +87 -7
- package/agent-runtime/localToolPolicy.ts +2 -13
- package/agent-runtime/localWorkspaceTools.ts +42 -110
- package/agent-runtime/runtimeToolPolicy.ts +2 -11
- package/agent-runtime/runtimeToolSurface.ts +198 -0
- package/agent-runtime/types.ts +1 -0
- package/agentAliases.ts +2 -1
- package/agentRunCommand.ts +10 -32
- package/agentRuntimeLocal.ts +1 -0
- package/ai/agent/_executeModel.ts +146 -0
- package/ai/agent/agentSlice.ts +556 -1
- package/ai/agent/appWorkingMemory.ts +126 -0
- package/ai/agent/buildEditingContext.ts +378 -0
- package/ai/agent/buildSystemPrompt.ts +585 -0
- package/ai/agent/canvasEditingContext.ts +49 -0
- package/ai/agent/cleanAgentMessages.ts +140 -0
- package/ai/agent/cliChatClient.ts +153 -0
- package/ai/agent/cliExecutor.ts +314 -40
- package/ai/agent/contextCompiler.ts +107 -0
- package/ai/agent/contextLayerContract.ts +44 -0
- package/ai/agent/createAgentSchema.ts +272 -0
- package/ai/agent/currentTimeContext.ts +65 -0
- package/ai/agent/executeToolCall.ts +58 -0
- package/ai/agent/fetchAgentContexts.ts +42 -0
- package/ai/agent/generatePrompt.ts +3 -0
- package/ai/agent/getFullChatContextKeys.ts +168 -0
- package/ai/agent/guidedCreation/capabilities.ts +84 -0
- package/ai/agent/guidedCreation/types.ts +38 -0
- package/ai/agent/isLiveAudioOnlyAgent.ts +26 -0
- package/ai/agent/liveAudioModel.ts +2 -0
- package/ai/agent/pageBuilderHandoffRules.ts +75 -0
- package/ai/agent/persistMessageWithFixedId.ts +37 -0
- package/ai/agent/planSlice.ts +259 -0
- package/ai/agent/referenceUtils.ts +229 -0
- package/ai/agent/runAgentBackground.ts +310 -0
- package/ai/agent/runAgentClientLoop.ts +164 -0
- package/ai/agent/runtimeGuidance.ts +116 -0
- package/ai/agent/serverOrigin.ts +7 -0
- package/ai/agent/startParallelAgentStreams.ts +424 -0
- package/ai/agent/startupProtocol.ts +53 -0
- package/ai/agent/streamAgentChatTurn.ts +2001 -0
- package/ai/agent/streamAgentChatTurnUtils.ts +860 -0
- package/ai/agent/toolFailureGuard.ts +99 -0
- package/ai/agent/types.ts +73 -0
- package/ai/agent/utils/imageOutput.ts +37 -0
- package/ai/agent/utils/publicImageAgentMode.ts +26 -0
- package/ai/ai.locale.ts +1095 -0
- package/ai/chat/accumulateToolCallChunks.ts +126 -0
- package/ai/chat/fetchUtils.ts +143 -0
- package/ai/chat/inlineImageUrlsForCustomProvider.ts +117 -0
- package/ai/chat/parseApiError.ts +67 -0
- package/ai/chat/parseMultilineSSE.ts +95 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +897 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +720 -0
- package/ai/chat/serverProxyRetry.ts +102 -0
- package/ai/chat/shouldUseServerProxy.ts +18 -0
- package/ai/chat/streamRetry.ts +46 -0
- package/ai/chat/updateTotalUsage.ts +89 -0
- package/ai/context/buildReferenceContext.ts +437 -0
- package/ai/context/retention.ts +165 -0
- package/ai/context/tokenUtils.ts +78 -0
- package/ai/llm/agentCapabilities.ts +74 -0
- package/ai/llm/deepinfra.ts +79 -0
- package/ai/llm/fireworks.ts +68 -0
- package/ai/llm/generateRequestBody.ts +165 -0
- package/ai/llm/getModelContextWindow.ts +93 -0
- package/ai/llm/getPricing.ts +252 -0
- package/ai/llm/imagePricing.ts +66 -0
- package/ai/llm/isResponseAPIModel.ts +13 -0
- package/ai/llm/kimi.ts +23 -0
- package/ai/llm/mimo.ts +99 -0
- package/ai/llm/mistral.ts +21 -0
- package/ai/llm/models.ts +45 -0
- package/ai/llm/openrouterModels.ts +6 -0
- package/ai/llm/providers.ts +313 -0
- package/ai/llm/reasoningModels.ts +27 -0
- package/ai/llm/types.ts +82 -0
- package/ai/llm/usageRequestOptions.ts +59 -0
- package/ai/memory/queryShared.ts +161 -0
- package/ai/memory/recentRelationshipRecap.ts +247 -0
- package/ai/memory/remember.ts +167 -0
- package/ai/memory/store.ts +20 -0
- package/ai/memory/storeShared.ts +76 -0
- package/ai/memory/types.ts +46 -0
- package/ai/memory/understanding.ts +349 -0
- package/ai/memory/understandingGreeting.ts +264 -0
- package/ai/policy/personalizationDialog.ts +333 -0
- package/ai/policy/runtimePolicy.ts +454 -0
- package/ai/policy/selfUpdateFields.ts +48 -0
- package/ai/policy/types.ts +64 -0
- package/ai/skills/referenceRuntime.ts +274 -0
- package/ai/skills/skillDiagnostics.ts +251 -0
- package/ai/skills/skillDocBuilder.ts +139 -0
- package/ai/skills/skillSummaryMarker.ts +26 -0
- package/ai/token/calculatePrice.ts +576 -0
- package/ai/token/missingUsageEstimate.ts +42 -0
- package/ai/token/normalizeUsage.ts +124 -0
- package/ai/token/openaiImageGenerationUsage.ts +56 -0
- package/ai/token/prepareTokenUsageData.ts +93 -0
- package/ai/token/resolveBillingTarget.ts +52 -0
- package/ai/token/saveTokenRecord.ts +53 -0
- package/ai/token/types.ts +103 -0
- package/ai/tools/agent/agentTools.ts +235 -0
- package/ai/tools/agent/agentUpdateShared.ts +311 -0
- package/ai/tools/agent/callAgentTool.ts +139 -0
- package/ai/tools/agent/createAgentTool.ts +512 -0
- package/ai/tools/agent/createDialogTool.ts +69 -0
- package/ai/tools/agent/createSkillAgentTool.ts +62 -0
- package/ai/tools/agent/parallelBudget.ts +221 -0
- package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +154 -0
- package/ai/tools/agent/runLlmTool.ts +96 -0
- package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
- package/ai/tools/agent/skillAgentArgs.ts +106 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +123 -0
- package/ai/tools/agent/updateAgentTool.ts +96 -0
- package/ai/tools/agent/updateSelfTool.ts +113 -0
- package/ai/tools/amazonProductScraperTool.ts +86 -0
- package/ai/tools/apifyActorClient.ts +45 -0
- package/ai/tools/appEditGuard.ts +372 -0
- package/ai/tools/appReadSnapshot.ts +153 -0
- package/ai/tools/appTools.ts +1549 -0
- package/ai/tools/applyEditTool.ts +259 -0
- package/ai/tools/applyLineEditsTool.ts +315 -0
- package/ai/tools/browserTools/click.ts +33 -0
- package/ai/tools/browserTools/closeSession.ts +29 -0
- package/ai/tools/browserTools/common.ts +27 -0
- package/ai/tools/browserTools/openSession.ts +48 -0
- package/ai/tools/browserTools/readContent.ts +38 -0
- package/ai/tools/browserTools/selectOption.ts +46 -0
- package/ai/tools/browserTools/typeText.ts +42 -0
- package/ai/tools/category/createCategoryTool.ts +66 -0
- package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
- package/ai/tools/category/updateContentCategoryTool.ts +75 -0
- package/ai/tools/cfBrowserTools.ts +319 -0
- package/ai/tools/cfSpeechToTextTool.ts +49 -0
- package/ai/tools/checkEnvTool.ts +102 -0
- package/ai/tools/cloudflareCrawlTool.ts +289 -0
- package/ai/tools/codeSearchTool.ts +123 -0
- package/ai/tools/codeTools.ts +101 -0
- package/ai/tools/convertMarxistsBookTool.ts +103 -0
- package/ai/tools/createDocTool.ts +132 -0
- package/ai/tools/createSkillDocTool.ts +155 -0
- package/ai/tools/createWorkflowTool.ts +154 -0
- package/ai/tools/delayTool.ts +31 -0
- package/ai/tools/deleteSpacesTool.ts +325 -0
- package/ai/tools/deleteSpacesToolModel.ts +159 -0
- package/ai/tools/devReloadUtils.ts +29 -0
- package/ai/tools/dialogMessageSearch.ts +137 -0
- package/ai/tools/doctorSkillTool.ts +72 -0
- package/ai/tools/ecommerceScraperTool.ts +86 -0
- package/ai/tools/emailTools.ts +549 -0
- package/ai/tools/evalSkillTool.ts +92 -0
- package/ai/tools/exaSearchTool.ts +63 -0
- package/ai/tools/execShellTool.ts +266 -0
- package/ai/tools/executeSqlTool.ts +192 -0
- package/ai/tools/fetchWebpageSupport.ts +333 -0
- package/ai/tools/fetchWebpageTool.ts +97 -0
- package/ai/tools/geminiImagePreviewTool.ts +386 -0
- package/ai/tools/generateDocxTool.ts +215 -0
- package/ai/tools/googleSearchScraperTool.ts +106 -0
- package/ai/tools/importDataTool.ts +133 -0
- package/ai/tools/importSkillTool.ts +162 -0
- package/ai/tools/index.ts +2308 -0
- package/ai/tools/jdProductScraperTool.ts +821 -0
- package/ai/tools/listUserSpacesTool.ts +113 -0
- package/ai/tools/localFilesTool.ts +276 -0
- package/ai/tools/marxistsOfflineBook.ts +253 -0
- package/ai/tools/modelUsageTools.ts +206 -0
- package/ai/tools/noloWorkspaceReadTools.ts +467 -0
- package/ai/tools/olmOcrTool.ts +33 -0
- package/ai/tools/openaiImageTool.ts +267 -0
- package/ai/tools/prepareTools.ts +23 -0
- package/ai/tools/readDocTool.ts +88 -0
- package/ai/tools/readFileTool.ts +214 -0
- package/ai/tools/readTool.ts +194 -0
- package/ai/tools/readXPostTool.ts +229 -0
- package/ai/tools/readXhsProfileTool.ts +627 -0
- package/ai/tools/rememberMemoryTool.ts +84 -0
- package/ai/tools/remotionVideoTool.ts +151 -0
- package/ai/tools/searchDialogMessagesTool.ts +222 -0
- package/ai/tools/searchWorkspaceTool.ts +259 -0
- package/ai/tools/skillFollowup.ts +86 -0
- package/ai/tools/surfWeatherTool.ts +169 -0
- package/ai/tools/table/addTableRowTool.ts +217 -0
- package/ai/tools/table/createTableTool.ts +315 -0
- package/ai/tools/table/rowTools.ts +386 -0
- package/ai/tools/table/schemaTools.ts +244 -0
- package/ai/tools/table/shareTableTool.ts +148 -0
- package/ai/tools/table/toolShared.ts +129 -0
- package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
- package/ai/tools/toolApiClient.ts +279 -0
- package/ai/tools/toolResultError.ts +42 -0
- package/ai/tools/toolRunSlice.ts +303 -0
- package/ai/tools/toolSchemaCompatibility.ts +53 -0
- package/ai/tools/uiAskChoiceTool.ts +104 -0
- package/ai/tools/updateContentTitleTool.ts +84 -0
- package/ai/tools/updateDocTool.ts +105 -0
- package/ai/tools/updateUserPreferenceProfileTool.ts +153 -0
- package/ai/tools/wereadGatewayTool.ts +152 -0
- package/ai/tools/whisperTool.ts +76 -0
- package/ai/tools/writeFileTool.ts +213 -0
- package/ai/tools/youtubeScraperTool.ts +116 -0
- package/ai/tools/ziweiChartTool.ts +678 -0
- package/ai/types.ts +55 -0
- package/ai/workflow/workflowExecutor.ts +323 -0
- package/ai/workflow/workflowSlice.ts +73 -0
- package/ai/workflow/workflowTypes.ts +106 -0
- package/app/actions/syncAppRecord.ts +73 -0
- package/app/constants/animationSets.ts +21 -0
- package/app/constants/appEditor.ts +99 -0
- package/app/favorite/favoriteSlice.ts +690 -0
- package/app/favorite/type.ts +43 -0
- package/app/hooks/deleteDbKey.ts +233 -0
- package/app/i18n/client.ts +13 -0
- package/app/i18n/i18n.base.ts +14 -0
- package/app/i18n/i18n.config.ts +77 -0
- package/app/i18n/index.ts +15 -0
- package/app/i18n/mapLanguage.ts +11 -0
- package/app/i18n/translations/appBuilder.locale.ts +453 -0
- package/app/i18n/translations/error.locale.ts +246 -0
- package/app/i18n/translations/interface.locale.ts +2174 -0
- package/app/i18n/translations/pricing.locale.ts +397 -0
- package/app/i18n/translations/privacy.locale.ts +168 -0
- package/app/i18n/translations/seo.locale.ts +129 -0
- package/app/i18n/translations/terms.locale.ts +124 -0
- package/app/i18n/types.ts +6 -0
- package/app/notifications/model.ts +77 -0
- package/app/notifications/notificationSlice.ts +141 -0
- package/app/reducer.ts +36 -0
- package/app/settings/dbActionThunks.ts +34 -0
- package/app/settings/defaultSpacePreference.ts +87 -0
- package/app/settings/settingSlice.tsx +1212 -0
- package/app/settings/settingsRecord.ts +8 -0
- package/app/stateViews/runtime.ts +90 -0
- package/app/store.ts +55 -0
- package/app/theme/colors.ts +280 -0
- package/app/theme/fontPreference.ts +77 -0
- package/app/theme/theme.config.ts +120 -0
- package/app/theme/themeModeBootstrap.ts +123 -0
- package/app/types/appSummary.ts +114 -0
- package/app/types.ts +358 -0
- package/app/utils/appKeys.ts +55 -0
- package/app/utils/async.ts +28 -0
- package/app/utils/desktopAgentRuntimeTurnClient.ts +73 -0
- package/app/utils/env.ts +17 -0
- package/app/utils/fileUtils.ts +229 -0
- package/app/utils/imageUtils.ts +274 -0
- package/app/utils/myContentItems.ts +289 -0
- package/app/utils/retryAfter.ts +36 -0
- package/app/utils/retryFetch.ts +61 -0
- package/app/utils/toast.ts +3 -0
- package/auth/action/signUpAction.ts +227 -0
- package/auth/adminPermissions.ts +18 -0
- package/auth/authSlice.ts +531 -0
- package/auth/client/loginRequest.ts +14 -0
- package/auth/resetAuthScopedClientState.ts +27 -0
- package/auth/routes.ts +178 -0
- package/auth/token.ts +63 -0
- package/auth/types.ts +40 -0
- package/chat/chat.locale.ts +813 -0
- package/chat/dialog/actions/addCybotAction.ts +52 -0
- package/chat/dialog/actions/addReferenceKeysAction.ts +62 -0
- package/chat/dialog/actions/builtinDialogLlm.ts +35 -0
- package/chat/dialog/actions/cleanupCliSession.ts +40 -0
- package/chat/dialog/actions/createDialogAction.ts +379 -0
- package/chat/dialog/actions/createScheduledTaskAction.ts +78 -0
- package/chat/dialog/actions/handleSendMessageAction.ts +197 -0
- package/chat/dialog/actions/handleSendMessageResolver.ts +27 -0
- package/chat/dialog/actions/removeCybotAction.ts +6 -0
- package/chat/dialog/actions/removeDialogAgentAction.ts +44 -0
- package/chat/dialog/actions/replacePrimaryCybotAction.ts +6 -0
- package/chat/dialog/actions/setPrimaryDialogAgentAction.ts +52 -0
- package/chat/dialog/actions/updateDialogSummaryAction.ts +430 -0
- package/chat/dialog/actions/updateDialogTitleAction.ts +151 -0
- package/chat/dialog/actions/updateDialogTitlePolicy.ts +30 -0
- package/chat/dialog/actions/updateTokensAction.ts +233 -0
- package/chat/dialog/dialogAgents.ts +72 -0
- package/chat/dialog/dialogSlice.ts +872 -0
- package/chat/dialog/dialogTitle.ts +81 -0
- package/chat/dialog/dialogTokenStats.ts +12 -0
- package/chat/dialog/dialogUrl.ts +6 -0
- package/chat/dialog/goal.ts +108 -0
- package/chat/dialog/objectAssistantRegistry.ts +286 -0
- package/chat/dialog/schedule.ts +17 -0
- package/chat/messages/fetchAndCacheMessages.ts +119 -0
- package/chat/messages/fetchConvMsgs.ts +51 -0
- package/chat/messages/fileUrl.ts +13 -0
- package/chat/messages/messageAgent.ts +8 -0
- package/chat/messages/messageContent.ts +199 -0
- package/chat/messages/messageContract.ts +88 -0
- package/chat/messages/messageSlice.ts +1367 -0
- package/chat/messages/toolThunks.ts +625 -0
- package/cli/agentAliases.ts +2 -1
- package/cli/agentRunCommand.ts +10 -32
- package/cli/agentRuntimeLocal.ts +1 -0
- package/cli/client/agentRun.ts +67 -95
- package/cli/client/localRuntimeAdapter.ts +323 -54
- package/cli/commandRegistry.ts +2 -2
- package/cli/machineWsRunDispatch.ts +51 -6
- package/cli/offlineMarxistsAgentCommand.ts +6 -4
- package/client/agentConfigResolver.test.ts +2 -2
- package/client/agentRun.test.ts +175 -57
- package/client/agentRun.ts +67 -95
- package/client/localRuntimeAdapter.test.ts +682 -37
- package/client/localRuntimeAdapter.ts +323 -54
- package/client/localRuntimeDryRun.test.ts +23 -9
- package/client/localToolPolicy.test.ts +4 -4
- package/commandRegistry.ts +2 -2
- package/core/builtinAgents.ts +209 -0
- package/core/config.ts +15 -0
- package/core/crypto.ts +51 -0
- package/core/generateKeyPairFromSeedV1.ts +15 -0
- package/core/generateMainKey.ts +22 -0
- package/core/init.ts +41 -0
- package/core/localOrigins.ts +8 -0
- package/core/password.ts +13 -0
- package/core/prefix.ts +23 -0
- package/create/editor/transforms/fromMarkdown/index.ts +260 -0
- package/create/editor/transforms/fromMarkdown/inline.ts +285 -0
- package/create/editor/transforms/fromMarkdown/table.ts +130 -0
- package/create/editor/transforms/markdownToSlate.ts +24 -0
- package/create/editor/transforms/slateToRenderMarkdown.ts +218 -0
- package/create/editor/transforms/slateToSimplifiedMarkdown.ts +224 -0
- package/create/editor/transforms/slateToText.ts +33 -0
- package/create/editor/utils/slateUtils.ts +233 -0
- package/create/space/addSpaceAction.ts +193 -0
- package/create/space/category/categoryActions.ts +427 -0
- package/create/space/changeSpaceAction.ts +36 -0
- package/create/space/constants.ts +8 -0
- package/create/space/content/addContentAction.ts +116 -0
- package/create/space/content/contentThunks.ts +90 -0
- package/create/space/content/deleteContentFromSpaceAction.ts +187 -0
- package/create/space/content/deleteMultipleContentAction.ts +112 -0
- package/create/space/content/moveContentAction.ts +285 -0
- package/create/space/content/updateContentCategoryAction.ts +154 -0
- package/create/space/content/updateContentTitleAction.ts +151 -0
- package/create/space/content/uploadAndAddFileToSpaceAction.ts +98 -0
- package/create/space/contentKeyUtils.ts +117 -0
- package/create/space/deleteSpaceAction.ts +276 -0
- package/create/space/fetchSpaceAction.ts +70 -0
- package/create/space/fetchSpaceSidebarStateAction.ts +26 -0
- package/create/space/loadDefaultSpaceAction.ts +57 -0
- package/create/space/member/addMemberAction.ts +79 -0
- package/create/space/member/fetchUserSpaceMembershipsAction.ts +386 -0
- package/create/space/member/memberThunks.ts +47 -0
- package/create/space/member/removeMemberAction.ts +67 -0
- package/create/space/resolvePreferredSpaceId.ts +113 -0
- package/create/space/sidebarVisibleTypes.ts +241 -0
- package/create/space/space.locale.ts +1087 -0
- package/create/space/spaceAccess.ts +182 -0
- package/create/space/spaceCollapsedState.ts +54 -0
- package/create/space/spaceKeys.ts +65 -0
- package/create/space/spaceSlice.ts +328 -0
- package/create/space/spaceThunks.ts +410 -0
- package/create/space/types.ts +27 -0
- package/create/space/updateSpaceAction.ts +107 -0
- package/create/space/utils/permissions.ts +47 -0
- package/create/types.ts +21 -0
- package/database/actions/agentReadResolution.ts +33 -0
- package/database/actions/common.ts +243 -0
- package/database/actions/deleteFile.ts +40 -0
- package/database/actions/fileContent.ts +125 -0
- package/database/actions/patch.ts +155 -0
- package/database/actions/read.ts +358 -0
- package/database/actions/readAndWait.ts +260 -0
- package/database/actions/readRequestManager.ts +120 -0
- package/database/actions/readResolution.ts +125 -0
- package/database/actions/remove.ts +94 -0
- package/database/actions/replication.ts +477 -0
- package/database/actions/upload.ts +186 -0
- package/database/actions/upsert.ts +56 -0
- package/database/actions/write.ts +146 -0
- package/database/authority/ownerKey.ts +80 -0
- package/database/authority/recordAuthority.ts +67 -0
- package/database/authority/userAuthorityRegistry.ts +54 -0
- package/database/client/fetchUserData.ts +61 -0
- package/database/dbActionThunks.ts +1 -0
- package/database/dbSlice.ts +149 -0
- package/database/fileRing.ts +51 -0
- package/database/fileStorage.ts +232 -0
- package/database/fileUrl.ts +34 -0
- package/database/keys.ts +767 -0
- package/database/queryPrefixes.ts +14 -0
- package/database/requests.ts +443 -0
- package/database/runtimeServerContext.ts +54 -0
- package/database/server/levelAuthorityStore.ts +20 -1
- package/database/tenantPlacement.ts +57 -0
- package/database/userPreferenceRegister.ts +131 -0
- package/database/utils/ulid.ts +1 -0
- package/integrations/anthropic/anthropicModels.ts +47 -0
- package/integrations/deepseek/models.ts +37 -0
- package/integrations/google/models.ts +341 -0
- package/integrations/openai/chatCompletionStreamMode.ts +10 -0
- package/integrations/openai/filterAndCleanMessages.ts +284 -0
- package/integrations/openai/flexTier.ts +102 -0
- package/integrations/openai/generateOpenAIRequestBody.ts +274 -0
- package/integrations/openai/generateResponseRequestBody.ts +77 -0
- package/integrations/openai/models.ts +271 -0
- package/integrations/openai/providerBodyCompatibility.ts +23 -0
- package/integrations/openai/responsesHelpers.ts +297 -0
- package/integrations/x-reader/types.ts +109 -0
- package/integrations/xai/models.ts +13 -0
- package/integrations/xhs-reader/analyze/profileAnalyzer.ts +205 -0
- package/integrations/xhs-reader/backends/playwrightProfileCollector.ts +624 -0
- package/integrations/xhs-reader/bridge/readXhsProfileWithBridge.ts +227 -0
- package/integrations/xhs-reader/client/xhsWebApiClient.ts +229 -0
- package/integrations/xhs-reader/normalize.ts +390 -0
- package/integrations/xhs-reader/orchestrator.ts +178 -0
- package/integrations/xhs-reader/redaction.ts +68 -0
- package/integrations/xhs-reader/types.ts +244 -0
- package/integrations/xhs-reader/url.ts +57 -0
- package/machineWsRunDispatch.ts +51 -6
- package/offlineMarxistsAgentCommand.ts +6 -4
- package/package.json +430 -3
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/types.ts +17 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +193 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/server/handlers/agentRun/types.ts +104 -0
- package/share/action.ts +225 -0
- package/share/helpers.ts +302 -0
- package/share/keys.ts +243 -0
- package/share/publicSharePolicy.ts +27 -0
- package/share/shareSlice.ts +53 -0
- package/share/types.ts +100 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// 文件路径: database/actions/write.ts
|
|
2
|
+
|
|
3
|
+
import type { AppThunkApi } from "../../app/store";
|
|
4
|
+
import { getRuntimeServerContext } from "../runtimeServerContext";
|
|
5
|
+
import { DataType } from "../../create/types";
|
|
6
|
+
import { normalizeTimeFields, logger } from "./common";
|
|
7
|
+
import {
|
|
8
|
+
resolveAuthorityReplicationServers,
|
|
9
|
+
scheduleWriteReplication,
|
|
10
|
+
} from "./replication";
|
|
11
|
+
import { toast } from "../../app/utils/toast";
|
|
12
|
+
|
|
13
|
+
const SPACE_MEMBER_PREFIX = "space-member-";
|
|
14
|
+
|
|
15
|
+
const getMemberUserIdFromSpaceMemberKey = (dbKey: string): string | null => {
|
|
16
|
+
if (!dbKey.startsWith(SPACE_MEMBER_PREFIX)) return null;
|
|
17
|
+
const rest = dbKey.slice(SPACE_MEMBER_PREFIX.length);
|
|
18
|
+
const lastDash = rest.lastIndexOf("-");
|
|
19
|
+
if (lastDash <= 0) return null;
|
|
20
|
+
return rest.slice(0, lastDash);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 辅助函数:保存到本地 DB
|
|
24
|
+
const saveToClientDb = async (
|
|
25
|
+
clientDb: any,
|
|
26
|
+
dbKey: string,
|
|
27
|
+
data: any
|
|
28
|
+
): Promise<void> => {
|
|
29
|
+
if (!clientDb) {
|
|
30
|
+
logger.error({ dbKey }, "Client database is undefined in saveToClientDb");
|
|
31
|
+
throw new Error("Client database instance is required");
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await clientDb.put(dbKey, data);
|
|
35
|
+
logger.debug({ dbKey }, "Data saved successfully to local database.");
|
|
36
|
+
} catch (err: any) {
|
|
37
|
+
logger.error({ err, dbKey }, "Failed to save data to local database");
|
|
38
|
+
throw new Error(`Local database put failed for ${dbKey}: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Write Action: 写入新数据项。
|
|
44
|
+
* 1. 验证数据类型。
|
|
45
|
+
* 2. 规范化数据(添加时间戳、dbKey、userId)。
|
|
46
|
+
* 3. 保存数据到本地数据库。
|
|
47
|
+
* 4. 若在线,异步将完整数据写入所有服务器。
|
|
48
|
+
*/
|
|
49
|
+
export const writeAction = async (
|
|
50
|
+
writeConfig: { data: any; customKey: string; userId?: string },
|
|
51
|
+
thunkApi: AppThunkApi
|
|
52
|
+
): Promise<any> => {
|
|
53
|
+
const { db: clientDb } = thunkApi.extra;
|
|
54
|
+
if (!clientDb) {
|
|
55
|
+
throw new Error("Client database instance is required in writeAction");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const state = thunkApi.getState();
|
|
59
|
+
const { currentServer, syncServers, currentUserId } =
|
|
60
|
+
getRuntimeServerContext(state);
|
|
61
|
+
|
|
62
|
+
const { data, customKey } = writeConfig;
|
|
63
|
+
const userId = writeConfig.userId || currentUserId;
|
|
64
|
+
const isSpaceMemberRecord = customKey.startsWith(SPACE_MEMBER_PREFIX);
|
|
65
|
+
const recordUserId = isSpaceMemberRecord
|
|
66
|
+
? data.userId || getMemberUserIdFromSpaceMemberKey(customKey) || userId
|
|
67
|
+
: userId;
|
|
68
|
+
|
|
69
|
+
// 1. 基础参数校验
|
|
70
|
+
if (!data || !customKey) {
|
|
71
|
+
const errorMsg =
|
|
72
|
+
"Invalid arguments for writeAction: data and customKey are required.";
|
|
73
|
+
logger.error(errorMsg, { writeConfig });
|
|
74
|
+
toast.error(errorMsg);
|
|
75
|
+
throw new Error(errorMsg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 2. 类型校验(保持原有行为:非法类型只告警,不阻塞)
|
|
79
|
+
const VALID_TYPES = [
|
|
80
|
+
DataType.MSG,
|
|
81
|
+
DataType.CYBOT,
|
|
82
|
+
DataType.DOC,
|
|
83
|
+
DataType.DIALOG,
|
|
84
|
+
DataType.NOTIFICATION,
|
|
85
|
+
DataType.TOKEN,
|
|
86
|
+
DataType.TRANSACTION,
|
|
87
|
+
DataType.SPACE,
|
|
88
|
+
DataType.SETTING,
|
|
89
|
+
DataType.TABLE,
|
|
90
|
+
DataType.TABLE_ROW,
|
|
91
|
+
DataType.EMAIL,
|
|
92
|
+
];
|
|
93
|
+
if (!data.type || !VALID_TYPES.includes(data.type)) {
|
|
94
|
+
logger.warn(
|
|
95
|
+
`Invalid data type "${data.type}" for writeAction with key ${customKey}. Proceeding anyway.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// 3. 规范化数据(时间字段 / dbKey / userId)
|
|
101
|
+
const willSaveData = normalizeTimeFields({
|
|
102
|
+
...data,
|
|
103
|
+
dbKey: customKey,
|
|
104
|
+
userId: recordUserId,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// 4. 本地保存
|
|
108
|
+
await saveToClientDb(clientDb, customKey, willSaveData);
|
|
109
|
+
|
|
110
|
+
// 5. 计算远程服务器列表(currentServer + syncServers,带去重 + 离线判断)
|
|
111
|
+
const servers = resolveAuthorityReplicationServers({
|
|
112
|
+
currentServer,
|
|
113
|
+
syncServers,
|
|
114
|
+
dbKey: customKey,
|
|
115
|
+
record: willSaveData,
|
|
116
|
+
state,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const serverWriteConfig = {
|
|
120
|
+
data: willSaveData,
|
|
121
|
+
customKey,
|
|
122
|
+
userId: recordUserId,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// 6. 后台异步同步到远程(若在线且有可用服务器)
|
|
126
|
+
if (servers.length > 0) {
|
|
127
|
+
logger.debug(
|
|
128
|
+
`[writeAction] Initiating background sync for key: ${customKey} to ${servers.length} servers.`
|
|
129
|
+
);
|
|
130
|
+
scheduleWriteReplication(servers, serverWriteConfig, state);
|
|
131
|
+
} else {
|
|
132
|
+
logger.warn(
|
|
133
|
+
"[writeAction] No available servers, data only saved locally.",
|
|
134
|
+
{ customKey }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return willSaveData;
|
|
139
|
+
} catch (error: any) {
|
|
140
|
+
const errorMessage = `Write action failed for ${customKey}: ${error?.message || "Unknown error"
|
|
141
|
+
}`;
|
|
142
|
+
logger.error("[writeAction] Error:", error);
|
|
143
|
+
toast.error(`Failed to save data for ${customKey}.`);
|
|
144
|
+
throw new Error(errorMessage);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const USER_OWNED_SECOND_SEGMENT_PREFIXES = new Set([
|
|
2
|
+
"agent",
|
|
3
|
+
"cybot",
|
|
4
|
+
"dialog",
|
|
5
|
+
"page",
|
|
6
|
+
"doc",
|
|
7
|
+
"notification",
|
|
8
|
+
"email",
|
|
9
|
+
"meta",
|
|
10
|
+
"row",
|
|
11
|
+
"view",
|
|
12
|
+
"file",
|
|
13
|
+
"job",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const PUBLIC_OWNER_SENTINELS = new Set(["pub", "id", "stats", "index"]);
|
|
17
|
+
|
|
18
|
+
const cleanSegment = (value: string | undefined): string | null => {
|
|
19
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
20
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const normalizeCandidateOwners = (
|
|
24
|
+
candidateOwnerUserIds: Array<string | null | undefined> | undefined
|
|
25
|
+
): string[] => {
|
|
26
|
+
const out: string[] = [];
|
|
27
|
+
for (const candidate of candidateOwnerUserIds ?? []) {
|
|
28
|
+
const normalized = cleanSegment(candidate ?? undefined);
|
|
29
|
+
if (!normalized || PUBLIC_OWNER_SENTINELS.has(normalized)) continue;
|
|
30
|
+
if (!out.includes(normalized)) out.push(normalized);
|
|
31
|
+
}
|
|
32
|
+
return out.sort((left, right) => right.length - left.length);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const resolveCandidateOwnerFromKeyRemainder = (
|
|
36
|
+
remainder: string,
|
|
37
|
+
candidateOwnerUserIds: Array<string | null | undefined> | undefined
|
|
38
|
+
): string | null => {
|
|
39
|
+
const normalizedRemainder = cleanSegment(remainder);
|
|
40
|
+
if (!normalizedRemainder) return null;
|
|
41
|
+
|
|
42
|
+
for (const candidate of normalizeCandidateOwners(candidateOwnerUserIds)) {
|
|
43
|
+
if (
|
|
44
|
+
normalizedRemainder === candidate ||
|
|
45
|
+
normalizedRemainder.startsWith(`${candidate}-`)
|
|
46
|
+
) {
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const parseOwnerUserIdFromDbKey = (
|
|
55
|
+
dbKey: string,
|
|
56
|
+
options: {
|
|
57
|
+
candidateOwnerUserIds?: Array<string | null | undefined>;
|
|
58
|
+
} = {}
|
|
59
|
+
): string | null => {
|
|
60
|
+
const normalized = cleanSegment(dbKey);
|
|
61
|
+
if (!normalized) return null;
|
|
62
|
+
|
|
63
|
+
const [prefix] = normalized.split("-");
|
|
64
|
+
if (!prefix) return null;
|
|
65
|
+
if (!USER_OWNED_SECOND_SEGMENT_PREFIXES.has(prefix)) return null;
|
|
66
|
+
|
|
67
|
+
const ownerAndRest = normalized.slice(prefix.length + 1);
|
|
68
|
+
const candidateOwner = resolveCandidateOwnerFromKeyRemainder(
|
|
69
|
+
ownerAndRest,
|
|
70
|
+
options.candidateOwnerUserIds
|
|
71
|
+
);
|
|
72
|
+
if (candidateOwner) return candidateOwner;
|
|
73
|
+
|
|
74
|
+
const [owner] = ownerAndRest.split("-");
|
|
75
|
+
const ownerUserId = cleanSegment(owner);
|
|
76
|
+
if (!ownerUserId) return null;
|
|
77
|
+
if (PUBLIC_OWNER_SENTINELS.has(ownerUserId)) return null;
|
|
78
|
+
|
|
79
|
+
return ownerUserId;
|
|
80
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { parseOwnerUserIdFromDbKey } from "./ownerKey";
|
|
2
|
+
import {
|
|
3
|
+
normalizeAuthorityServerOrigin,
|
|
4
|
+
resolveUserAuthorityServer,
|
|
5
|
+
type UserAuthorityRegistry,
|
|
6
|
+
} from "./userAuthorityRegistry";
|
|
7
|
+
|
|
8
|
+
export type RecordAuthorityInput = {
|
|
9
|
+
dbKey: string;
|
|
10
|
+
record?: Record<string, unknown> | null;
|
|
11
|
+
currentUserId?: string | null;
|
|
12
|
+
currentServer?: string | null;
|
|
13
|
+
userAuthorityRegistry?: UserAuthorityRegistry | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type RecordAuthorityResolution = {
|
|
17
|
+
ownerUserId: string | null;
|
|
18
|
+
authorityServer: string | null;
|
|
19
|
+
serverOrigin: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const normalizeUserId = (value: unknown): string | null => {
|
|
23
|
+
if (typeof value !== "string") return null;
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getRegistryOwnerCandidates = (
|
|
29
|
+
registry: UserAuthorityRegistry | null | undefined
|
|
30
|
+
): string[] =>
|
|
31
|
+
registry && typeof registry === "object" && !Array.isArray(registry)
|
|
32
|
+
? Object.keys(registry)
|
|
33
|
+
: [];
|
|
34
|
+
|
|
35
|
+
export const resolveRecordAuthority = ({
|
|
36
|
+
dbKey,
|
|
37
|
+
record,
|
|
38
|
+
currentUserId,
|
|
39
|
+
currentServer,
|
|
40
|
+
userAuthorityRegistry,
|
|
41
|
+
}: RecordAuthorityInput): RecordAuthorityResolution => {
|
|
42
|
+
const ownerUserId =
|
|
43
|
+
parseOwnerUserIdFromDbKey(dbKey, {
|
|
44
|
+
candidateOwnerUserIds: [
|
|
45
|
+
normalizeUserId(record?.userId),
|
|
46
|
+
normalizeUserId(currentUserId),
|
|
47
|
+
...getRegistryOwnerCandidates(userAuthorityRegistry),
|
|
48
|
+
],
|
|
49
|
+
}) ?? normalizeUserId(record?.userId);
|
|
50
|
+
const serverOrigin = normalizeAuthorityServerOrigin(record?.serverOrigin);
|
|
51
|
+
const explicitAuthority = normalizeAuthorityServerOrigin(record?.authorityServer);
|
|
52
|
+
const registryAuthority = resolveUserAuthorityServer({
|
|
53
|
+
ownerUserId,
|
|
54
|
+
registry: userAuthorityRegistry,
|
|
55
|
+
});
|
|
56
|
+
const currentUserAuthority =
|
|
57
|
+
ownerUserId && ownerUserId === normalizeUserId(currentUserId)
|
|
58
|
+
? normalizeAuthorityServerOrigin(currentServer)
|
|
59
|
+
: null;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
ownerUserId,
|
|
63
|
+
authorityServer:
|
|
64
|
+
explicitAuthority ?? registryAuthority ?? currentUserAuthority ?? serverOrigin,
|
|
65
|
+
serverOrigin,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type UserAuthorityRegistryEntry =
|
|
2
|
+
| string
|
|
3
|
+
| {
|
|
4
|
+
authorityServer?: unknown;
|
|
5
|
+
homeServer?: unknown;
|
|
6
|
+
primaryServer?: unknown;
|
|
7
|
+
servers?: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type UserAuthorityRegistry = Record<string, UserAuthorityRegistryEntry>;
|
|
11
|
+
|
|
12
|
+
export const normalizeAuthorityServerOrigin = (value: unknown): string | null => {
|
|
13
|
+
if (typeof value !== "string") return null;
|
|
14
|
+
const trimmed = value.trim().replace(/\/+$/, "");
|
|
15
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const normalizeUserId = (value: unknown): string | null => {
|
|
19
|
+
if (typeof value !== "string") return null;
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const readEntryAuthorityServer = (
|
|
25
|
+
entry: UserAuthorityRegistryEntry | undefined
|
|
26
|
+
): string | null => {
|
|
27
|
+
if (typeof entry === "string") {
|
|
28
|
+
return normalizeAuthorityServerOrigin(entry);
|
|
29
|
+
}
|
|
30
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
normalizeAuthorityServerOrigin(entry.authorityServer) ??
|
|
36
|
+
normalizeAuthorityServerOrigin(entry.homeServer) ??
|
|
37
|
+
normalizeAuthorityServerOrigin(entry.primaryServer)
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const resolveUserAuthorityServer = ({
|
|
42
|
+
ownerUserId,
|
|
43
|
+
registry,
|
|
44
|
+
}: {
|
|
45
|
+
ownerUserId?: string | null;
|
|
46
|
+
registry?: UserAuthorityRegistry | null;
|
|
47
|
+
}): string | null => {
|
|
48
|
+
const normalizedOwner = normalizeUserId(ownerUserId);
|
|
49
|
+
if (!normalizedOwner || !registry || typeof registry !== "object") {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return readEntryAuthorityServer(registry[normalizedOwner]);
|
|
54
|
+
};
|
|
@@ -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 @@
|
|
|
1
|
+
export { readAndWait, patch, upsert, write } from "./dbSlice";
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// 文件路径: database/dbSlice.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
asyncThunkCreator,
|
|
5
|
+
buildCreateSlice,
|
|
6
|
+
createEntityAdapter,
|
|
7
|
+
type PayloadAction,
|
|
8
|
+
} from "@reduxjs/toolkit";
|
|
9
|
+
import type { RootState } from "../app/store";
|
|
10
|
+
|
|
11
|
+
// Import actions
|
|
12
|
+
import { removeAction } from "./actions/remove";
|
|
13
|
+
import { readAction } from "./actions/read";
|
|
14
|
+
import { readAndWaitAction } from "./actions/readAndWait";
|
|
15
|
+
import { writeAction } from "./actions/write";
|
|
16
|
+
import { patchAction } from "./actions/patch";
|
|
17
|
+
import { upsertAction } from "./actions/upsert";
|
|
18
|
+
import { uploadFileAction } from "./actions/upload";
|
|
19
|
+
import { readFileContentAction } from "./actions/fileContent";
|
|
20
|
+
import { shareResourceAction } from "../share/action";
|
|
21
|
+
|
|
22
|
+
// Use dbKey as the entity's unique identifier
|
|
23
|
+
export const dbAdapter = createEntityAdapter<any>({
|
|
24
|
+
selectId: (entity: any) => entity.dbKey,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Selectors
|
|
28
|
+
export const {
|
|
29
|
+
selectById,
|
|
30
|
+
selectEntities,
|
|
31
|
+
selectAll,
|
|
32
|
+
selectIds,
|
|
33
|
+
selectTotal,
|
|
34
|
+
} = dbAdapter.getSelectors((state: RootState) => state.db);
|
|
35
|
+
|
|
36
|
+
// Initial state
|
|
37
|
+
const initialState = dbAdapter.getInitialState({});
|
|
38
|
+
|
|
39
|
+
// Create slice with async thunks
|
|
40
|
+
const createSliceWithThunks = buildCreateSlice({
|
|
41
|
+
creators: { asyncThunk: asyncThunkCreator },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Slice definition
|
|
45
|
+
const dbSlice = createSliceWithThunks({
|
|
46
|
+
name: "db",
|
|
47
|
+
initialState,
|
|
48
|
+
reducers: (create) => ({
|
|
49
|
+
// Async Thunks
|
|
50
|
+
read: create.asyncThunk(readAction, {
|
|
51
|
+
fulfilled: (state, action) => {
|
|
52
|
+
if (action.payload && Object.keys(action.payload).length > 0) {
|
|
53
|
+
dbAdapter.upsertOne(state, action.payload);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
readAndWait: create.asyncThunk(readAndWaitAction, {
|
|
58
|
+
fulfilled: (state, action) => {
|
|
59
|
+
if (action.payload && Object.keys(action.payload).length > 0) {
|
|
60
|
+
dbAdapter.upsertOne(state, action.payload);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
remove: create.asyncThunk(removeAction, {
|
|
65
|
+
fulfilled: (state, action) => {
|
|
66
|
+
const { dbKey } = action.payload;
|
|
67
|
+
if (dbKey) dbAdapter.removeOne(state, dbKey);
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
write: create.asyncThunk(writeAction, {
|
|
71
|
+
fulfilled: (state, action) => {
|
|
72
|
+
if (
|
|
73
|
+
action.payload &&
|
|
74
|
+
action.payload.dbKey &&
|
|
75
|
+
Object.keys(action.payload).length > 0
|
|
76
|
+
) {
|
|
77
|
+
dbAdapter.upsertOne(state, action.payload);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
patch: create.asyncThunk(patchAction, {
|
|
82
|
+
fulfilled: (state, action) => {
|
|
83
|
+
const { payload } = action;
|
|
84
|
+
if (payload && payload.dbKey && Object.keys(payload).length > 0) {
|
|
85
|
+
dbAdapter.upsertOne(state, payload);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
upsert: create.asyncThunk(upsertAction, {
|
|
90
|
+
fulfilled: (state, action) => {
|
|
91
|
+
if (
|
|
92
|
+
action.payload &&
|
|
93
|
+
action.payload.dbKey &&
|
|
94
|
+
Object.keys(action.payload).length > 0
|
|
95
|
+
) {
|
|
96
|
+
dbAdapter.upsertOne(state, action.payload);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
// 文件上传(avatar / Slate / Space 等统一走这里)
|
|
101
|
+
upload: create.asyncThunk(uploadFileAction, {
|
|
102
|
+
fulfilled: (state, action) => {
|
|
103
|
+
const payload = action.payload;
|
|
104
|
+
if (payload && payload.dbKey && Object.keys(payload).length > 0) {
|
|
105
|
+
dbAdapter.upsertOne(state, payload);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
// 读取文件内容(优先本地 IndexedDB,无状态副作用)
|
|
110
|
+
readFileContent: create.asyncThunk(readFileContentAction, {
|
|
111
|
+
// fulfilled 时不修改 db state;由调用方通过 unwrap() 拿返回值使用
|
|
112
|
+
}),
|
|
113
|
+
share: create.asyncThunk(shareResourceAction, {
|
|
114
|
+
fulfilled: (state, action) => {
|
|
115
|
+
if (action.payload && action.payload.key) {
|
|
116
|
+
// We might want to store the shared object in local DB state too?
|
|
117
|
+
// writeAction already does it. This is just for return value.
|
|
118
|
+
// Actually, writeAction is dispatched inside shareResourceAction.
|
|
119
|
+
// Does writeAction update state? Yes.
|
|
120
|
+
// So here we might not need to do anything extra to state,
|
|
121
|
+
// just define the thunk.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}),
|
|
125
|
+
// SSR 预取:服务端直接注入实体到 db slice,供首屏 hydrate 使用
|
|
126
|
+
upsertSSREntity: create.reducer((state, action: PayloadAction<any>) => {
|
|
127
|
+
if (action.payload && action.payload.dbKey) {
|
|
128
|
+
dbAdapter.upsertOne(state, action.payload);
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Export actions
|
|
135
|
+
export const {
|
|
136
|
+
remove,
|
|
137
|
+
read,
|
|
138
|
+
readAndWait,
|
|
139
|
+
write,
|
|
140
|
+
patch,
|
|
141
|
+
upsert,
|
|
142
|
+
upload,
|
|
143
|
+
readFileContent,
|
|
144
|
+
share,
|
|
145
|
+
upsertSSREntity,
|
|
146
|
+
} = dbSlice.actions;
|
|
147
|
+
|
|
148
|
+
// Export the reducer
|
|
149
|
+
export default dbSlice.reducer;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// 文件路径: database/fileRing.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 简单的 FNV-1a 32bit 字符串哈希
|
|
5
|
+
* - 稳定、实现简单,适合作为 hash ring 的排序依据
|
|
6
|
+
*/
|
|
7
|
+
const fnv1a32 = (str: string): number => {
|
|
8
|
+
let hash = 0x811c9dc5;
|
|
9
|
+
for (let i = 0; i < str.length; i++) {
|
|
10
|
+
hash ^= str.charCodeAt(i);
|
|
11
|
+
hash = (hash * 0x01000193) >>> 0; // 32bit
|
|
12
|
+
}
|
|
13
|
+
return hash >>> 0;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 通用服务器选择函数:
|
|
18
|
+
*
|
|
19
|
+
* 给定一组服务器和一个“key”(可以是 tenantId / userId / orgId / fileId …),
|
|
20
|
+
* 选择 replicaCount 个服务器作为该 key 的“归属服务器”。
|
|
21
|
+
*
|
|
22
|
+
* 规则:
|
|
23
|
+
* - 对每个 server 计算 hash(server + "::" + key)
|
|
24
|
+
* - 按 hash 升序排序
|
|
25
|
+
* - 取前 replicaCount 个
|
|
26
|
+
*
|
|
27
|
+
* 说明:
|
|
28
|
+
* - 这不是严格意义上的一致性哈希环,但分布效果 + 实现复杂度比较均衡;
|
|
29
|
+
* - 之后要支持更多服务器,只需要在配置中把新 server 加入列表即可,
|
|
30
|
+
* 这里不需要改代码。
|
|
31
|
+
*/
|
|
32
|
+
export const chooseServersByKey = (
|
|
33
|
+
allServers: string[],
|
|
34
|
+
key: string,
|
|
35
|
+
replicaCount: number
|
|
36
|
+
): string[] => {
|
|
37
|
+
if (!allServers.length || replicaCount <= 0) return [];
|
|
38
|
+
|
|
39
|
+
const uniqueServers = Array.from(new Set(allServers)).filter((s) => !!s);
|
|
40
|
+
if (uniqueServers.length === 0) return [];
|
|
41
|
+
|
|
42
|
+
const scored = uniqueServers.map((server) => ({
|
|
43
|
+
server,
|
|
44
|
+
score: fnv1a32(`${server}::${key}`),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
scored.sort((a, b) => a.score - b.score);
|
|
48
|
+
|
|
49
|
+
const limit = Math.min(replicaCount, scored.length);
|
|
50
|
+
return scored.slice(0, limit).map((item) => item.server);
|
|
51
|
+
};
|