feat(mutate): single funnel for user-intent commands and invoke gate

Adds src/services/mutate.ts: one path for optimistic apply, services-layer
invoke, Busy handling, failure rollback, and the error toast, with busy
bookkeeping mirrored into the app store. The patch hooks (verge, clash,
profiles) now route through it. Moves the remaining direct invokes
(unlock ×3, DNS ×4) into cmds.ts and turns on the invoke-only-in-services
architecture gate in CI.
This commit is contained in:
Tunglies
2026-10-01 14:34:17 +08:00
committed by GitHub
parent 5b53c32de1
commit 763338dade
10 changed files with 204 additions and 48 deletions
+36 -1
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/core'
import { Channel, invoke } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import type { CommandFailure } from '@/services/notice-service'
@@ -182,6 +182,22 @@ export async function setDnsOverride(
>('set_dns_override', { profileUid, enabled, confirmation })
}
export async function getDnsConfigContent() {
return invoke<string | null>('get_dns_config_content')
}
export async function saveDnsConfig(dnsConfig: Record<string, unknown>) {
return invoke<void>('save_dns_config', { dnsConfig })
}
export async function validateDnsConfig() {
return invoke<ValidationOutcome>('validate_dns_config')
}
export async function applyDnsConfig(apply: boolean) {
return invoke<void>('apply_dns_config', { apply })
}
export async function takeDnsOverrideNotice() {
return invoke<boolean>('take_dns_override_notice')
}
@@ -540,3 +556,22 @@ export const probeListener = async (request: ListenerProbe) => {
export const saveProxyPorts = async (settings: ProxyPortSettings) => {
return invoke<SaveProxyPortsOutcome>('save_proxy_ports', { settings })
}
export interface UnlockItem {
name: string
status: string
region?: string | null
check_time?: string | null
}
export async function getUnlockItems() {
return invoke<UnlockItem[]>('get_unlock_items')
}
export async function checkMediaUnlock(onComplete: Channel<UnlockItem>) {
return invoke<UnlockItem[]>('check_media_unlock', { onComplete })
}
export async function checkMediaUnlockItem(name: string) {
return invoke<UnlockItem>('check_media_unlock_item', { name })
}