mcpField
Q
query-layer

query-layer

github.com/epicenterhq开发4.8k64 次安装2 周前更新
代码仓库

关于 query-layer

用于构建 UI 与服务之间响应式查询层的开发指南,基于 TanStack Query。它说明共享查询与变更定义、缓存行为、错误传递及命令式调用方式。

主要功能

  1. 01TanStack Query 适配模式
  2. 02共享查询和变更定义
  3. 03缓存键与失效处理指南
  4. 04响应式与命令式 API
  5. 05传递带标签的错误
  6. 06操作层边界划分

适用场景

  1. 01为 UI 组件定义共享查询
  2. 02通过共享缓存标识执行变更
  3. 03在组件外获取或确保数据
  4. 04有依据地选择缓存更新方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/epicenterhq/epicenter --skill query-layer

安装后在 Claude Code 中输入/query-layer确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-query-patterns.md
error-transformation-patterns.md
runtime-dependency-injection.md

详细介绍

查询层模式

参考仓库

上游依据

当 TanStack Query 的行为、Svelte 适配器类型、缓存失效语义、乐观更新或变更生命周期回调会影响正确性时,请针对 TanStack/query 向 DeepWiki 提出一个范围明确的问题,再以此为依据。用它来梳理方向,然后在修改代码前,通过本地已安装的类型、源码或官方文档核实关键细节。

对于稳定的基础知识,以及下文已记录的仓库本地模式,无需使用 DeepWiki。

查询层是 UI 组件与服务层之间的响应式桥梁。它使用 TanStack Query 和 Wellcrafted 工厂,为服务函数或可观察操作封装缓存、变更生命周期状态、失效处理和直接命令式访问。

相关技能:有关这些查询所使用的服务层,请参阅 services-layer。有关 Svelte 专用的 TanStack Query 模式,请参阅 svelte。有关 Result 到达 UI 边界后的 toast 和报告模式,请参阅 error-handling。

核心架构

┌─────────────┐     ┌─────────────┐     ┌──────────────┐
│     UI      │ --> │    Query    │ --> │   Services   │
│ Components  │     │    Layer    │     │  (UI-free)   │
└─────────────┘     └─────────────┘     └──────────────┘
      ↑                    │
      └────────────────────┘
         Reactive Updates

查询层职责:

  • 使用注入的设置/配置调用服务
  • 保留有类型的服务和操作错误,除非适配器引入了新的本地失败
  • 管理 TanStack Query 缓存以支持乐观更新
  • 提供可用于 hook 的 .options,并在存在时提供明确的命令式 API
  • 通过导出的 *Keys 映射管理共享缓存标识

Wellcrafted 查询 API 形态

范围查询变更
Hook 局部 Result 适配器resultQueryOptions(input)resultMutationOptions(input)
可复用定义defineQuery(input)defineMutation(input)

当返回 Result 的函数需要进入 TanStack 的数据/错误通道,且无需命令式 API 或共享查询标识时,请在单个 hook 调用处使用 resultQueryOptions 和 resultMutationOptions。

适配器会在内部解包(返回 Ok.data,抛出 Err.error)。 传入返回 Result 的函数;不要先调用 unwrap。

在共享的 $lib/queries 模块中使用 defineQuery 和 defineMutation。

查询会公开 .options、.fetch() 和 .ensure()。它们不可调用。

变更会公开 .options,且可调用。它们不会公开 .execute()。

Whispering 查询模块的规范形态

这些示例所源自的 Whispering 查询模块已被移除,因为 Whispering 已成为不带查询层的独立文件夹应用。这种形态仍是包含查询层的应用中 $lib/queries 模块的约定。

对于 Whispering 风格的 $lib/queries 模块,请将事实来源声明放在靠近其描述的工作的位置。工厂会显式接收由会话持有的运行时:

export const audioKeys = defineKeys({
	availability: (id: string, blobId: string, uploadedAt: string | null) =>
		['audio', 'availability', id, blobId, uploadedAt] as const,
});

export function createAudioQueries({ defineQuery }: WhisperingQueryRuntime) {
	return {
		availability: (recording: Accessor<Recording>) =>
			defineQuery({
				queryKey: audioKeys.availability(
					recording().id,
					recording().audioBlobId,
					recording().uploadedAt,
				),
				queryFn: () => getRecordingAudioAvailability(recording()),
			}),
	};
}

规则:

  • 在拥有该工作的适配器或状态模块旁导出 *Keys = defineKeys({ ... })。
  • 静态键无需使用 as const;当键工厂需要保留字面位置时,使用 as const。
  • 将键保留在所属模块中,除非其他层需要相同的回退标识。
  • 将简短且仅使用一次的输入对象内联。仅当输入类型会被复用、需要导出、过大而影响函数可读性,或具有领域含义时,才为其命名。将命名的输入类型放在紧邻其所用适配器命名空间之前的位置。
  • 除非其他模块需要命名该确切联合类型,否则将适配器局部的 defineErrors 命名空间保留在本地。

适配器边界:查询与操作

使用 $lib/queries 作为共享的 TanStack 观察层。它可以封装直接的服务/状态调用,也可以封装 $lib/operations 入口点,前提是 UI 需要共享变更身份:多个使用方、缓存失效、乐观更新、useIsMutating,或为该操作设置命名变更键。

将编排逻辑放在 $lib/operations 中:投递、声音、分析、剪贴板写入和多步骤工作流。可复用的操作会返回其结果;终端交互控制器也可以报告该结果。不要仅仅为了观察本地待处理状态,就把只在单个组件中使用的操作提升到 $lib/queries。组件应在本地 createMutation 与直接 await 之间做出选择,这属于 svelte 技能的职责。

依赖方向

UI -> operations/* -> services/* + state/* + $lib/tauri
UI -> queries/*    -> services/* or operations/*, plus narrow state reads/writes for observed lifecycle

查询模块接收由会话拥有的查询运行时,并导入服务、状态或操作。它们不会仅为了编排工作而导入同级查询模块;跨适配器协调应放在操作中。

错误流转

在 Whispering 中,服务和操作错误已经是带标签的错误。查询适配器会将其原样传递。由 UI/报告边界决定如何呈现这些错误。

Service / Operation       ->  Query Adapter     ->  UI / Report
TaggedError<'Name'>           same error            report.error({ cause: error })

只有当适配器本身发现了无法由下层处理的故障时,才定义查询本地错误,例如在调用操作前未能找到录音。

响应式与命令式用法

查询层适配器同时提供响应式钩子用法和显式命令式用法。

响应式接口:.options

共享查询适配器将 .options 暴露为静态对象。Svelte 钩子会在访问器中读取它:

<script lang="ts">
	import { createQuery, createMutation } from '@tanstack/svelte-query';
	import { getWhisperingQueries } from '$lib/whispering/context';

	const queries = getWhisperingQueries();
	const availability = createQuery(() =>
		queries.audio.availability(() => recording).options,
	);

	const transcribeRecording = createMutation(
		() => queries.transcription.transcribeRecording.options,
	);
</script>

{#if availability.isPending}
	<Spinner />
{:else if availability.error !== null}
	<Error message={availability.error.message} />
{:else}
	<AvailabilityBadge value={availability.data} />
{/if}
命令式接口:查询选择缓存策略,变更可直接调用

在组件上下文之外使用,或在调用方需要直接获取 Result 时使用:

// In an event handler or workflow
async function handleDownload(recording: Recording) {
	const { error } = await queries.download.downloadRecording(recording);
	if (error !== null) {
		report.error({ cause: error });
		return;
	}
	report.success({ title: 'Recording downloaded' });
}

// In a sequential workflow
async function stopAndTranscribe(toastId: string) {
	const { data: url, error: playbackUrlError } =
		await queries.audio.availability(() => recording).fetch();

	if (playbackUrlError !== null) {
		report.error({ cause: playbackUrlError });
		return;
	}

	// Continue with transcription...
}

当你希望 TanStack 按照查询的常规过期策略进行判断时,使用 .fetch():即使无需发起请求,也可能直接返回新鲜的缓存数据。当缓存中存在任何数据都可以接受,且仅在缓存为空时才需要获取数据时,使用 .ensure()。

何时使用各接口
适配器接口模式
共享响应式查询createQuery(() => queries.thing.options)
共享响应式变更createMutation(() => queries.thing.options)
命令式读取查询queries.thing(...).fetch() 或 queries.thing(...).ensure()
命令式变更queries.thing(input)

关于本地组件的操作放置和生命周期决策,请参阅 svelte 技能中的变更指南。

关键规则

  1. 对共享缓存身份使用 defineKeys - 在所属模块旁导出键映射
  2. 使用 .options(不带括号) - 它是静态对象,在 Svelte 中应将其包装在访问器中
  3. 默认不要转换带标签的错误 - 将服务/操作错误原样传递到报告边界
  4. 服务接收显式应用输入 - 由消费端注入设置和设备配置
  5. 将组件生命周期策略保留在 svelte 中 - 此技能负责共享适配器形态和缓存行为
  6. 审慎更新缓存 - 仅当缓存所属方和回滚路径明确时才使用乐观写入;否则应使缓存失效或重新获取

参考资料

根据当前工作内容,按需阅读以下资料:

评论

支持 Markdown