mcpField
S
services-layer

services-layer

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

关于 services-layer

services-layer 是指导 Whispering 服务层改动的项目专用技能。它规定无 UI 服务的边界、错误处理、平台选择,以及服务对象与工厂的使用约定。

主要功能

  1. 01界定无 UI 的服务边界
  2. 02指导基于 Result 的错误处理
  3. 03说明构建时的平台映射
  4. 04区分直接对象与工厂
  5. 05说明服务汇总模块的约定

适用场景

  1. 01设计新的 Whispering 服务
  2. 02检查服务依赖与边界
  3. 03选择特定平台的实现
  4. 04决定使用对象还是工厂

安装

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

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

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

附带文件references/service-implementation-pattern.mdreferences/service-organization-platforms.md

详细介绍

服务层

服务指南应以 apps/whispering/src/lib/services、它的使用方,以及 apps/whispering/package.json 中的 #platform/* 映射为依据。历史 示例和相邻技能只能作为线索,不能代表当前架构。

产品定位

服务层负责与 UI 无关的能力;调用方通过显式输入注入应用策略,可失败的操作返回 Result,而平台选择由一个构建时所有者负责。

边界

服务可以执行 IO,也可以拥有服务本地的运行时状态。它们不必是纯函数,但必须与 UI 和应用自有策略保持隔离:

  • 不得在运行时读取 Svelte stores、settings、deviceConfig、toast 或 report;
  • 将凭据、模型名称、端点、路径和用户选择作为输入;
  • 返回领域数据和错误,而非展示文案或 UI 状态;
  • 在 #platform/* 接缝的两侧暴露相同的契约。

$lib/operations 通常负责读取应用设置、选择提供方并组合服务。仅当 UI 需要时,$lib/queries 才会添加共享查询标识和可观察的生命周期。

转录目录还包含提供方注册表数据和面向 UI 的 provider-ui.ts 联接模块。这些放在同一目录中的元数据模块并非服务实现;不要以此削弱服务边界。

如实建模可失败性

对于在常规运行时使用中可能失败的公共操作,使用 Result<T, E>。 使用 error-handling 技能适配会抛出异常的平台或库调用。

不要为不会失败的内存操作或清理操作伪造 Result。当前示例包括返回 void 的本地快捷键 register / unregister,以及返回清理函数的监听器设置操作。

export type DownloadService = {
	downloadBlob(args: {
		name: string;
		blob: Blob;
	}): Promise<Result<void, DownloadError>>;
};

错误应由理解该失败的层负责。组合服务时保留底层的带标签错误。仅当服务本身拥有某种失败时,才定义服务本地的变体。使用 define-errors 确定变体形状和消息规则;使用 error-handling 进行适配和传播。

直接对象还是工厂

当构造不需要输入或生命周期时,默认使用直接的 *ServiceLive 对象。当前的下载、分析、文本和转录提供方实现都采用这种形式。下载、分析和文本服务会使用 satisfies <Service> 检查其共享平台契约。

当工厂负责真实的构造输入、可替换依赖或有状态生命周期时,才使用工厂。浏览器和 CPAL 录音器工厂之所以适合这种边界,是因为它们各自会创建具有停止、取消、订阅和拆除行为的录音会话。

不要为了一个无状态对象而机械地同时添加 create* 和 *Live。

阅读服务实现模式,了解当前的直接对象示例和工厂选择依据。

平台和运行时选择

对于同时具有浏览器和 Tauri 实现的能力,使用 #platform/* 导入。package.json#imports 在构建时选择实现;共享调用方导入一个稳定名称,不检查 window.__TAURI_INTERNALS__。

对于仅限 Tauri 的能力,使用可空的 #platform/tauri 命名空间。

用户选择的提供方属于运行时策略,而非平台接缝。将分派逻辑保留在调用该服务的操作中。Whispering 转录会在 $lib/operations/transcribe.ts 中读取选定的提供方;查询层只观察该操作。

添加或迁移平台服务时,请阅读服务组织与平台变体。

服务汇总模块

$lib/services/index.ts 汇集稳定的跨平台能力:

export const services = {
	analytics: AnalyticsServiceLive,
	text: TextServiceLive,
	blobs: BlobsLive,
	blobSources: BlobSourcesLive,
	download: DownloadServiceLive,
	localShortcutManager: LocalShortcutManagerLive,
	sound: PlaySoundServiceLive,
} as const;

不要强行将每个提供方实现都放入此汇总模块。当运行时操作拥有分派表时,可以直接导入提供方专属服务。

最终检查

  • 该服务不导入任何 UI 或应用自有设置。
  • 每项应用选择都作为显式输入传入,或由调用方选定。
  • 可能失败的公共操作返回 Result;不会失败的操作保持为普通形式。
  • 工厂负责构造或生命周期管理,而非仅依赖约定。
  • 平台选择通过一个 #platform/* 映射完成。
  • 运行时提供方在使用该操作时只选择一次。
  • 除非此服务负责引入新故障,否则低层错误应直接传递。

评论

支持 Markdown