From 4b17f968a6d3db69db04e0890845b3887f4f8e38 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Tue, 30 Jun 2026 22:15:29 +0800 Subject: [PATCH] perf(web): lazy load console contract shards (#38230) --- .../generated/api/console/orpc.gen.ts | 151 +++----- packages/contracts/openapi-ts.api.config.ts | 9 +- .../components/plugins/marketplace/query.ts | 2 +- web/contract/console/agent.ts | 13 + web/contract/console/apps.ts | 23 ++ web/contract/console/billing.ts | 7 + web/contract/console/explore.ts | 16 + web/contract/console/files.ts | 9 + web/contract/console/model-providers.ts | 5 + web/contract/console/plugins.ts | 5 + web/contract/console/snippets.ts | 28 ++ web/contract/console/tags.ts | 11 + web/contract/console/trigger.ts | 18 + web/contract/console/try-app.ts | 9 + web/contract/console/workflow.ts | 7 + web/contract/console/workspaces.ts | 9 + web/contract/marketplace.ts | 21 +- web/contract/router.ts | 343 ++++++------------ .../configure/build-draft-query.ts | 9 +- web/knip.config.ts | 1 - web/service/client.ts | 53 +-- web/service/console-link.ts | 32 ++ web/service/console-router-loader.ts | 51 +++ web/service/server.ts | 48 ++- 24 files changed, 484 insertions(+), 396 deletions(-) create mode 100644 web/contract/console/agent.ts create mode 100644 web/service/console-link.ts create mode 100644 web/service/console-router-loader.ts diff --git a/packages/contracts/generated/api/console/orpc.gen.ts b/packages/contracts/generated/api/console/orpc.gen.ts index 1d25989f247..89b2865fd77 100644 --- a/packages/contracts/generated/api/console/orpc.gen.ts +++ b/packages/contracts/generated/api/console/orpc.gen.ts @@ -1,103 +1,54 @@ // This file is auto-generated by @hey-api/openapi-ts -import { account } from './account/orpc.gen' -import { activate } from './activate/orpc.gen' -import { agent } from './agent/orpc.gen' -import { allWorkspaces } from './all-workspaces/orpc.gen' -import { apiBasedExtension } from './api-based-extension/orpc.gen' -import { apiKeyAuth } from './api-key-auth/orpc.gen' -import { appDslVersion } from './app-dsl-version/orpc.gen' -import { app } from './app/orpc.gen' -import { apps } from './apps/orpc.gen' -import { auth } from './auth/orpc.gen' -import { billing } from './billing/orpc.gen' -import { codeBasedExtension } from './code-based-extension/orpc.gen' -import { compliance } from './compliance/orpc.gen' -import { dataSource } from './data-source/orpc.gen' -import { datasets } from './datasets/orpc.gen' -import { emailCodeLogin } from './email-code-login/orpc.gen' -import { emailRegister } from './email-register/orpc.gen' -import { explore } from './explore/orpc.gen' -import { features } from './features/orpc.gen' -import { files } from './files/orpc.gen' -import { forgotPassword } from './forgot-password/orpc.gen' -import { form } from './form/orpc.gen' -import { info } from './info/orpc.gen' -import { installedApps } from './installed-apps/orpc.gen' -import { instructionGenerate } from './instruction-generate/orpc.gen' -import { login } from './login/orpc.gen' -import { logout } from './logout/orpc.gen' -import { notification } from './notification/orpc.gen' -import { notion } from './notion/orpc.gen' -import { oauth } from './oauth/orpc.gen' -import { rag } from './rag/orpc.gen' -import { refreshToken } from './refresh-token/orpc.gen' -import { remoteFiles } from './remote-files/orpc.gen' -import { resetPassword } from './reset-password/orpc.gen' -import { ruleCodeGenerate } from './rule-code-generate/orpc.gen' -import { ruleGenerate } from './rule-generate/orpc.gen' -import { ruleStructuredOutputGenerate } from './rule-structured-output-generate/orpc.gen' -import { snippets } from './snippets/orpc.gen' -import { spec } from './spec/orpc.gen' -import { systemFeatures } from './system-features/orpc.gen' -import { tagBindings } from './tag-bindings/orpc.gen' -import { tags } from './tags/orpc.gen' -import { test } from './test/orpc.gen' -import { trialApps } from './trial-apps/orpc.gen' -import { trialModels } from './trial-models/orpc.gen' -import { website } from './website/orpc.gen' -import { workflowGenerate } from './workflow-generate/orpc.gen' -import { workflow } from './workflow/orpc.gen' -import { workspaces } from './workspaces/orpc.gen' - -export const contract = { - account, - activate, - agent, - allWorkspaces, - apiBasedExtension, - apiKeyAuth, - app, - appDslVersion, - apps, - auth, - billing, - codeBasedExtension, - compliance, - dataSource, - datasets, - emailCodeLogin, - emailRegister, - explore, - features, - files, - forgotPassword, - form, - info, - installedApps, - instructionGenerate, - login, - logout, - notification, - notion, - oauth, - rag, - refreshToken, - remoteFiles, - resetPassword, - ruleCodeGenerate, - ruleGenerate, - ruleStructuredOutputGenerate, - snippets, - spec, - systemFeatures, - tagBindings, - tags, - test, - trialApps, - trialModels, - website, - workflow, - workflowGenerate, - workspaces, +export const contractLoaders = { + account: () => import('./account/orpc.gen').then(({ account }) => ({ account })), + activate: () => import('./activate/orpc.gen').then(({ activate }) => ({ activate })), + agent: () => import('./agent/orpc.gen').then(({ agent }) => ({ agent })), + allWorkspaces: () => import('./all-workspaces/orpc.gen').then(({ allWorkspaces }) => ({ allWorkspaces })), + apiBasedExtension: () => import('./api-based-extension/orpc.gen').then(({ apiBasedExtension }) => ({ apiBasedExtension })), + apiKeyAuth: () => import('./api-key-auth/orpc.gen').then(({ apiKeyAuth }) => ({ apiKeyAuth })), + app: () => import('./app/orpc.gen').then(({ app }) => ({ app })), + appDslVersion: () => import('./app-dsl-version/orpc.gen').then(({ appDslVersion }) => ({ appDslVersion })), + apps: () => import('./apps/orpc.gen').then(({ apps }) => ({ apps })), + auth: () => import('./auth/orpc.gen').then(({ auth }) => ({ auth })), + billing: () => import('./billing/orpc.gen').then(({ billing }) => ({ billing })), + codeBasedExtension: () => import('./code-based-extension/orpc.gen').then(({ codeBasedExtension }) => ({ codeBasedExtension })), + compliance: () => import('./compliance/orpc.gen').then(({ compliance }) => ({ compliance })), + dataSource: () => import('./data-source/orpc.gen').then(({ dataSource }) => ({ dataSource })), + datasets: () => import('./datasets/orpc.gen').then(({ datasets }) => ({ datasets })), + emailCodeLogin: () => import('./email-code-login/orpc.gen').then(({ emailCodeLogin }) => ({ emailCodeLogin })), + emailRegister: () => import('./email-register/orpc.gen').then(({ emailRegister }) => ({ emailRegister })), + explore: () => import('./explore/orpc.gen').then(({ explore }) => ({ explore })), + features: () => import('./features/orpc.gen').then(({ features }) => ({ features })), + files: () => import('./files/orpc.gen').then(({ files }) => ({ files })), + forgotPassword: () => import('./forgot-password/orpc.gen').then(({ forgotPassword }) => ({ forgotPassword })), + form: () => import('./form/orpc.gen').then(({ form }) => ({ form })), + info: () => import('./info/orpc.gen').then(({ info }) => ({ info })), + installedApps: () => import('./installed-apps/orpc.gen').then(({ installedApps }) => ({ installedApps })), + instructionGenerate: () => import('./instruction-generate/orpc.gen').then(({ instructionGenerate }) => ({ instructionGenerate })), + login: () => import('./login/orpc.gen').then(({ login }) => ({ login })), + logout: () => import('./logout/orpc.gen').then(({ logout }) => ({ logout })), + notification: () => import('./notification/orpc.gen').then(({ notification }) => ({ notification })), + notion: () => import('./notion/orpc.gen').then(({ notion }) => ({ notion })), + oauth: () => import('./oauth/orpc.gen').then(({ oauth }) => ({ oauth })), + rag: () => import('./rag/orpc.gen').then(({ rag }) => ({ rag })), + refreshToken: () => import('./refresh-token/orpc.gen').then(({ refreshToken }) => ({ refreshToken })), + remoteFiles: () => import('./remote-files/orpc.gen').then(({ remoteFiles }) => ({ remoteFiles })), + resetPassword: () => import('./reset-password/orpc.gen').then(({ resetPassword }) => ({ resetPassword })), + ruleCodeGenerate: () => import('./rule-code-generate/orpc.gen').then(({ ruleCodeGenerate }) => ({ ruleCodeGenerate })), + ruleGenerate: () => import('./rule-generate/orpc.gen').then(({ ruleGenerate }) => ({ ruleGenerate })), + ruleStructuredOutputGenerate: () => + import('./rule-structured-output-generate/orpc.gen').then(({ ruleStructuredOutputGenerate }) => ({ ruleStructuredOutputGenerate })), + snippets: () => import('./snippets/orpc.gen').then(({ snippets }) => ({ snippets })), + spec: () => import('./spec/orpc.gen').then(({ spec }) => ({ spec })), + systemFeatures: () => import('./system-features/orpc.gen').then(({ systemFeatures }) => ({ systemFeatures })), + tagBindings: () => import('./tag-bindings/orpc.gen').then(({ tagBindings }) => ({ tagBindings })), + tags: () => import('./tags/orpc.gen').then(({ tags }) => ({ tags })), + test: () => import('./test/orpc.gen').then(({ test }) => ({ test })), + trialApps: () => import('./trial-apps/orpc.gen').then(({ trialApps }) => ({ trialApps })), + trialModels: () => import('./trial-models/orpc.gen').then(({ trialModels }) => ({ trialModels })), + website: () => import('./website/orpc.gen').then(({ website }) => ({ website })), + workflow: () => import('./workflow/orpc.gen').then(({ workflow }) => ({ workflow })), + workflowGenerate: () => import('./workflow-generate/orpc.gen').then(({ workflowGenerate }) => ({ workflowGenerate })), + workspaces: () => import('./workspaces/orpc.gen').then(({ workspaces }) => ({ workspaces })), } diff --git a/packages/contracts/openapi-ts.api.config.ts b/packages/contracts/openapi-ts.api.config.ts index 99e1f79ee2b..09ee6bf8707 100644 --- a/packages/contracts/openapi-ts.api.config.ts +++ b/packages/contracts/openapi-ts.api.config.ts @@ -344,16 +344,13 @@ const consoleContractEntryContent = (segments: string[]) => { } }) - const imports = contracts - .map(contract => `import { ${contract.name} } from './${contract.importPath}/orpc.gen'`) + const contractEntries = contracts + .map(contract => ` ${contract.name}: () => import('./${contract.importPath}/orpc.gen').then(({ ${contract.name} }) => ({ ${contract.name} })),`) .join('\n') - const contractEntries = contracts.map(contract => ` ${contract.name},`).join('\n') return `// This file is auto-generated by @hey-api/openapi-ts -${imports} - -export const contract = { +export const contractLoaders = { ${contractEntries} } ` diff --git a/web/app/components/plugins/marketplace/query.ts b/web/app/components/plugins/marketplace/query.ts index 35d99a2bd58..f6fda05e2d0 100644 --- a/web/app/components/plugins/marketplace/query.ts +++ b/web/app/components/plugins/marketplace/query.ts @@ -1,5 +1,5 @@ import type { PluginsSearchParams } from './types' -import type { MarketPlaceInputs } from '@/contract/router' +import type { MarketPlaceInputs } from '@/contract/marketplace' import { useInfiniteQuery, useQuery } from '@tanstack/react-query' import { marketplaceQuery } from '@/service/client' import { getMarketplaceCollectionsAndPlugins, getMarketplacePlugins } from './utils' diff --git a/web/contract/console/agent.ts b/web/contract/console/agent.ts new file mode 100644 index 00000000000..2b2ee1ac938 --- /dev/null +++ b/web/contract/console/agent.ts @@ -0,0 +1,13 @@ +import { agent } from '@dify/contracts/api/console/agent/orpc.gen' +import { agentDriveContracts } from './agent-drive' + +export const agentRouterContract = { + ...agent, + byAgentId: { + ...agent.byAgentId, + drive: { + ...agent.byAgentId.drive, + ...agentDriveContracts.byAgentId.drive, + }, + }, +} diff --git a/web/contract/console/apps.ts b/web/contract/console/apps.ts index f7046b1f277..c52eb3883ab 100644 --- a/web/contract/console/apps.ts +++ b/web/contract/console/apps.ts @@ -1,8 +1,10 @@ import type { AppListResponse, WorkflowOnlineUsersResponse } from '@/models/app' import type { CommonResponse } from '@/models/common' import type { AppModeEnum } from '@/types/app' +import { apps } from '@dify/contracts/api/console/apps/orpc.gen' import { type } from '@orpc/contract' import { base } from '../base' +import { agentDriveContracts } from './agent-drive' export type AppListSortBy = 'last_modified' | 'recently_created' | 'earliest_created' type AppListMode = AppModeEnum | 'agent' | 'channel' | 'all' @@ -85,3 +87,24 @@ export const workflowOnlineUsersContract = base } }>()) .output(type()) + +export const appsRouterContract = { + ...apps, + list: appListContract, + deleteApp: appDeleteContract, + starredList: appStarredListContract, + star: appStarContract, + unstar: appUnstarContract, + workflowOnlineUsers: workflowOnlineUsersContract, + byAppId: { + ...apps.byAppId, + agent: { + ...apps.byAppId.agent, + ...agentDriveContracts.byAppId.agent, + drive: { + ...apps.byAppId.agent.drive, + ...agentDriveContracts.byAppId.agent.drive, + }, + }, + }, +} diff --git a/web/contract/console/billing.ts b/web/contract/console/billing.ts index 08e1d0668f4..df20155a522 100644 --- a/web/contract/console/billing.ts +++ b/web/contract/console/billing.ts @@ -1,3 +1,4 @@ +import { billing } from '@dify/contracts/api/console/billing/orpc.gen' import { type } from '@orpc/contract' import { base } from '../base' @@ -23,3 +24,9 @@ export const bindPartnerStackContract = base } }>()) .output(type()) + +export const billingRouterContract = { + ...billing, + invoices: invoicesContract, + bindPartnerStack: bindPartnerStackContract, +} diff --git a/web/contract/console/explore.ts b/web/contract/console/explore.ts index 0ff93471360..11c75397b9a 100644 --- a/web/contract/console/explore.ts +++ b/web/contract/console/explore.ts @@ -5,6 +5,7 @@ import type { Banner } from '@/models/app' import type { App, AppCategory, InstalledApp } from '@/models/explore' import type { AppMeta } from '@/models/share' import type { AppModeEnum } from '@/types/app' +import { explore } from '@dify/contracts/api/console/explore/orpc.gen' import { type } from '@orpc/contract' import { base } from '../base' @@ -142,3 +143,18 @@ export const exploreBannersContract = base }) .input(type<{ query?: { language?: string } }>()) .output(type()) + +export const exploreRouterContract = { + ...explore, + apps: exploreAppsContract, + learnDifyApps: learnDifyAppsContract, + appDetail: exploreAppDetailContract, + installedApps: exploreInstalledAppsContract, + uninstallInstalledApp: exploreInstalledAppUninstallContract, + updateInstalledApp: exploreInstalledAppPinContract, + appAccessMode: exploreInstalledAppAccessModeContract, + updateAppAccessMode: exploreInstalledAppAccessModeUpdateContract, + installedAppParameters: exploreInstalledAppParametersContract, + installedAppMeta: exploreInstalledAppMetaContract, + banners: exploreBannersContract, +} diff --git a/web/contract/console/files.ts b/web/contract/console/files.ts index fb7b97b8a38..2325c067991 100644 --- a/web/contract/console/files.ts +++ b/web/contract/console/files.ts @@ -1,4 +1,5 @@ import type { PostFilesUploadResponse } from '@dify/contracts/api/console/files/types.gen' +import { files } from '@dify/contracts/api/console/files/orpc.gen' import { type } from '@orpc/contract' import { base } from '../base' @@ -14,3 +15,11 @@ export const fileUploadContract = base } }>()) .output(type()) + +export const filesRouterContract = { + ...files, + upload: { + ...files.upload, + post: fileUploadContract, + }, +} diff --git a/web/contract/console/model-providers.ts b/web/contract/console/model-providers.ts index 2feab369abb..97c0fd394cf 100644 --- a/web/contract/console/model-providers.ts +++ b/web/contract/console/model-providers.ts @@ -31,3 +31,8 @@ export const changePreferredProviderTypeContract = base } }>()) .output(type()) + +export const modelProvidersRouterContract = { + models: modelProvidersModelsContract, + changePreferredProviderType: changePreferredProviderTypeContract, +} diff --git a/web/contract/console/plugins.ts b/web/contract/console/plugins.ts index 1b1bc70ffa6..4269fd2acad 100644 --- a/web/contract/console/plugins.ts +++ b/web/contract/console/plugins.ts @@ -25,3 +25,8 @@ export const pluginLatestVersionsContract = base } }>()) .output(type()) + +export const pluginsRouterContract = { + checkInstalled: pluginCheckInstalledContract, + latestVersions: pluginLatestVersionsContract, +} diff --git a/web/contract/console/snippets.ts b/web/contract/console/snippets.ts index 159ccda7e40..0c9a9f7d9ec 100644 --- a/web/contract/console/snippets.ts +++ b/web/contract/console/snippets.ts @@ -342,3 +342,31 @@ export const stopSnippetWorkflowTaskContract = base } }>()) .output(type()) + +export const snippetsRouterContract = { + list: listCustomizedSnippetsContract, + create: createCustomizedSnippetContract, + detail: getCustomizedSnippetContract, + update: updateCustomizedSnippetContract, + delete: deleteCustomizedSnippetContract, + export: exportCustomizedSnippetContract, + import: importCustomizedSnippetContract, + confirmImport: confirmSnippetImportContract, + checkDependencies: checkSnippetDependenciesContract, + incrementUseCount: incrementSnippetUseCountContract, + draftWorkflow: getSnippetDraftWorkflowContract, + syncDraftWorkflow: syncSnippetDraftWorkflowContract, + draftConfig: getSnippetDraftConfigContract, + publishedWorkflow: getSnippetPublishedWorkflowContract, + publishWorkflow: publishSnippetWorkflowContract, + defaultBlockConfigs: getSnippetDefaultBlockConfigsContract, + workflowRuns: listSnippetWorkflowRunsContract, + workflowRunDetail: getSnippetWorkflowRunDetailContract, + workflowRunNodeExecutions: listSnippetWorkflowRunNodeExecutionsContract, + runDraftNode: runSnippetDraftNodeContract, + lastDraftNodeRun: getSnippetDraftNodeLastRunContract, + runDraftIterationNode: runSnippetDraftIterationNodeContract, + runDraftLoopNode: runSnippetDraftLoopNodeContract, + runDraftWorkflow: runSnippetDraftWorkflowContract, + stopWorkflowTask: stopSnippetWorkflowTaskContract, +} diff --git a/web/contract/console/tags.ts b/web/contract/console/tags.ts index 417ec2f9f07..632729cbf9b 100644 --- a/web/contract/console/tags.ts +++ b/web/contract/console/tags.ts @@ -1,3 +1,4 @@ +import { tags } from '@dify/contracts/api/console/tags/orpc.gen' import { type } from '@orpc/contract' import { base } from '../base' @@ -89,3 +90,13 @@ export const tagBindingRemoveContract = base } }>()) .output(type()) + +export const tagsRouterContract = { + ...tags, + list: tagListContract, + create: tagCreateContract, + update: tagUpdateContract, + delete: tagDeleteContract, + bind: tagBindingCreateContract, + unbind: tagBindingRemoveContract, +} diff --git a/web/contract/console/trigger.ts b/web/contract/console/trigger.ts index 41a326ccf56..3eda2ff11f2 100644 --- a/web/contract/console/trigger.ts +++ b/web/contract/console/trigger.ts @@ -117,3 +117,21 @@ export const triggerOAuthInitiateContract = base .route({ path: '/workspaces/current/trigger-provider/{provider}/subscriptions/oauth/authorize', method: 'GET' }) .input(type<{ params: { provider: string } }>()) .output(type<{ authorization_url: string, subscription_builder: TriggerSubscriptionBuilder }>()) + +export const triggersRouterContract = { + list: triggersContract, + providerInfo: triggerProviderInfoContract, + subscriptions: triggerSubscriptionsContract, + subscriptionBuilderCreate: triggerSubscriptionBuilderCreateContract, + subscriptionBuilderUpdate: triggerSubscriptionBuilderUpdateContract, + subscriptionBuilderVerifyUpdate: triggerSubscriptionBuilderVerifyUpdateContract, + subscriptionVerify: triggerSubscriptionVerifyContract, + subscriptionBuild: triggerSubscriptionBuildContract, + subscriptionDelete: triggerSubscriptionDeleteContract, + subscriptionUpdate: triggerSubscriptionUpdateContract, + subscriptionBuilderLogs: triggerSubscriptionBuilderLogsContract, + oauthConfig: triggerOAuthConfigContract, + oauthConfigure: triggerOAuthConfigureContract, + oauthDelete: triggerOAuthDeleteContract, + oauthInitiate: triggerOAuthInitiateContract, +} diff --git a/web/contract/console/try-app.ts b/web/contract/console/try-app.ts index f1a7551d7fe..eab05e7f135 100644 --- a/web/contract/console/try-app.ts +++ b/web/contract/console/try-app.ts @@ -1,6 +1,7 @@ import type { ChatConfig } from '@/app/components/base/chat/types' import type { DataSetListResponse } from '@/models/datasets' import type { TryAppFlowPreview, TryAppInfo } from '@/models/try-app' +import { trialApps } from '@dify/contracts/api/console/trial-apps/orpc.gen' import { type } from '@orpc/contract' import { base } from '../base' @@ -54,3 +55,11 @@ export const trialAppParametersContract = base } }>()) .output(type()) + +export const trialAppsRouterContract = { + ...trialApps, + info: trialAppInfoContract, + datasets: trialAppDatasetsContract, + parameters: trialAppParametersContract, + workflows: trialAppWorkflowsContract, +} diff --git a/web/contract/console/workflow.ts b/web/contract/console/workflow.ts index 2c70a58e25a..81ffd4a150b 100644 --- a/web/contract/console/workflow.ts +++ b/web/contract/console/workflow.ts @@ -78,3 +78,10 @@ export const workflowDraftUpdateFeaturesContract = base } }>()) .output(type()) + +export const workflowDraftRouterContract = { + environmentVariables: workflowDraftEnvironmentVariablesContract, + updateEnvironmentVariables: workflowDraftUpdateEnvironmentVariablesContract, + updateConversationVariables: workflowDraftUpdateConversationVariablesContract, + updateFeatures: workflowDraftUpdateFeaturesContract, +} diff --git a/web/contract/console/workspaces.ts b/web/contract/console/workspaces.ts index e355ce80a7e..e81e0f22d14 100644 --- a/web/contract/console/workspaces.ts +++ b/web/contract/console/workspaces.ts @@ -1,3 +1,4 @@ +import { workspaces } from '@dify/contracts/api/console/workspaces/orpc.gen' import { type } from '@orpc/contract' import { base } from '../base' @@ -54,3 +55,11 @@ export const workspaceSwitchContract = base body: SwitchWorkspaceRequest }>()) .output(type()) + +export const workspacesRouterContract = { + ...workspaces, + get: workspacesGetContract, + switch: { + post: workspaceSwitchContract, + }, +} diff --git a/web/contract/marketplace.ts b/web/contract/marketplace.ts index 3fdb3441612..f3bd953ef3f 100644 --- a/web/contract/marketplace.ts +++ b/web/contract/marketplace.ts @@ -1,10 +1,11 @@ +import type { InferContractRouterInputs } from '@orpc/contract' import type { CollectionsAndPluginsSearchParams, MarketplaceCollection, PluginsSearchParams } from '@/app/components/plugins/marketplace/types' import type { Plugin, PluginsFromMarketplaceResponse } from '@/app/components/plugins/types' import type { MarketplaceTemplate } from '@/types/marketplace-template' import { type } from '@orpc/contract' import { base } from './base' -export const collectionsContract = base +const collectionsContract = base .route({ path: '/collections', method: 'GET', @@ -22,7 +23,7 @@ export const collectionsContract = base }>(), ) -export const collectionPluginsContract = base +const collectionPluginsContract = base .route({ path: '/collections/{collectionId}/plugins', method: 'POST', @@ -43,7 +44,7 @@ export const collectionPluginsContract = base }>(), ) -export const searchAdvancedContract = base +const searchAdvancedContract = base .route({ path: '/{kind}/search/advanced', method: 'POST', @@ -56,7 +57,7 @@ export const searchAdvancedContract = base }>()) .output(type<{ data: PluginsFromMarketplaceResponse }>()) -export const templateDetailContract = base +const templateDetailContract = base .route({ path: '/templates/{templateId}', method: 'GET', @@ -68,7 +69,7 @@ export const templateDetailContract = base }>()) .output(type<{ data: MarketplaceTemplate }>()) -export const downloadPluginContract = base +const downloadPluginContract = base .route({ path: '/plugins/{organization}/{pluginName}/{version}/download', method: 'GET', @@ -81,3 +82,13 @@ export const downloadPluginContract = base } }>()) .output(type()) + +export const marketplaceRouterContract = { + collections: collectionsContract, + collectionPlugins: collectionPluginsContract, + searchAdvanced: searchAdvancedContract, + templateDetail: templateDetailContract, + downloadPlugin: downloadPluginContract, +} + +export type MarketPlaceInputs = InferContractRouterInputs diff --git a/web/contract/router.ts b/web/contract/router.ts index c00657892f0..012cdf096f2 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -1,249 +1,120 @@ -import type { InferContractRouterInputs } from '@orpc/contract' -import { contract as communityContract } from '@dify/contracts/api/console/orpc.gen' +import { account } from '@dify/contracts/api/console/account/orpc.gen' +import { activate } from '@dify/contracts/api/console/activate/orpc.gen' +import { allWorkspaces } from '@dify/contracts/api/console/all-workspaces/orpc.gen' +import { apiBasedExtension } from '@dify/contracts/api/console/api-based-extension/orpc.gen' +import { apiKeyAuth } from '@dify/contracts/api/console/api-key-auth/orpc.gen' +import { appDslVersion } from '@dify/contracts/api/console/app-dsl-version/orpc.gen' +import { app } from '@dify/contracts/api/console/app/orpc.gen' +import { auth } from '@dify/contracts/api/console/auth/orpc.gen' +import { codeBasedExtension } from '@dify/contracts/api/console/code-based-extension/orpc.gen' +import { compliance } from '@dify/contracts/api/console/compliance/orpc.gen' +import { dataSource } from '@dify/contracts/api/console/data-source/orpc.gen' +import { datasets } from '@dify/contracts/api/console/datasets/orpc.gen' +import { emailCodeLogin } from '@dify/contracts/api/console/email-code-login/orpc.gen' +import { emailRegister } from '@dify/contracts/api/console/email-register/orpc.gen' +import { features } from '@dify/contracts/api/console/features/orpc.gen' +import { forgotPassword } from '@dify/contracts/api/console/forgot-password/orpc.gen' +import { form } from '@dify/contracts/api/console/form/orpc.gen' +import { info } from '@dify/contracts/api/console/info/orpc.gen' +import { installedApps } from '@dify/contracts/api/console/installed-apps/orpc.gen' +import { instructionGenerate } from '@dify/contracts/api/console/instruction-generate/orpc.gen' +import { login } from '@dify/contracts/api/console/login/orpc.gen' +import { logout } from '@dify/contracts/api/console/logout/orpc.gen' +import { notion } from '@dify/contracts/api/console/notion/orpc.gen' +import { oauth } from '@dify/contracts/api/console/oauth/orpc.gen' +import { rag } from '@dify/contracts/api/console/rag/orpc.gen' +import { refreshToken } from '@dify/contracts/api/console/refresh-token/orpc.gen' +import { remoteFiles } from '@dify/contracts/api/console/remote-files/orpc.gen' +import { resetPassword } from '@dify/contracts/api/console/reset-password/orpc.gen' +import { ruleCodeGenerate } from '@dify/contracts/api/console/rule-code-generate/orpc.gen' +import { ruleGenerate } from '@dify/contracts/api/console/rule-generate/orpc.gen' +import { ruleStructuredOutputGenerate } from '@dify/contracts/api/console/rule-structured-output-generate/orpc.gen' +import { spec } from '@dify/contracts/api/console/spec/orpc.gen' +import { systemFeatures } from '@dify/contracts/api/console/system-features/orpc.gen' +import { tagBindings } from '@dify/contracts/api/console/tag-bindings/orpc.gen' +import { test } from '@dify/contracts/api/console/test/orpc.gen' +import { trialModels } from '@dify/contracts/api/console/trial-models/orpc.gen' +import { website } from '@dify/contracts/api/console/website/orpc.gen' +import { workflowGenerate } from '@dify/contracts/api/console/workflow-generate/orpc.gen' +import { workflow } from '@dify/contracts/api/console/workflow/orpc.gen' import { contract as enterpriseContract } from '@dify/contracts/enterprise/orpc.gen' import { rbacAccessConfigContract } from './console/access-control' -import { agentDriveContracts } from './console/agent-drive' -import { - appDeleteContract, - appListContract, - appStarContract, - appStarredListContract, - appUnstarContract, - workflowOnlineUsersContract, -} from './console/apps' -import { bindPartnerStackContract, invoicesContract } from './console/billing' -import { - exploreAppDetailContract, - exploreAppsContract, - exploreBannersContract, - exploreInstalledAppAccessModeContract, - exploreInstalledAppAccessModeUpdateContract, - exploreInstalledAppMetaContract, - exploreInstalledAppParametersContract, - exploreInstalledAppPinContract, - exploreInstalledAppsContract, - exploreInstalledAppUninstallContract, - learnDifyAppsContract, -} from './console/explore' -import { fileUploadContract } from './console/files' -import { changePreferredProviderTypeContract, modelProvidersModelsContract } from './console/model-providers' +import { agentRouterContract } from './console/agent' +import { appsRouterContract } from './console/apps' +import { billingRouterContract } from './console/billing' +import { exploreRouterContract } from './console/explore' +import { filesRouterContract } from './console/files' +import { modelProvidersRouterContract } from './console/model-providers' import { notificationContract, notificationDismissContract } from './console/notification' -import { pluginCheckInstalledContract, pluginLatestVersionsContract } from './console/plugins' -import { - checkSnippetDependenciesContract, - confirmSnippetImportContract, - createCustomizedSnippetContract, - deleteCustomizedSnippetContract, - exportCustomizedSnippetContract, - getCustomizedSnippetContract, - getSnippetDefaultBlockConfigsContract, - getSnippetDraftConfigContract, - getSnippetDraftNodeLastRunContract, - getSnippetDraftWorkflowContract, - getSnippetPublishedWorkflowContract, - getSnippetWorkflowRunDetailContract, - importCustomizedSnippetContract, - incrementSnippetUseCountContract, - listCustomizedSnippetsContract, - listSnippetWorkflowRunNodeExecutionsContract, - listSnippetWorkflowRunsContract, - publishSnippetWorkflowContract, - runSnippetDraftIterationNodeContract, - runSnippetDraftLoopNodeContract, - runSnippetDraftNodeContract, - runSnippetDraftWorkflowContract, - stopSnippetWorkflowTaskContract, - syncSnippetDraftWorkflowContract, - updateCustomizedSnippetContract, -} from './console/snippets' -import { - tagBindingCreateContract, - tagBindingRemoveContract, - tagCreateContract, - tagDeleteContract, - tagListContract, - tagUpdateContract, -} from './console/tags' -import { - triggerOAuthConfigContract, - triggerOAuthConfigureContract, - triggerOAuthDeleteContract, - triggerOAuthInitiateContract, - triggerProviderInfoContract, - triggersContract, - triggerSubscriptionBuildContract, - triggerSubscriptionBuilderCreateContract, - triggerSubscriptionBuilderLogsContract, - triggerSubscriptionBuilderUpdateContract, - triggerSubscriptionBuilderVerifyUpdateContract, - triggerSubscriptionDeleteContract, - triggerSubscriptionsContract, - triggerSubscriptionUpdateContract, - triggerSubscriptionVerifyContract, -} from './console/trigger' -import { trialAppDatasetsContract, trialAppInfoContract, trialAppParametersContract, trialAppWorkflowsContract } from './console/try-app' -import { - workflowDraftEnvironmentVariablesContract, - workflowDraftUpdateConversationVariablesContract, - workflowDraftUpdateEnvironmentVariablesContract, - workflowDraftUpdateFeaturesContract, -} from './console/workflow' +import { pluginsRouterContract } from './console/plugins' +import { snippetsRouterContract } from './console/snippets' +import { tagsRouterContract } from './console/tags' +import { triggersRouterContract } from './console/trigger' +import { trialAppsRouterContract } from './console/try-app' +import { workflowDraftRouterContract } from './console/workflow' import { workflowCommentContracts } from './console/workflow-comment' -import { workspacesGetContract, workspaceSwitchContract } from './console/workspaces' -import { collectionPluginsContract, collectionsContract, downloadPluginContract, searchAdvancedContract, templateDetailContract } from './marketplace' +import { workspacesRouterContract } from './console/workspaces' -export const marketplaceRouterContract = { - collections: collectionsContract, - collectionPlugins: collectionPluginsContract, - searchAdvanced: searchAdvancedContract, - templateDetail: templateDetailContract, - downloadPlugin: downloadPluginContract, +const communityContract = { + account, + activate, + allWorkspaces, + apiBasedExtension, + apiKeyAuth, + app, + appDslVersion, + auth, + codeBasedExtension, + compliance, + dataSource, + datasets, + emailCodeLogin, + emailRegister, + features, + forgotPassword, + form, + info, + installedApps, + instructionGenerate, + login, + logout, + notion, + oauth, + rag, + refreshToken, + remoteFiles, + resetPassword, + ruleCodeGenerate, + ruleGenerate, + ruleStructuredOutputGenerate, + spec, + systemFeatures, + tagBindings, + test, + trialModels, + website, + workflow, + workflowGenerate, } -export type MarketPlaceInputs = InferContractRouterInputs - export const consoleRouterContract = { enterprise: enterpriseContract, ...communityContract, - apps: { - ...communityContract.apps, - list: appListContract, - deleteApp: appDeleteContract, - starredList: appStarredListContract, - star: appStarContract, - unstar: appUnstarContract, - workflowOnlineUsers: workflowOnlineUsersContract, - byAppId: { - ...communityContract.apps.byAppId, - agent: { - ...communityContract.apps.byAppId.agent, - ...agentDriveContracts.byAppId.agent, - drive: { - ...communityContract.apps.byAppId.agent.drive, - ...agentDriveContracts.byAppId.agent.drive, - }, - }, - }, - }, - agent: { - ...communityContract.agent, - byAgentId: { - ...communityContract.agent.byAgentId, - drive: { - ...communityContract.agent.byAgentId.drive, - ...agentDriveContracts.byAgentId.drive, - }, - }, - }, - explore: { - ...communityContract.explore, - apps: exploreAppsContract, - learnDifyApps: learnDifyAppsContract, - appDetail: exploreAppDetailContract, - installedApps: exploreInstalledAppsContract, - uninstallInstalledApp: exploreInstalledAppUninstallContract, - updateInstalledApp: exploreInstalledAppPinContract, - appAccessMode: exploreInstalledAppAccessModeContract, - updateAppAccessMode: exploreInstalledAppAccessModeUpdateContract, - installedAppParameters: exploreInstalledAppParametersContract, - installedAppMeta: exploreInstalledAppMetaContract, - banners: exploreBannersContract, - }, - trialApps: { - ...communityContract.trialApps, - info: trialAppInfoContract, - datasets: trialAppDatasetsContract, - parameters: trialAppParametersContract, - workflows: trialAppWorkflowsContract, - }, - files: { - ...communityContract.files, - upload: { - ...communityContract.files.upload, - post: fileUploadContract, - }, - }, - modelProviders: { - models: modelProvidersModelsContract, - changePreferredProviderType: changePreferredProviderTypeContract, - }, - plugins: { - checkInstalled: pluginCheckInstalledContract, - latestVersions: pluginLatestVersionsContract, - }, - rbacAccessConfig: rbacAccessConfigContract, - snippets: { - list: listCustomizedSnippetsContract, - create: createCustomizedSnippetContract, - detail: getCustomizedSnippetContract, - update: updateCustomizedSnippetContract, - delete: deleteCustomizedSnippetContract, - export: exportCustomizedSnippetContract, - import: importCustomizedSnippetContract, - confirmImport: confirmSnippetImportContract, - checkDependencies: checkSnippetDependenciesContract, - incrementUseCount: incrementSnippetUseCountContract, - draftWorkflow: getSnippetDraftWorkflowContract, - syncDraftWorkflow: syncSnippetDraftWorkflowContract, - draftConfig: getSnippetDraftConfigContract, - publishedWorkflow: getSnippetPublishedWorkflowContract, - publishWorkflow: publishSnippetWorkflowContract, - defaultBlockConfigs: getSnippetDefaultBlockConfigsContract, - workflowRuns: listSnippetWorkflowRunsContract, - workflowRunDetail: getSnippetWorkflowRunDetailContract, - workflowRunNodeExecutions: listSnippetWorkflowRunNodeExecutionsContract, - runDraftNode: runSnippetDraftNodeContract, - lastDraftNodeRun: getSnippetDraftNodeLastRunContract, - runDraftIterationNode: runSnippetDraftIterationNodeContract, - runDraftLoopNode: runSnippetDraftLoopNodeContract, - runDraftWorkflow: runSnippetDraftWorkflowContract, - stopWorkflowTask: stopSnippetWorkflowTaskContract, - }, - billing: { - ...communityContract.billing, - invoices: invoicesContract, - bindPartnerStack: bindPartnerStackContract, - }, - workflowDraft: { - environmentVariables: workflowDraftEnvironmentVariablesContract, - updateEnvironmentVariables: workflowDraftUpdateEnvironmentVariablesContract, - updateConversationVariables: workflowDraftUpdateConversationVariablesContract, - updateFeatures: workflowDraftUpdateFeaturesContract, - }, - workflowComments: workflowCommentContracts, + agent: agentRouterContract, + apps: appsRouterContract, + billing: billingRouterContract, + explore: exploreRouterContract, + files: filesRouterContract, + modelProviders: modelProvidersRouterContract, notification: notificationContract, notificationDismiss: notificationDismissContract, - tags: { - ...communityContract.tags, - list: tagListContract, - create: tagCreateContract, - update: tagUpdateContract, - delete: tagDeleteContract, - bind: tagBindingCreateContract, - unbind: tagBindingRemoveContract, - }, - triggers: { - list: triggersContract, - providerInfo: triggerProviderInfoContract, - subscriptions: triggerSubscriptionsContract, - subscriptionBuilderCreate: triggerSubscriptionBuilderCreateContract, - subscriptionBuilderUpdate: triggerSubscriptionBuilderUpdateContract, - subscriptionBuilderVerifyUpdate: triggerSubscriptionBuilderVerifyUpdateContract, - subscriptionVerify: triggerSubscriptionVerifyContract, - subscriptionBuild: triggerSubscriptionBuildContract, - subscriptionDelete: triggerSubscriptionDeleteContract, - subscriptionUpdate: triggerSubscriptionUpdateContract, - subscriptionBuilderLogs: triggerSubscriptionBuilderLogsContract, - oauthConfig: triggerOAuthConfigContract, - oauthConfigure: triggerOAuthConfigureContract, - oauthDelete: triggerOAuthDeleteContract, - oauthInitiate: triggerOAuthInitiateContract, - }, - workspaces: { - ...communityContract.workspaces, - get: workspacesGetContract, - switch: { - post: workspaceSwitchContract, - }, - }, + plugins: pluginsRouterContract, + rbacAccessConfig: rbacAccessConfigContract, + snippets: snippetsRouterContract, + tags: tagsRouterContract, + triggers: triggersRouterContract, + trialApps: trialAppsRouterContract, + workflowComments: workflowCommentContracts, + workflowDraft: workflowDraftRouterContract, + workspaces: workspacesRouterContract, } diff --git a/web/features/agent-v2/agent-detail/configure/build-draft-query.ts b/web/features/agent-v2/agent-detail/configure/build-draft-query.ts index c1799c61e83..77ed5f8509e 100644 --- a/web/features/agent-v2/agent-detail/configure/build-draft-query.ts +++ b/web/features/agent-v2/agent-detail/configure/build-draft-query.ts @@ -2,20 +2,21 @@ import type { ContractRouterClient } from '@orpc/contract' import type { JsonifiedClient } from '@orpc/openapi-client' +import type { ConsoleRouterContract } from '@/service/console-link' import { createORPCClient } from '@orpc/client' import { OpenAPILink } from '@orpc/openapi-client/fetch' import { createTanstackQueryUtils } from '@orpc/tanstack-query' import { API_PREFIX } from '@/config' -import { consoleRouterContract } from '@/contract/router' // eslint-disable-next-line no-restricted-imports import { request } from '@/service/base' import { getBaseURL } from '@/service/client' +import { createConsoleDynamicLink } from '@/service/console-link' type AgentConfigureConsoleClientContext = { silent?: boolean } -const agentConfigureConsoleLink = new OpenAPILink(consoleRouterContract, { +const agentConfigureConsoleLink = createConsoleDynamicLink(contract => new OpenAPILink(contract, { url: getBaseURL(API_PREFIX), fetch: (input, init, options) => { return request( @@ -28,9 +29,9 @@ const agentConfigureConsoleLink = new OpenAPILink> +const agentConfigureConsoleClient: JsonifiedClient> = createORPCClient(agentConfigureConsoleLink) export const agentConfigureConsoleQuery = createTanstackQueryUtils(agentConfigureConsoleClient, { diff --git a/web/knip.config.ts b/web/knip.config.ts index b32c7ef6b22..5e7000163d8 100644 --- a/web/knip.config.ts +++ b/web/knip.config.ts @@ -38,7 +38,6 @@ const config: KnipConfig = { 'i18n-config/locale-resources/*.ts', ], ignoreBinaries: [ - 'only-allow', 'pbcopy', 'which', ], diff --git a/web/service/client.ts b/web/service/client.ts index e5c1abc7edf..896784683d5 100644 --- a/web/service/client.ts +++ b/web/service/client.ts @@ -5,11 +5,13 @@ import type { ListReleasesResponse, PrecheckReleaseRequest, } from '@dify/contracts/enterprise/types.gen' -import type { ContractRouterClient } from '@orpc/contract' +import type { ClientLink } from '@orpc/client' +import type { AnyContractRouter, ContractRouterClient } from '@orpc/contract' import type { JsonifiedClient } from '@orpc/openapi-client' import type { RouterUtils } from '@orpc/tanstack-query' import type { InfiniteData, QueryClient, QueryKey } from '@tanstack/react-query' import type { Tag } from '@/contract/console/tags' +import type { consoleRouterContract } from '@/contract/router' import { createORPCClient, onError } from '@orpc/client' import { OpenAPILink } from '@orpc/openapi-client/fetch' import { createTanstackQueryUtils } from '@orpc/tanstack-query' @@ -19,13 +21,11 @@ import { IS_MARKETPLACE, MARKETPLACE_API_PREFIX, } from '@/config' -import { - consoleRouterContract, - marketplaceRouterContract, -} from '@/contract/router' +import { marketplaceRouterContract } from '@/contract/marketplace' import { isClient } from '@/utils/client' // eslint-disable-next-line no-restricted-imports import { request } from './base' +import { createConsoleDynamicLink } from './console-link' function getMarketplaceHeaders() { return new Headers({ @@ -58,6 +58,30 @@ export function getBaseURL(path: string) { return url } +type ConsoleClientContext = Record +type ConsoleClientLink = ClientLink + +function createConsoleOpenAPILink(contract: AnyContractRouter): ConsoleClientLink { + return new OpenAPILink(contract, { + url: getBaseURL(API_PREFIX), + fetch: (input, init) => { + return request( + input.url, + init, + { + fetchCompat: true, + request: input, + }, + ) + }, + interceptors: [ + onError((error) => { + console.error(error) + }), + ], + }) +} + const marketplaceLink = new OpenAPILink(marketplaceRouterContract, { url: MARKETPLACE_API_PREFIX, headers: () => (getMarketplaceHeaders()), @@ -323,24 +347,7 @@ async function invalidateReleaseMutationQueries( ]) } -const consoleLink = new OpenAPILink(consoleRouterContract, { - url: getBaseURL(API_PREFIX), - fetch: (input, init) => { - return request( - input.url, - init, - { - fetchCompat: true, - request: input, - }, - ) - }, - interceptors: [ - onError((error) => { - console.error(error) - }), - ], -}) +const consoleLink = createConsoleDynamicLink(createConsoleOpenAPILink) export const consoleClient: JsonifiedClient> = createORPCClient(consoleLink) diff --git a/web/service/console-link.ts b/web/service/console-link.ts new file mode 100644 index 00000000000..e6423fa149a --- /dev/null +++ b/web/service/console-link.ts @@ -0,0 +1,32 @@ +import type { ClientContext, ClientLink } from '@orpc/client' +import type { AnyContractRouter } from '@orpc/contract' +import type { consoleRouterContract } from '@/contract/router' +import { DynamicLink } from '@orpc/client' +import { loadConsoleContractForSegment } from './console-router-loader' + +export type ConsoleRouterContract = typeof consoleRouterContract + +export function createConsoleDynamicLink( + createLink: (contract: AnyContractRouter) => ClientLink, +) { + const routerLinkPromises = new Map>>() + + function getRouterLink(path: readonly string[]) { + const segment = path[0] + if (!segment) + throw new Error('Console contract path is empty.') + + let routerLinkPromise = routerLinkPromises.get(segment) + if (!routerLinkPromise) { + routerLinkPromise = loadConsoleContractForSegment(segment).then(createLink).catch((error) => { + routerLinkPromises.delete(segment) + throw error + }) + routerLinkPromises.set(segment, routerLinkPromise) + } + + return routerLinkPromise + } + + return new DynamicLink((_options, path) => getRouterLink(path)) +} diff --git a/web/service/console-router-loader.ts b/web/service/console-router-loader.ts new file mode 100644 index 00000000000..a84a73f1853 --- /dev/null +++ b/web/service/console-router-loader.ts @@ -0,0 +1,51 @@ +import type { AnyContractRouter } from '@orpc/contract' +import { contractLoaders } from '@dify/contracts/api/console/orpc.gen' + +const wrapConsoleContract = (segment: string, contract: unknown) => ({ [segment]: contract }) as AnyContractRouter + +async function loadGeneratedConsoleContract(segment: string) { + const loader = contractLoaders[segment as keyof typeof contractLoaders] + if (!loader) + return null + + return loader() as Promise +} + +const customConsoleContractLoaders: Record Promise> = { + agent: () => import('@/contract/console/agent').then(({ agentRouterContract }) => wrapConsoleContract('agent', agentRouterContract)), + apps: () => import('@/contract/console/apps').then(({ appsRouterContract }) => wrapConsoleContract('apps', appsRouterContract)), + billing: () => import('@/contract/console/billing').then(({ billingRouterContract }) => wrapConsoleContract('billing', billingRouterContract)), + enterprise: () => import('@dify/contracts/enterprise/orpc.gen').then(({ contract }) => wrapConsoleContract('enterprise', contract)), + explore: () => import('@/contract/console/explore').then(({ exploreRouterContract }) => wrapConsoleContract('explore', exploreRouterContract)), + files: () => import('@/contract/console/files').then(({ filesRouterContract }) => wrapConsoleContract('files', filesRouterContract)), + modelProviders: () => + import('@/contract/console/model-providers').then(({ modelProvidersRouterContract }) => wrapConsoleContract('modelProviders', modelProvidersRouterContract)), + notification: () => + import('@/contract/console/notification').then(({ notificationContract }) => wrapConsoleContract('notification', notificationContract)), + notificationDismiss: () => + import('@/contract/console/notification').then(({ notificationDismissContract }) => wrapConsoleContract('notificationDismiss', notificationDismissContract)), + plugins: () => import('@/contract/console/plugins').then(({ pluginsRouterContract }) => wrapConsoleContract('plugins', pluginsRouterContract)), + rbacAccessConfig: () => + import('@/contract/console/access-control').then(({ rbacAccessConfigContract }) => wrapConsoleContract('rbacAccessConfig', rbacAccessConfigContract)), + snippets: () => import('@/contract/console/snippets').then(({ snippetsRouterContract }) => wrapConsoleContract('snippets', snippetsRouterContract)), + tags: () => import('@/contract/console/tags').then(({ tagsRouterContract }) => wrapConsoleContract('tags', tagsRouterContract)), + triggers: () => import('@/contract/console/trigger').then(({ triggersRouterContract }) => wrapConsoleContract('triggers', triggersRouterContract)), + trialApps: () => import('@/contract/console/try-app').then(({ trialAppsRouterContract }) => wrapConsoleContract('trialApps', trialAppsRouterContract)), + workflowComments: () => + import('@/contract/console/workflow-comment').then(({ workflowCommentContracts }) => wrapConsoleContract('workflowComments', workflowCommentContracts)), + workflowDraft: () => + import('@/contract/console/workflow').then(({ workflowDraftRouterContract }) => wrapConsoleContract('workflowDraft', workflowDraftRouterContract)), + workspaces: () => import('@/contract/console/workspaces').then(({ workspacesRouterContract }) => wrapConsoleContract('workspaces', workspacesRouterContract)), +} + +export async function loadConsoleContractForSegment(segment: string) { + const customContractLoader = customConsoleContractLoaders[segment] + if (customContractLoader) + return customContractLoader() + + const generatedContract = await loadGeneratedConsoleContract(segment) + if (generatedContract) + return generatedContract + + throw new Error(`Console contract segment "${segment}" is not configured.`) +} diff --git a/web/service/server.ts b/web/service/server.ts index 22614236f3d..e41dec8fd36 100644 --- a/web/service/server.ts +++ b/web/service/server.ts @@ -1,5 +1,7 @@ -import type { ContractRouterClient } from '@orpc/contract' +import type { ClientLink } from '@orpc/client' +import type { AnyContractRouter, ContractRouterClient } from '@orpc/contract' import type { JsonifiedClient } from '@orpc/openapi-client' +import type { consoleRouterContract } from '@/contract/router' import { createORPCClient, onError } from '@orpc/client' import { OpenAPILink } from '@orpc/openapi-client/fetch' import { createTanstackQueryUtils } from '@orpc/tanstack-query' @@ -9,7 +11,7 @@ import { CSRF_HEADER_NAME, } from '@/config' import { SERVER_CONSOLE_API_PREFIX } from '@/config/server' -import { consoleRouterContract } from '@/contract/router' +import { createConsoleDynamicLink } from './console-link' import 'server-only' @@ -68,6 +70,29 @@ const createServerConsoleRequestHeaders = (context: ServerConsoleClientContext | return requestHeaders } +type ServerConsoleClientLink = ClientLink + +function createServerConsoleOpenAPILink(contract: AnyContractRouter): ServerConsoleClientLink { + return new OpenAPILink(contract, { + url: getServerConsoleApiPrefix, + headers: ({ context }) => createServerConsoleRequestHeaders(context), + fetch: (request, init) => { + if (request.body && !request.headers.has('content-type')) + request.headers.set('Content-Type', 'application/json') + + return globalThis.fetch(request, { + ...init, + cache: 'no-store', + }) + }, + interceptors: [ + onError((error) => { + console.error(error) + }), + ], + }) +} + export const getServerConsoleClientContext = async (): Promise => { const { cookies, headers } = await import('@/next/headers') const requestHeaders = await headers() @@ -82,24 +107,7 @@ export const getServerConsoleClientContext = async (): Promise createServerConsoleRequestHeaders(await getServerConsoleClientContext()) -const serverConsoleLink = new OpenAPILink(consoleRouterContract, { - url: getServerConsoleApiPrefix, - headers: ({ context }) => createServerConsoleRequestHeaders(context), - fetch: (request, init) => { - if (request.body && !request.headers.has('content-type')) - request.headers.set('Content-Type', 'application/json') - - return globalThis.fetch(request, { - ...init, - cache: 'no-store', - }) - }, - interceptors: [ - onError((error) => { - console.error(error) - }), - ], -}) +const serverConsoleLink = createConsoleDynamicLink(createServerConsoleOpenAPILink) export const serverConsoleClient: JsonifiedClient> = createORPCClient(serverConsoleLink)