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.
- package/README.md +9 -2
- package/agent-runtime/hostAdapter.ts +53 -0
- package/agent-runtime/index.ts +28 -0
- package/agent-runtime/localLoop.ts +62 -0
- package/agent-runtime/runtimeDecision.ts +70 -0
- package/agent-runtime/types.ts +87 -0
- package/agentRuntimeCommands.ts +139 -22
- package/agentRuntimeLocal.ts +7 -0
- package/ai/agent/_executeModel.ts +118 -0
- package/ai/agent/agentSlice.ts +544 -1
- package/ai/agent/appWorkingMemory.ts +126 -0
- package/ai/agent/avatarUtils.ts +24 -0
- package/ai/agent/buildEditingContext.ts +373 -0
- package/ai/agent/buildSystemPrompt.ts +532 -0
- package/ai/agent/cleanAgentMessages.ts +140 -0
- package/ai/agent/cliChatClient.ts +119 -0
- package/ai/agent/contextCompiler.ts +107 -0
- package/ai/agent/contextLayerContract.ts +44 -0
- package/ai/agent/createAgentSchema.ts +234 -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/hooks/fetchPublicAgents.ts +133 -0
- package/ai/agent/hooks/useAgentConfig.ts +61 -0
- package/ai/agent/hooks/useAgentDialog.ts +35 -0
- package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
- package/ai/agent/hooks/usePublicAgents.ts +473 -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 +238 -0
- package/ai/agent/runAgentClientLoop.ts +138 -0
- package/ai/agent/runtimeGuidance.ts +97 -0
- package/ai/agent/runtimeServerBase.ts +37 -0
- package/ai/agent/server/fetchPublicAgents.ts +128 -0
- package/ai/agent/startParallelAgentStreams.ts +424 -0
- package/ai/agent/startupProtocol.ts +53 -0
- package/ai/agent/streamAgentChatTurn.ts +1299 -0
- package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
- package/ai/agent/types.ts +71 -0
- package/ai/agent/utils/imageOutput.ts +39 -0
- package/ai/agent/utils/publicImageAgentMode.ts +26 -0
- package/ai/agent/utils/sortUtils.ts +250 -0
- package/ai/agent/web/referencePickerUtils.ts +146 -0
- package/ai/ai.locale.ts +1083 -0
- package/ai/chat/accumulateToolCallChunks.ts +95 -0
- package/ai/chat/fetchUtils.native.ts +276 -0
- package/ai/chat/fetchUtils.ts +153 -0
- package/ai/chat/inlineImageUrlsForCustomProvider.ts +117 -0
- package/ai/chat/parseApiError.ts +64 -0
- package/ai/chat/parseMultilineSSE.ts +95 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +712 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +512 -0
- package/ai/chat/shouldUseServerProxy.ts +18 -0
- package/ai/chat/sseClient.native.ts +91 -0
- package/ai/chat/sseClient.ts +67 -0
- package/ai/chat/streamReader.native.ts +31 -0
- package/ai/chat/streamReader.ts +62 -0
- package/ai/chat/updateTotalUsage.ts +72 -0
- package/ai/context/buildReferenceContext.ts +437 -0
- package/ai/context/calculateContextUsage.ts +133 -0
- package/ai/context/retention.ts +165 -0
- package/ai/context/tokenUtils.ts +78 -0
- package/ai/index.ts +1 -1
- package/ai/llm/agentCapabilities.ts +74 -0
- package/ai/llm/calculateGeminiImageTokens.ts +57 -0
- package/ai/llm/deepinfra.ts +28 -0
- package/ai/llm/fireworks.ts +68 -0
- package/ai/llm/generateRequestBody.ts +165 -0
- package/ai/llm/getModelContextWindow.ts +84 -0
- package/ai/llm/getNoloKey.ts +37 -0
- package/ai/llm/getPricing.ts +232 -0
- package/ai/llm/hooks/useModelPricing.ts +75 -0
- package/ai/llm/imagePricing.ts +66 -0
- package/ai/llm/isResponseAPIModel.ts +13 -0
- package/ai/llm/kimi.ts +18 -0
- package/ai/llm/mimo.ts +71 -0
- package/ai/llm/mistral.ts +22 -0
- package/ai/llm/modelAvatar.ts +427 -0
- package/ai/llm/models.ts +45 -0
- package/ai/llm/openrouterModels.ts +141 -0
- package/ai/llm/providers.ts +307 -0
- package/ai/llm/reasoningModels.ts +28 -0
- package/ai/llm/types.ts +59 -0
- package/ai/llm/usageRequestOptions.ts +59 -0
- package/ai/memory/capture.ts +148 -0
- package/ai/memory/consolidate.ts +104 -0
- package/ai/memory/delete.ts +147 -0
- package/ai/memory/overlay.ts +84 -0
- package/ai/memory/query.ts +38 -0
- package/ai/memory/queryShared.ts +160 -0
- package/ai/memory/rank.ts +105 -0
- package/ai/memory/recentRelationshipRecap.ts +247 -0
- package/ai/memory/remember.ts +167 -0
- package/ai/memory/runtime.ts +76 -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/messages/type.ts +20 -0
- package/ai/policy/personalizationDialog.ts +333 -0
- package/ai/policy/runtimePolicy.ts +440 -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/skillDocProtocol.ts +434 -0
- package/ai/skills/skillReferenceSummary.ts +63 -0
- package/ai/skills/skillSummaryMarker.ts +26 -0
- package/ai/token/calculatePrice.ts +546 -0
- package/ai/token/db.ts +98 -0
- package/ai/token/externalToolCost.ts +321 -0
- package/ai/token/hooks/useRecords.ts +65 -0
- package/ai/token/missingUsageEstimate.ts +42 -0
- package/ai/token/modelUsageQuery.ts +252 -0
- package/ai/token/normalizeUsage.ts +84 -0
- package/ai/token/openaiImageGenerationUsage.ts +56 -0
- package/ai/token/prepareTokenUsageData.ts +88 -0
- package/ai/token/query.ts +88 -0
- package/ai/token/queryUserTokens.ts +59 -0
- package/ai/token/resolveBillingTarget.ts +52 -0
- package/ai/token/saveTokenRecord.ts +53 -0
- package/ai/token/serverDialogProjection.ts +78 -0
- package/ai/token/serverTokenWriter.ts +143 -0
- package/ai/token/stats.ts +21 -0
- package/ai/token/tokenThunks.ts +24 -0
- package/ai/token/types.ts +93 -0
- package/ai/tools/agent/agentTools.ts +176 -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/presets/appBuilderPreset.ts +147 -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/skillAgentPreset.ts +89 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +122 -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 +256 -0
- package/ai/tools/applyLineEditsTool.ts +312 -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 +65 -0
- package/ai/tools/cloudflareCrawlTool.ts +289 -0
- package/ai/tools/codeSearchTool.ts +111 -0
- package/ai/tools/codeTools.ts +101 -0
- package/ai/tools/createDocTool.ts +132 -0
- package/ai/tools/createPlanTool.ts +999 -0
- package/ai/tools/createSkillDocTool.ts +155 -0
- package/ai/tools/createWorkflowTool.ts +154 -0
- package/ai/tools/deepseekOcrTool.ts +34 -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 +64 -0
- package/ai/tools/execBashTool.ts +379 -0
- package/ai/tools/executeSqlTool.ts +192 -0
- package/ai/tools/fetchWebpageSupport.ts +309 -0
- package/ai/tools/fetchWebpageTool.ts +84 -0
- package/ai/tools/geminiImagePreviewTool.ts +361 -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 +1927 -0
- package/ai/tools/listFilesTool.ts +82 -0
- package/ai/tools/listUserSpacesTool.ts +113 -0
- package/ai/tools/modelUsageTools.ts +199 -0
- package/ai/tools/olmOcrTool.ts +34 -0
- package/ai/tools/openaiImageTool.ts +267 -0
- package/ai/tools/prepareTools.ts +23 -0
- package/ai/tools/readDocTool.ts +84 -0
- package/ai/tools/readFileTool.ts +211 -0
- package/ai/tools/readTool.ts +163 -0
- package/ai/tools/readXPostTool.ts +233 -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/searchRepoTool.ts +115 -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 +366 -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/toolApiClient.ts +198 -0
- package/ai/tools/toolNameAliases.ts +57 -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/toolVisibility.ts +4 -0
- package/ai/tools/types.ts +20 -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 +145 -0
- package/ai/tools/whisperTool.ts +77 -0
- package/ai/tools/writeFileTool.ts +210 -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/client/agentRun.test.ts +240 -0
- package/client/agentRun.ts +182 -19
- package/client/compactDialog.test.ts +238 -0
- package/client/localRuntimeAdapter.test.ts +135 -0
- package/client/localRuntimeAdapter.ts +244 -0
- package/client/profileConfig.test.ts +40 -0
- package/client/streamingOutput.test.ts +22 -0
- package/client/streamingOutput.ts +38 -0
- package/commandRegistry.ts +9 -2
- package/connector-experimental/index.ts +5 -0
- package/database/actions/cacheMergedUserData.ts +64 -0
- package/database/actions/common.ts +242 -0
- package/database/actions/deleteFile.ts +40 -0
- package/database/actions/fetchUserData.ts +16 -0
- package/database/actions/fileContent.ts +125 -0
- package/database/actions/patch.ts +155 -0
- package/database/actions/read.ts +337 -0
- package/database/actions/readAndWait.ts +224 -0
- package/database/actions/readRequestManager.ts +120 -0
- package/database/actions/remove.ts +94 -0
- package/database/actions/replication.ts +366 -0
- package/database/actions/upload.ts +174 -0
- package/database/actions/upsert.ts +56 -0
- package/database/actions/write.ts +126 -0
- package/database/client/db.native.ts +73 -0
- package/database/client/db.ts +51 -0
- package/database/client/fetchUserData.ts +61 -0
- package/database/client/handleError.ts +19 -0
- package/database/client/queryRequest.ts +21 -0
- package/database/config.ts +21 -0
- package/database/dbActionThunks.ts +1 -0
- package/database/dbSlice.ts +149 -0
- package/database/email.ts +42 -0
- package/database/fileRing.ts +51 -0
- package/database/fileSharding.ts +70 -0
- package/database/fileStorage.native.ts +92 -0
- package/database/fileStorage.ts +232 -0
- package/database/fileUrl.ts +34 -0
- package/database/hooks/useUserData.ts +489 -0
- package/database/index.ts +1 -0
- package/database/keys.ts +765 -0
- package/database/queryPrefixes.ts +14 -0
- package/database/requests.ts +443 -0
- package/database/runtimeServerContext.ts +35 -0
- package/database/server/MemoryDB.ts +76 -0
- package/database/server/actorAccess.ts +76 -0
- package/database/server/agentDelegation.ts +124 -0
- package/database/server/coreDataOwnership.ts +13 -0
- package/database/server/coreDataProxy.ts +76 -0
- package/database/server/cybotReadonly.ts +18 -0
- package/database/server/dataHandlers.ts +111 -0
- package/database/server/db.ts +118 -0
- package/database/server/dbPath.ts +20 -0
- package/database/server/delete.ts +499 -0
- package/database/server/emailRepository.ts +1480 -0
- package/database/server/ensureDbOpen.ts +12 -0
- package/database/server/fileRead.ts +337 -0
- package/database/server/fileService.ts +436 -0
- package/database/server/handleTransaction.ts +86 -0
- package/database/server/patch.ts +282 -0
- package/database/server/query.ts +138 -0
- package/database/server/read.ts +325 -0
- package/database/server/resourceAccess.ts +211 -0
- package/database/server/routes.ts +110 -0
- package/database/server/spaceMemberAuthority.ts +67 -0
- package/database/server/upload.ts +159 -0
- package/database/server/write.ts +494 -0
- package/database/server/writeAuthority.ts +133 -0
- package/database/sqliteDb.ts +46 -0
- package/database/table/deleteTable.ts +120 -0
- package/database/tenantPlacement.ts +57 -0
- package/database/tombstones.ts +52 -0
- package/database/userDataLoadDecision.ts +17 -0
- package/database/userDataMerge.ts +95 -0
- package/database/userPreferenceRegister.ts +108 -0
- package/database/utils/dbPath.ts +47 -0
- package/database/utils/ulid.native.ts +6 -0
- package/database/utils/ulid.ts +1 -0
- package/index.ts +25 -15
- package/localRuntimeDb.ts +28 -0
- package/package.json +16 -4
- package/runtimeModeArgs.ts +33 -0
- package/tui/readlineWorkspace.ts +1 -0
- package/tui/session.ts +22 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const TWO_PART_USER_KEYS = new Set(["settings", "profile"]);
|
|
2
|
+
const OWNER_IN_SECOND_SEGMENT_PREFIXES = new Set([
|
|
3
|
+
"dialog",
|
|
4
|
+
"page",
|
|
5
|
+
"doc",
|
|
6
|
+
"email",
|
|
7
|
+
"file",
|
|
8
|
+
"meta",
|
|
9
|
+
"row",
|
|
10
|
+
"agent",
|
|
11
|
+
"cybot",
|
|
12
|
+
"app",
|
|
13
|
+
"notification",
|
|
14
|
+
"token",
|
|
15
|
+
"image",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export const resolveKeyOwnerId = (dbKey: string): string | null => {
|
|
19
|
+
if (!dbKey || typeof dbKey !== "string") return null;
|
|
20
|
+
|
|
21
|
+
const parts = dbKey.split("-");
|
|
22
|
+
if (parts.length === 2 && TWO_PART_USER_KEYS.has(parts[1])) {
|
|
23
|
+
return parts[0] || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (parts[0] === "user" && parts[1] === "pref" && parts.length >= 4) {
|
|
27
|
+
return parts[2] || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
parts[0] === "space" &&
|
|
32
|
+
(parts[1] === "setting" || parts[1] === "member") &&
|
|
33
|
+
parts.length >= 4
|
|
34
|
+
) {
|
|
35
|
+
return parts[2] || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (
|
|
39
|
+
parts[0] === "token" &&
|
|
40
|
+
parts[1] === "stats" &&
|
|
41
|
+
parts[2] === "day" &&
|
|
42
|
+
parts[3] === "user" &&
|
|
43
|
+
parts.length >= 6
|
|
44
|
+
) {
|
|
45
|
+
return parts[4] || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (parts[0] === "dialog" && parts[2] === "msg") {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (parts[0] === "email" && parts.length >= 3) {
|
|
53
|
+
return parts.slice(1, -1).join("-") || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (OWNER_IN_SECOND_SEGMENT_PREFIXES.has(parts[0]) && parts.length >= 3) {
|
|
57
|
+
if (parts[1] === "pub") {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return parts[1] || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const resolveWriteAuthority = ({
|
|
67
|
+
dbKey,
|
|
68
|
+
actionUserId,
|
|
69
|
+
existingRecord,
|
|
70
|
+
}: {
|
|
71
|
+
dbKey: string;
|
|
72
|
+
actionUserId?: string | null;
|
|
73
|
+
existingRecord?: any;
|
|
74
|
+
}) => {
|
|
75
|
+
const keyOwnerId = resolveKeyOwnerId(dbKey);
|
|
76
|
+
const recordOwnerId =
|
|
77
|
+
(typeof existingRecord?.ownerId === "string" && existingRecord.ownerId) ||
|
|
78
|
+
(typeof existingRecord?.userId === "string" && existingRecord.userId) ||
|
|
79
|
+
null;
|
|
80
|
+
const isKeyOwner =
|
|
81
|
+
Boolean(actionUserId) && Boolean(keyOwnerId) && keyOwnerId === actionUserId;
|
|
82
|
+
const isRecordOwner =
|
|
83
|
+
Boolean(actionUserId) &&
|
|
84
|
+
Boolean(recordOwnerId) &&
|
|
85
|
+
recordOwnerId === actionUserId;
|
|
86
|
+
const isMember =
|
|
87
|
+
Boolean(actionUserId) &&
|
|
88
|
+
Array.isArray(existingRecord?.members) &&
|
|
89
|
+
existingRecord.members.includes(actionUserId);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
keyOwnerId,
|
|
93
|
+
recordOwnerId,
|
|
94
|
+
isAllowed: Boolean(isKeyOwner || isRecordOwner || isMember),
|
|
95
|
+
isKeyOwner,
|
|
96
|
+
isRecordOwner,
|
|
97
|
+
isMember,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const canWriteRecord = ({
|
|
102
|
+
dbKey,
|
|
103
|
+
actionUserId,
|
|
104
|
+
record,
|
|
105
|
+
}: {
|
|
106
|
+
dbKey: string;
|
|
107
|
+
actionUserId?: string | null;
|
|
108
|
+
record?: any;
|
|
109
|
+
}) => {
|
|
110
|
+
const { keyOwnerId, recordOwnerId, isAllowed } = resolveWriteAuthority({
|
|
111
|
+
dbKey,
|
|
112
|
+
actionUserId,
|
|
113
|
+
existingRecord: record,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (keyOwnerId && recordOwnerId && keyOwnerId !== recordOwnerId) {
|
|
117
|
+
return {
|
|
118
|
+
keyOwnerId,
|
|
119
|
+
recordOwnerId,
|
|
120
|
+
isAllowed: false,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
keyOwnerId,
|
|
126
|
+
recordOwnerId,
|
|
127
|
+
isAllowed:
|
|
128
|
+
isAllowed ||
|
|
129
|
+
(Boolean(actionUserId) &&
|
|
130
|
+
!keyOwnerId &&
|
|
131
|
+
!recordOwnerId),
|
|
132
|
+
};
|
|
133
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// 文件路径: packages/database/sqliteDb.ts
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
import { getDbFilePath } from "./utils/dbPath";
|
|
4
|
+
|
|
5
|
+
const userDatabases = new Map<string, Database>();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 获取指定用户的 SQLite 数据库实例。
|
|
9
|
+
* 如果该用户的数据库实例尚未打开,则会创建并打开它。
|
|
10
|
+
* @param userId 用户的唯一 ID。
|
|
11
|
+
* @returns 对应用户的 SQLite 数据库实例。
|
|
12
|
+
*/
|
|
13
|
+
export function getSqliteDb(userId: string): Database {
|
|
14
|
+
let dbInstance = userDatabases.get(userId);
|
|
15
|
+
|
|
16
|
+
if (!dbInstance) {
|
|
17
|
+
const dbPath = getDbFilePath(userId);
|
|
18
|
+
|
|
19
|
+
dbInstance = new Database(dbPath, { create: true });
|
|
20
|
+
|
|
21
|
+
// WAL 模式:更适合高并发读写
|
|
22
|
+
dbInstance.exec("PRAGMA journal_mode = WAL;");
|
|
23
|
+
|
|
24
|
+
userDatabases.set(userId, dbInstance);
|
|
25
|
+
|
|
26
|
+
console.log(`SQLite Database connected for user ${userId}: ${dbPath}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return dbInstance;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 关闭所有已打开的用户 SQLite 数据库连接。
|
|
34
|
+
* 由应用入口在优雅关机时统一调用。
|
|
35
|
+
*/
|
|
36
|
+
export function closeAllUserSqliteDbs() {
|
|
37
|
+
console.log("Closing all user-specific SQLite database connections...");
|
|
38
|
+
for (const [userId, db] of userDatabases.entries()) {
|
|
39
|
+
try {
|
|
40
|
+
db.close(false);
|
|
41
|
+
console.log(`Closed DB for user ${userId}.`);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error(`Error closing DB for user ${userId}:`, e);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// 文件: database/table/deleteTable.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
idxKey,
|
|
5
|
+
isTableMetaKey,
|
|
6
|
+
rowKey,
|
|
7
|
+
SEPARATOR,
|
|
8
|
+
createKey,
|
|
9
|
+
} from "database/keys";
|
|
10
|
+
|
|
11
|
+
export interface KeyValueDB {
|
|
12
|
+
iterator: (opts: {
|
|
13
|
+
gte?: string;
|
|
14
|
+
lte?: string;
|
|
15
|
+
keys?: boolean;
|
|
16
|
+
values?: boolean;
|
|
17
|
+
}) => AsyncIterable<[string, unknown]>;
|
|
18
|
+
del: (key: string) => Promise<void>;
|
|
19
|
+
batch?: (ops: { type: "del"; key: string }[]) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DeleteTableResult {
|
|
23
|
+
processingIds: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 从 meta key 中解析 tenantId / tableId
|
|
28
|
+
* 约定:meta-{tenantId}-{tableId}
|
|
29
|
+
*/
|
|
30
|
+
const parseMetaKey = (metaDbKey: string) => {
|
|
31
|
+
if (!isTableMetaKey(metaDbKey)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`deleteTable 预期传入的是表 meta key(meta-{tenantId}-{tableId}),收到: ${metaDbKey}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [, tenantId, tableId] = metaDbKey.split(SEPARATOR);
|
|
38
|
+
return { tenantId, tableId };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 扫描一个范围内的所有 key(只取 key,不取 value)
|
|
43
|
+
*/
|
|
44
|
+
const collectKeysInRange = async (
|
|
45
|
+
db: KeyValueDB,
|
|
46
|
+
range: { gte: string; lte: string }
|
|
47
|
+
): Promise<string[]> => {
|
|
48
|
+
const keys: string[] = [];
|
|
49
|
+
|
|
50
|
+
let iterator = (db as any).iterator({
|
|
51
|
+
gte: range.gte,
|
|
52
|
+
lte: range.lte,
|
|
53
|
+
keys: true,
|
|
54
|
+
values: false,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (iterator && typeof iterator.then === 'function') {
|
|
58
|
+
iterator = await iterator;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for await (const [key] of iterator) {
|
|
62
|
+
keys.push(key as string);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return keys;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 删除一整张表:
|
|
70
|
+
* - 入参是表的 meta key:meta-{tenantId}-{tableId}
|
|
71
|
+
* - 删除:
|
|
72
|
+
* 1) 所有行:row-{tenantId}-{tableId}-{rowId}
|
|
73
|
+
* 2) 所有索引:idx-{tenantId}-{tableId}-{indexName}-{indexKey}-{rowId}
|
|
74
|
+
* 3) 所有视图:view-{tenantId}-{tableId}-{viewId}
|
|
75
|
+
* 5) meta 本身:meta-{tenantId}-{tableId}
|
|
76
|
+
*/
|
|
77
|
+
export const deleteTable = async (
|
|
78
|
+
db: KeyValueDB,
|
|
79
|
+
metaDbKey: string
|
|
80
|
+
): Promise<DeleteTableResult> => {
|
|
81
|
+
const { tenantId, tableId } = parseMetaKey(metaDbKey);
|
|
82
|
+
|
|
83
|
+
const keysToDelete = new Set<string>();
|
|
84
|
+
// 先把 meta 自己加进去
|
|
85
|
+
keysToDelete.add(metaDbKey);
|
|
86
|
+
|
|
87
|
+
// 1) 收集所有行 key
|
|
88
|
+
const rowRange = rowKey.range(tenantId, tableId);
|
|
89
|
+
const rowKeys = await collectKeysInRange(db, rowRange);
|
|
90
|
+
rowKeys.forEach((k) => keysToDelete.add(k));
|
|
91
|
+
|
|
92
|
+
// 2) 收集所有索引 key
|
|
93
|
+
const indexRange = idxKey.prefix(tenantId, tableId);
|
|
94
|
+
const indexKeys = await collectKeysInRange(db, indexRange);
|
|
95
|
+
indexKeys.forEach((k) => keysToDelete.add(k));
|
|
96
|
+
|
|
97
|
+
// 3) 收集所有视图 key:view-{tenantId}-{tableId}-{viewId}
|
|
98
|
+
const viewPrefix = createKey("view", tenantId, tableId, "");
|
|
99
|
+
const viewKeys = await collectKeysInRange(db, {
|
|
100
|
+
gte: viewPrefix,
|
|
101
|
+
lte: viewPrefix + "\uffff",
|
|
102
|
+
});
|
|
103
|
+
viewKeys.forEach((k) => keysToDelete.add(k));
|
|
104
|
+
|
|
105
|
+
const processingIds = Array.from(keysToDelete);
|
|
106
|
+
|
|
107
|
+
// 5) 批量删除
|
|
108
|
+
if (typeof db.batch === "function") {
|
|
109
|
+
await db.batch(
|
|
110
|
+
processingIds.map((key) => ({
|
|
111
|
+
type: "del" as const,
|
|
112
|
+
key,
|
|
113
|
+
}))
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
await Promise.all(processingIds.map((key) => db.del(key)));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { processingIds };
|
|
120
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// 文件路径: database/tenantPlacement.ts
|
|
2
|
+
|
|
3
|
+
import { chooseServersByKey } from "./fileRing";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 当前 tenant(用户 / 组织)的副本个数:
|
|
7
|
+
*
|
|
8
|
+
* - TENANT_REPLICA_COUNT = 2:
|
|
9
|
+
* 表示每个 tenant 至少分布在 2 台服务器上(例如 main + us)。
|
|
10
|
+
*
|
|
11
|
+
* 后续如果要提高容灾能力,可以改为 3,
|
|
12
|
+
* 但是建议不要轻易降低(否则会破坏“至少两台机器有数据”的假设)。
|
|
13
|
+
*/
|
|
14
|
+
export const TENANT_REPLICA_COUNT = 2;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 基于 tenantId(通常为 userId)为某个 tenant 选择服务器集合。
|
|
18
|
+
*
|
|
19
|
+
* 目标:
|
|
20
|
+
* - 同一个 tenant 的所有数据(结构化 + 文件)尽量落在同一组服务器上;
|
|
21
|
+
* - 至少 TENANT_REPLICA_COUNT 台服务器;
|
|
22
|
+
* - currentServer 若在 allServers 中,强制包含在结果中(早期可以保证 main 一定在内)。
|
|
23
|
+
*
|
|
24
|
+
* 参数:
|
|
25
|
+
* - allServers: 由 currentServer + syncServers 去重后得到的服务器列表。
|
|
26
|
+
* - currentServer: 当前前端所连接的主服务器(可以为 null/undefined)。
|
|
27
|
+
* - tenantId: 租户 ID,当前你可以直接用 userId。
|
|
28
|
+
*
|
|
29
|
+
* 返回:
|
|
30
|
+
* - servers: 用于写入该 tenant 数据的服务器列表。
|
|
31
|
+
*/
|
|
32
|
+
export const planServersForTenant = (
|
|
33
|
+
allServers: string[],
|
|
34
|
+
currentServer: string | null | undefined,
|
|
35
|
+
tenantId: string | null | undefined
|
|
36
|
+
): string[] => {
|
|
37
|
+
const uniqueServers = Array.from(new Set(allServers)).filter(Boolean);
|
|
38
|
+
if (!uniqueServers.length) return [];
|
|
39
|
+
|
|
40
|
+
const key = tenantId && tenantId.trim() ? tenantId.trim() : "default-tenant";
|
|
41
|
+
|
|
42
|
+
// 基于 tenantId 进行 hash ring 分布
|
|
43
|
+
const fromRing = chooseServersByKey(
|
|
44
|
+
uniqueServers,
|
|
45
|
+
key,
|
|
46
|
+
TENANT_REPLICA_COUNT
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const set = new Set(fromRing);
|
|
50
|
+
|
|
51
|
+
// 强制包含 currentServer(若其在配置中)
|
|
52
|
+
if (currentServer && uniqueServers.includes(currentServer)) {
|
|
53
|
+
set.add(currentServer);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Array.from(set);
|
|
57
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const getRecordTimestamp = (record: any): number => {
|
|
2
|
+
if (!record || typeof record !== "object") return 0;
|
|
3
|
+
|
|
4
|
+
const candidates = [
|
|
5
|
+
record.updatedAt,
|
|
6
|
+
record.updated_at,
|
|
7
|
+
record.createdAt,
|
|
8
|
+
record.created,
|
|
9
|
+
record?.meta?.createdAt,
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
for (const candidate of candidates) {
|
|
13
|
+
if (typeof candidate === "number" && Number.isFinite(candidate) && candidate > 0) {
|
|
14
|
+
return candidate;
|
|
15
|
+
}
|
|
16
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
17
|
+
const timestamp = Date.parse(candidate);
|
|
18
|
+
if (Number.isFinite(timestamp) && timestamp > 0) {
|
|
19
|
+
return timestamp;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return 0;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const isTombstoneRecord = (record: any): boolean => {
|
|
28
|
+
if (!record || typeof record !== "object") return false;
|
|
29
|
+
const deletedAt = record.deletedAt;
|
|
30
|
+
if (typeof deletedAt === "string") return deletedAt.trim().length > 0;
|
|
31
|
+
return Boolean(deletedAt);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const shouldReplaceWithNextRecord = (
|
|
35
|
+
nextRecord: any,
|
|
36
|
+
currentRecord: any
|
|
37
|
+
): boolean => {
|
|
38
|
+
const nextTs = getRecordTimestamp(nextRecord);
|
|
39
|
+
const currentTs = getRecordTimestamp(currentRecord);
|
|
40
|
+
|
|
41
|
+
if (nextTs !== currentTs) return nextTs > currentTs;
|
|
42
|
+
return isTombstoneRecord(nextRecord) && !isTombstoneRecord(currentRecord);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const buildTombstoneRecord = <T extends Record<string, any>>(
|
|
46
|
+
record: T,
|
|
47
|
+
nowIso: string
|
|
48
|
+
): T & { deletedAt: string; updatedAt: string } => ({
|
|
49
|
+
...record,
|
|
50
|
+
deletedAt: nowIso,
|
|
51
|
+
updatedAt: nowIso,
|
|
52
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type UserDataLoadDecision = "load" | "skip" | "queue";
|
|
2
|
+
|
|
3
|
+
export const getUserDataLoadDecision = ({
|
|
4
|
+
loading,
|
|
5
|
+
sameParams,
|
|
6
|
+
forceRefresh,
|
|
7
|
+
}: {
|
|
8
|
+
loading: boolean;
|
|
9
|
+
sameParams: boolean;
|
|
10
|
+
forceRefresh?: boolean;
|
|
11
|
+
}): UserDataLoadDecision => {
|
|
12
|
+
if (forceRefresh) {
|
|
13
|
+
return loading ? "queue" : "load";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return loading || sameParams ? "skip" : "load";
|
|
17
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRecordTimestamp,
|
|
3
|
+
isTombstoneRecord,
|
|
4
|
+
shouldReplaceWithNextRecord,
|
|
5
|
+
} from "./tombstones";
|
|
6
|
+
|
|
7
|
+
interface MergeableUserDataItem {
|
|
8
|
+
id?: string;
|
|
9
|
+
dbKey?: string;
|
|
10
|
+
contentKey?: string;
|
|
11
|
+
appKey?: string;
|
|
12
|
+
appId?: string;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const getUserDataItemTimestamp = (dataItem: MergeableUserDataItem): number =>
|
|
17
|
+
getRecordTimestamp(dataItem);
|
|
18
|
+
|
|
19
|
+
const getItemKey = (item: MergeableUserDataItem): string | null => {
|
|
20
|
+
// dbKey 必须优先于 id:dbKey 是全局唯一的 "type-userId-id" 格式,
|
|
21
|
+
// 而 id 可能只是 ULID 短串,与本地 tombstone 的 dbKey 不匹配会导致
|
|
22
|
+
// merge 时 tombstone 无法覆盖远端活记录。
|
|
23
|
+
const candidates = [
|
|
24
|
+
typeof item.dbKey === "string" ? item.dbKey : undefined,
|
|
25
|
+
item.id,
|
|
26
|
+
typeof item.contentKey === "string" ? item.contentKey : undefined,
|
|
27
|
+
typeof item.appKey === "string" ? item.appKey : undefined,
|
|
28
|
+
typeof item.appId === "string" ? item.appId : undefined,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const mergeAndDedupUserData = (
|
|
41
|
+
localData: MergeableUserDataItem[],
|
|
42
|
+
remoteResults: any[],
|
|
43
|
+
options: { includeDeleted?: boolean } = {}
|
|
44
|
+
): MergeableUserDataItem[] => {
|
|
45
|
+
const uniqueMap = new Map<string, MergeableUserDataItem>();
|
|
46
|
+
|
|
47
|
+
const mergeRecordMetadata = (
|
|
48
|
+
currentItem: MergeableUserDataItem,
|
|
49
|
+
nextItem: MergeableUserDataItem
|
|
50
|
+
): MergeableUserDataItem => {
|
|
51
|
+
if (
|
|
52
|
+
typeof currentItem.serverOrigin !== "string" &&
|
|
53
|
+
typeof nextItem.serverOrigin === "string" &&
|
|
54
|
+
nextItem.serverOrigin.trim().length > 0
|
|
55
|
+
) {
|
|
56
|
+
return {
|
|
57
|
+
...currentItem,
|
|
58
|
+
serverOrigin: nextItem.serverOrigin,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return currentItem;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const addToMap = (item: MergeableUserDataItem) => {
|
|
66
|
+
const itemKey = getItemKey(item);
|
|
67
|
+
if (!itemKey) return;
|
|
68
|
+
|
|
69
|
+
const existing = uniqueMap.get(itemKey);
|
|
70
|
+
if (!existing) {
|
|
71
|
+
uniqueMap.set(itemKey, item);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (shouldReplaceWithNextRecord(item, existing)) {
|
|
76
|
+
uniqueMap.set(itemKey, item);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
uniqueMap.set(itemKey, mergeRecordMetadata(existing, item));
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
localData.forEach(addToMap);
|
|
84
|
+
remoteResults.forEach((result) => {
|
|
85
|
+
const items = result?.data?.data;
|
|
86
|
+
if (Array.isArray(items)) {
|
|
87
|
+
items.forEach(addToMap);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const merged = Array.from(uniqueMap.values());
|
|
92
|
+
return options.includeDeleted
|
|
93
|
+
? merged
|
|
94
|
+
: merged.filter((item) => !isTombstoneRecord(item));
|
|
95
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { DataType } from "create/types";
|
|
2
|
+
import { ulid } from "database/utils/ulid";
|
|
3
|
+
|
|
4
|
+
export const USER_PREFERENCE_NAMES = {
|
|
5
|
+
DEFAULT_SPACE: "space_default",
|
|
6
|
+
DEFAULT_AGENT: "agent_default",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type UserPreferenceName =
|
|
10
|
+
(typeof USER_PREFERENCE_NAMES)[keyof typeof USER_PREFERENCE_NAMES];
|
|
11
|
+
|
|
12
|
+
export interface UserPreferenceRegisterRecord<T = unknown> {
|
|
13
|
+
type: DataType.SETTING;
|
|
14
|
+
registerType: "user_preference";
|
|
15
|
+
preferenceName: UserPreferenceName;
|
|
16
|
+
schemaVersion: 1;
|
|
17
|
+
userId: string;
|
|
18
|
+
value: T | null;
|
|
19
|
+
opId: string;
|
|
20
|
+
createdAt: number;
|
|
21
|
+
updatedAt: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const toTimestamp = (value: unknown): number => {
|
|
25
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
const parsed = Date.parse(value);
|
|
28
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const nextRegisterUpdatedAt = (previousRecord?: Partial<UserPreferenceRegisterRecord>) => {
|
|
34
|
+
const previousTimestamp = Math.max(
|
|
35
|
+
toTimestamp(previousRecord?.updatedAt),
|
|
36
|
+
toTimestamp(previousRecord?.createdAt)
|
|
37
|
+
);
|
|
38
|
+
return Math.max(Date.now(), previousTimestamp + 1);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const buildUserPreferenceRegisterRecord = <T>({
|
|
42
|
+
userId,
|
|
43
|
+
preferenceName,
|
|
44
|
+
value,
|
|
45
|
+
previousRecord,
|
|
46
|
+
}: {
|
|
47
|
+
userId: string;
|
|
48
|
+
preferenceName: UserPreferenceName;
|
|
49
|
+
value: T | null;
|
|
50
|
+
previousRecord?: Partial<UserPreferenceRegisterRecord<T>> | null;
|
|
51
|
+
}): UserPreferenceRegisterRecord<T> => {
|
|
52
|
+
const updatedAt = nextRegisterUpdatedAt(previousRecord ?? undefined);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
type: DataType.SETTING,
|
|
56
|
+
registerType: "user_preference",
|
|
57
|
+
preferenceName,
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
userId,
|
|
60
|
+
value,
|
|
61
|
+
opId: ulid(),
|
|
62
|
+
createdAt: toTimestamp(previousRecord?.createdAt) || updatedAt,
|
|
63
|
+
updatedAt,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const readUserPreferenceRegisterValue = <T>(
|
|
68
|
+
record: any,
|
|
69
|
+
preferenceName: UserPreferenceName
|
|
70
|
+
): T | null | undefined => {
|
|
71
|
+
if (!record || typeof record !== "object") return undefined;
|
|
72
|
+
if (record.registerType !== "user_preference") return undefined;
|
|
73
|
+
if (record.preferenceName !== preferenceName) return undefined;
|
|
74
|
+
if (!("value" in record)) return undefined;
|
|
75
|
+
return record.value as T | null;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const buildDefaultAgentPreferenceRegisterRecord = ({
|
|
79
|
+
userId,
|
|
80
|
+
defaultAgentId,
|
|
81
|
+
previousRecord,
|
|
82
|
+
}: {
|
|
83
|
+
userId: string;
|
|
84
|
+
defaultAgentId: string | null;
|
|
85
|
+
previousRecord?: Partial<UserPreferenceRegisterRecord<string>> | null;
|
|
86
|
+
}) =>
|
|
87
|
+
buildUserPreferenceRegisterRecord<string>({
|
|
88
|
+
userId,
|
|
89
|
+
preferenceName: USER_PREFERENCE_NAMES.DEFAULT_AGENT,
|
|
90
|
+
value: defaultAgentId,
|
|
91
|
+
previousRecord,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const buildDefaultSpacePreferenceRegisterRecord = ({
|
|
95
|
+
userId,
|
|
96
|
+
defaultSpaceId,
|
|
97
|
+
previousRecord,
|
|
98
|
+
}: {
|
|
99
|
+
userId: string;
|
|
100
|
+
defaultSpaceId: string | null;
|
|
101
|
+
previousRecord?: Partial<UserPreferenceRegisterRecord<string>> | null;
|
|
102
|
+
}) =>
|
|
103
|
+
buildUserPreferenceRegisterRecord<string>({
|
|
104
|
+
userId,
|
|
105
|
+
preferenceName: USER_PREFERENCE_NAMES.DEFAULT_SPACE,
|
|
106
|
+
value: defaultSpaceId,
|
|
107
|
+
previousRecord,
|
|
108
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// packages/database/utils/dbPath.ts (新建文件)
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
// --- 路径计算和目录创建逻辑 ---
|
|
6
|
+
|
|
7
|
+
// 使用 import.meta.dir 获取当前文件 (packages/database/utils/dbPath.ts) 的目录。
|
|
8
|
+
const CURRENT_FILE_DIR = import.meta.dir;
|
|
9
|
+
// => /path/to/BUN-NOLO/packages/database/utils
|
|
10
|
+
|
|
11
|
+
// 构建项目根目录 (BUN-NOLO) 的绝对路径:
|
|
12
|
+
// 从当前文件目录向上退三级 ('..', '..', '..')
|
|
13
|
+
const PROJECT_ROOT = path.join(CURRENT_FILE_DIR, "..", "..", "..");
|
|
14
|
+
// => /path/to/BUN-NOLO
|
|
15
|
+
|
|
16
|
+
// 构建数据根目录的绝对路径:在项目根目录下找到 'data' 目录
|
|
17
|
+
const DATA_BASE_DIRECTORY = path.join(PROJECT_ROOT, "data");
|
|
18
|
+
// => /path/to/BUN-NOLO/data
|
|
19
|
+
|
|
20
|
+
// 构建用户数据库文件的存放目录的绝对路径:在 'data' 目录下创建 'user_dbs'
|
|
21
|
+
const USER_DATABASES_DIRECTORY = path.join(DATA_BASE_DIRECTORY, "user_dbs");
|
|
22
|
+
// => /path/to/BUN-NOLO/data/user_dbs
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 辅助函数:确保指定目录存在。
|
|
26
|
+
* @param dirPath 要检查或创建的目录路径。
|
|
27
|
+
*/
|
|
28
|
+
function ensureDirectoryExists(dirPath: string) {
|
|
29
|
+
try {
|
|
30
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
31
|
+
} catch (e: any) {
|
|
32
|
+
if (e.code !== "EEXIST") {
|
|
33
|
+
console.error(`Error creating directory ${dirPath}:`, e);
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 根据用户ID获取其专属SQLite数据库文件的完整路径。
|
|
41
|
+
* @param userId 用户的唯一ID。
|
|
42
|
+
* @returns 对应用户SQLite数据库文件的完整路径。
|
|
43
|
+
*/
|
|
44
|
+
export function getDbFilePath(userId: string): string {
|
|
45
|
+
ensureDirectoryExists(USER_DATABASES_DIRECTORY);
|
|
46
|
+
return path.join(USER_DATABASES_DIRECTORY, `${userId}.sqlite`);
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ulid } from "ulid";
|