Insomnia Service 层 API 命名与签名规范:insomnia-data 数据访问层设计约定详解

发布时间:2026/9/6 22:32:12
Insomnia Service 层 API 命名与签名规范:insomnia-data 数据访问层设计约定详解 Insomnia Service 层 API 命名与签名规范insomnia-data 数据访问层设计约定详解【免费下载链接】insomniaThe open-source, cross-platform API client for GraphQL, REST, WebSockets, SSE and gRPC. With Cloud, Local and Git storage.项目地址: https://gitcode.com/GitHub_Trending/in/insomnia本篇指南围绕 Insomnia 仓库中 Service API 命名约定文档 展开讲解insomnia-data包 Node 端服务层node-src/services/的三组核心 API 规范数据库形 API 的命名映射、变更操作 API 的签名形态以及命名查询助手的取舍原则。读完本文你在为本仓库新增或重构任意模型服务Request、Workspace、MockServer 等时都能做到命名、签名与底层 NeDB 数据库接口一一对应并理解这些约定如何通过 IPC 契约约束渲染进程端的调用方式。一、约定作用域服务层在 Insomnia 架构中的位置这些约定只约束 node-src/services/ 目录下的模型服务模块每个模型一个.ts文件如request.ts、workspace.ts、project.ts。该目录下约 40 个服务模块覆盖了从请求、环境、工作区到 Git 仓库、MCP/WebSocket/gRPC 请求等全部数据模型。从 services/index.ts 的聚合导出可以看到两条重要架构事实每个服务模块都以命名空间形式挂载到servicesNodeImpl上例如request: requestService、workspace: workspaceService文件内注释明确说明服务经由 preload 通过 IPCipcRenderer.invoke供渲染进程消费因此契约必须保持 async——即使某个主进程实现理论上可以同步返回。satisfies Recordstring, Recordstring, (...args: never[]) Promiseunknown这一类型断言的作用正是在编译期拦截任何同步非Promise返回的 action。所以服务层约定不仅是好看它同时是跨进程通信契约的一部分所有方法的参数、返回值都需满足可序列化且异步的 IPC 边界。二、数据库形 API与 database 操作的命名映射约定文档给出的核心规则是当一个服务有意暴露与其模型类型对应的数据库操作时使用以下名称服务层 API映射的 database 调用用途list(query?, sort?, limit?)database.find(modelType, query, sort, limit)返回匹配文档列表get(query?, sort?)database.findOne(modelType, query, sort)返回单个匹配文档count(query?)database.count(modelType, query)返回匹配文档数量这三组签名不是凭空定义的而是直接镜像底层 NeDB 数据库接口的形参。在 database/types.ts 中可以确认对应的方法声明countT(type, query?)、findT(type, query?, sort?, limit?)、findOneT(type, query?, sort?)等服务层只是去掉了modelType参数由模块内const { type } models.xxx固化并加上了模型泛型。project.ts 是这一规范的标准实现样板const { type } models.project; export function create(patch: PartialProject {}) { return db.docCreateProject(type, patch); } export function list(query?: QueryProject, sort?: Recordstring, any, limit?: number) { return db.findProject(type, query, sort, limit); } export function count(query?: QueryProject) { return db.countProject(type, query); } export function get(query?: QueryProject, sort?: Recordstring, any) { return db.findOneProject(type, query, sort); }注意list/get/count的 query 参数类型是QueryProject——这是 NeDB 风格的查询对象支持$in、$nin、$ne、$gt等操作符。project.ts中的syncTeamProjects就大量使用了这类操作符如remoteId: { $nin: [...], $ne: null }用于远端已不存在的项目回落为本地项目。getById_id查找的专用入口约定还强调了一条易错点用getById(id)表示常见的_id查找当get是泛化的findOneAPI 时不要用get(id)做 id 查找。原因是语义冲突——get({ _id: id })与get(someIdString)两个调用形态无法在类型层面区分。project.ts 的写法值得对照export function getById(id: string) { return get({ _id: id }); } export function getByRemoteId(remoteId: string) { return get({ remoteId }) as PromiseRemoteProject | null; }getById内部复用泛化getgetByRemoteId同样复用get来表达不同字段的查找。request.ts 则因该服务未定义泛化get直接内联实现db.findOneRequest(type, { _id: id })同样遵守id 查找走getById的原则。三、变更操作 APIcreate / update / remove约定规定除非方法代表更宽泛的工作流变更操作统一沿用模型层的既有命名create(patch?)update(docOrId, patch)remove(docOrId)docOrId这一签名形态接受文档对象或 id 字符串在 project.ts 中有完整的参考实现const _getProjectByIdOrProject async (idOrProject: string | Project) { const project typeof idOrProject string ? await getById(idOrProject) : idOrProject; if (!project) { throw new Error(Project not found: ...); } return project; }; export async function update(idOrProject: string | Project, patch: PartialProject) { const project await _getProjectByIdOrProject(idOrProject); return db.docUpdate(project, patch); } export async function remove(idOrProject: string | Project) { const project await _getProjectByIdOrProject(idOrProject); return db.remove(project); }其设计意图可以推断为调用方如果手头已经有完整文档就直接传入避免一次多余的 id 查询只有字符串 id 时才走getById补全并对找不到给出带上下文的错误信息。并非所有服务都必须支持docOrIdrequest.ts 的update(request, patch)只接受文档对象mock-server.ts 同样如此——约定描述的是命名与职责形态具体宽窄由各模型的实际调用方决定。此外create的约定形态是create(patch?)但实现中常加入业务前置校验。例如 request.ts 与 mock-server.ts 的create都会在缺少parentId时直接抛错把父级必填这一数据完整性约束前置到服务入口。而mock-server.ts中还有一个典型的非标准入口getOrCreateForParentId——按约定这类方法代表更宽泛的工作流所以允许突破create/list/get的三件套命名见第五节。四、命名查询助手按需添加按过滤器命名约定文档对命名查询助手给出三条规则只有存在真实调用方或重复业务需求时才添加命名助手不要预先创建countBy...这类平行的助手家族除非它们真的被使用按助手所表达的过滤器命名文档列举的命名样例为listByRemoteId(remoteId)listByGitRepositoryIds(gitRepositoryIds)listByOrganizationIds(organizationIds)当一个助手需同时支持单个 id 与多个 id 时优先提供单一的复数 API参数类型string | string[]而不是拆成单数/复数两个方法。这三条规则在 project.ts 中都有精确的落地证据export function listByOrganizationIds(organizationIds: string | string[]) { const ids Array.isArray(organizationIds) ? organizationIds : [organizationIds]; return list({ parentId: ids.length 1 ? ids[0] : { $in: ids }, }); } export function listByGitRepositoryIds(gitRepositoryIds: string | string[]) { const ids Array.isArray(gitRepositoryIds) ? gitRepositoryIds : [gitRepositoryIds]; const queryIds ids.flatMap(id models.project.getQueryableGitRepositoryIds(id)); return list({ gitRepositoryId: { $in: queryIds } }); }两个助手都符合单 API string | string[]的取舍内部先归一化为数组单 id 时listByOrganizationIds直接用字段值查询、多 id 时切换为{ $in: ids }省一次无谓的 IN 查询。listByGitRepositoryIds还体现了助手价值的另一面——它封装的不只是过滤器还有业务变换getQueryableGitRepositoryIds把一个 Git 仓库 id 展开为多个可查询值再统一$in这种变换细节对调用方完全透明。按需添加这条规则在仓库中也能看到反面教材的反证全库范围内listByRemoteId并未被预先铺到每个模型服务上project.ts中提供的是getByRemoteId单文档查找助手只出现在有真实调用方的位置。而listByOrganizationIds确实有跨服务的实际调用者environment.ts 用它取组织下所有项目进而收集环境app-data/organization-data.ts 用它取组织数据。跨服务复用正是重复业务需求的量化体现。五、工作流方法按业务意图命名而非数据库机制约定的最后一条工作流方法应按业务意图命名而不是按数据库机制命名。对照仓库源码数据库机制命名指find/update/count这类描述怎么操作存储的词业务意图命名则是描述要达成什么业务结果。project.ts 给出了两个好例子syncProjects(organizationId)/syncTeamProjects({ organizationId, teamProjects })内部组合了多次list按remoteId$in/$nin过滤、create、update实现以云端 API 为事实来源的增量同步命名直接表达同步这一业务意图getAllTeamProjects(organizationId)拉取远端数据而非查本地库用获取全部团队项目命名避免了listFromApi这类机制化命名。mock-server.ts 的getOrCreateForParentId是另一典型先findOne后条件docCreate命名按父 id 获取或创建恰好概括了这段 read-then-write 业务逻辑。而 request.ts 的duplicate(request, patch)更进一步——它调用数据库层的db.duplicate但服务层实现了额外的业务语义自动补(Copy)后缀、按metaSortKey间隔计算插入排序键复制请求这一业务意图比克隆文档更贴合用户心智。需要说明的一个边界organization.ts 这类服务同样遵守list()/get(id)的命名规范但其数据来源是云端 APIgetSpaces({ sessionId })而非本地 NeDB。这说明本约定描述的是服务层对外契约的命名形态对底层是本地数据库还是远端 API保持中立文档标题中database-shaped的限定词正是为了表达这一中立性。六、落地检查清单在为本仓库新增一个模型服务时可以按以下清单自查依据即 CONVENTIONS.md 全文暴露标准数据库操作时签名严格为list(query?, sort?, limit?)、get(query?, sort?)、count(query?)并让modelType由模块内的const { type } models.xxx固化_id查找一律getById(id)其他单值字段查找可加getByField并复用get变更操作命名create(patch?)/update(docOrId, patch)/remove(docOrId)docOrId形态需先归一化文档并显式抛出未找到错误新增listBy*助手前先确认存在真实调用方需要兼容单/多 id 时用string | string[]的单一复数 API组合多次读写实现的业务逻辑按业务意图命名如sync...、getOrCreate...不要暴露findThenCreate这类数据库机制名所有方法必须异步返回Promise——servicesNodeImpl的satisfies ... Promiseunknown类型约束见 index.ts会在编译期拒绝同步实现底层 NeDB 能力find/findOne/count/docCreate/docUpdate/duplicate/removeWhere等见 database-nedb.ts 与 types.ts应被视为服务的受控依赖服务层负责把type、查询归一化、业务校验封装起来调用方不直接触达database层。这套约定本质上是把命名即文档落到了数据访问层读到一个listByOrganizationIds(string | string[])即可推断出它对应parentId过滤、复用list实现、单/多 id 通用而不必打开实现。对维护者而言这是低成本的一致性收益对 Agent 或 LLM 检索者而言这是可直接作为代码生成与评审规则引用的显式契约。【免费下载链接】insomniaThe open-source, cross-platform API client for GraphQL, REST, WebSockets, SSE and gRPC. With Cloud, Local and Git storage.项目地址: https://gitcode.com/GitHub_Trending/in/insomnia创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考