后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载envelop/instrumentation是 Envelop 生态中专门负责“执行链路埋点”的基础设施包它提供chain、composeInstrumentation、getInstrumentationAndPlugin、getInstrumented等工具函数与类型让你可以统一控制 Envelop、Yoga、whatwg-node 及 Hive Gateway 各插件的 instrumentation 执行顺序。读完本文你将理解 instrumentation 与普通 hook 的区别、多个 instrumentation 如何被“套娃”式组合以及如何通过composeInstrumentation在插件数组中显式重排埋点顺序并能在源码层面验证其同步与异步两种执行路径的行为。包定位为什么需要一个单独的 instrumentation 工具包包 README 开宗明义该包“包含一系列工具函数和类型用于简化 instrumentation 在 Envelop、Yoga、whatwg-node 和 Hive Gateway 插件中的使用”。README 中有一条重要提示值得先记住Instrumentation 默认会被自动组合automatically composed。只有当默认顺序不符合你的需求时例如希望 instrumentation 与 hooks 以不同顺序执行才需要显式使用composeInstrumentation。也就是说绝大多数插件开发者只要在自己的 Envelop 插件里挂一个instrumentation属性即可框架会替你按插件定义顺序自动串联。本包提供的 API 主要服务于两类场景框架作者如 Envelop 核心、GraphQL Yoga需要在内部复用同一套组合逻辑高级用户需要在多个插件的 instrumentation 之间手工调整执行顺序。从 package.json 看该包发布为envelop/instrumentation当前版本 1.0.1依赖极轻——仅whatwg-node/promise-helpers和tslib要求 Node 18同时产出 CJS/ESM 双产物dist/cjs与dist/esm因此可以被任何 JS 运行时的 Envelop 应用直接引入。核心概念instrumentation 与普通 hook 的区别先明确“instrumentation”在 Envelop 中的形态。在类型定义 plugin.ts 中InstrumentationTContext是一组可选方法init、parse、validate、context、execute、subscribe等每个方法的签名都形如phase: (payload: { context: TContext }, wrapped: () MaybePromisevoid) MaybePromisevoid它接收两个参数payload当前阶段上下文至少包含context和wrapped包裹“下一阶段执行”的回调。调用wrapped()才会继续执行内部逻辑——这是典型的中间件middleware模型。与onExecute等 hook 相比instrumentation 能包裹“hook 的执行本身”因此在做 tracing、计时、重试时位置更底层。本包在 instrumentation.ts 中定义了一个更通用的结构类型供chain与composeInstrumentation使用export type GenericInstrumentation Record string, (payload: any, wrapped: () MaybePromisevoid) MaybePromisevoid ;它是一个“方法名 → 包装器函数”的映射方法名是开放的execute、operation、request等均可这为跨包复用组合逻辑提供了类型基础。chain两个 instrumentation 的合并规则composeInstrumentation的地基是chain它把两个 instrumentation 合并为一个/** * Composes 2 instrumentations together into one instrumentation. * The first one will be the outer call, the second one the inner call. */ export function chainFirst extends GenericInstrumentation, Next extends GenericInstrumentation( first: First, next: Next, ) { const merged: GenericInstrumentation { ...next, ...first }; for (const key of Object.keys(merged)) { if (key in first key in next) { merged[key] (payload, wrapped) first[key]!(payload, () next[key]!(payload, wrapped)); } } return merged as First Next; }见 instrumentation.ts合并规则可拆成三步浅合并先以...next为底、...first覆盖得到所有可能出现的方法名集合。同名方法串行若first与next都定义了同名方法如都定义了execute则生成一个新的包装器——先执行first的实现而first收到的wrapped回调内部会再执行next的同名实现next最终才接到真正的wrapped。类型收敛返回First Next的交集类型保证 TypeScript 下调用方仍能拿到两侧定义的方法签名。由此得到关键语义第一个参数是最外层调用第二个参数是更内层的调用。payload会沿链条原封不动地传到底测试文件 instrumentation.spec.ts 中 “should pass the payload all the way down the instrument chain” 用例专门验证了这一点。测试用例证实“洋葱模型”与同步/异步一致性test/instrumentation.spec.ts 是理解执行顺序最直接的证据。四个 instrumentation 各定义一个execute包装器在调用wrapped前后各记录一次编号然后reduce(chain)串联执行const createInstrument (name: number): GenericInstrumentation ({ execute: (_, wrapped) { result.push(name); wrapped(); result.push(name); }, }); let [instrument, ...instrumentation] [ createInstrument(1), createInstrument(2), createInstrument(3), createInstrument(4), ]; for (const other of instrumentation) { instrument chain(instrument, other); } instrument[execute]!({}, () {}); expect(result).toEqual([1, 2, 3, 4, 4, 3, 2, 1]);结果[1, 2, 3, 4, 4, 3, 2, 1]清晰地呈现了洋葱模型外层 instrumentation 的“前处理”先依次执行最内层先“后处理”然后逐层回退。该测试还有一个异步版本instrumentation.spec.ts其中wrapped是 10ms 的 Promise断言同样成立——说明chain对同步、异步混合链路都能保持顺序语义。另一个测试instrumentation.spec.ts验证了“只在一侧定义的方法直接透传”instrument1定义了execute与operationinstrument2定义了execute与request合并后operation只走instrument1request只走instrument2execute两侧都调用。这正是{ ...next, ...first }浅合并 同名串行规则的直接体现。composeInstrumentation数组化组合与默认顺序在chain之上composeInstrumentation把任意长度的数组归约为一个/** * Composes a list of instrumentation together into one instrumentation object. * The order of execution will respect the order of the array, * the first one being the outter most call, the last one the inner most call. */ export function composeInstrumentationT extends GenericInstrumentation( instrumentation: T[], ): T | undefined { return instrumentation.length 0 ? instrumentation.reduce(chain) : undefined; }见 instrumentation.ts。两点行为值得注意顺序契约数组第一个元素是最外层outermost最后一个是最内层innermost与 README 中 “called in the same order as they are in the array (from top to bottom)” 的表述一致。空数组返回undefined这使得调用方可以直接把结果挂到插件的instrumentation属性上该属性本身可选无需判空。README 中的标准用法重排插件 instrumentation 顺序README 给出的核心示例即“把各插件自带的 instrumentation 抽出来重新指定顺序后挂回一个合成插件”import { composeInstrumentation } from envelop/instrumentation // Extract instrumentation to compose from their plugins const { instrumentation: instrumentation1, ...plugin1 } usePlugin1() const { instrumentation: instrumentation2, ...plugin2 } usePlugin2() const getEnveloped envelop({ plugins: [ plugin1, plugin2, // Plugin instrumentation and plugin hooks will be executed in a different order { instrumentation: composeInstrumentation([instrumentation1, instrumentation2]) } ] })这个写法的关键在于解构时剥离{ instrumentation, ...plugin }只取出instrumentation属性其余 hook 属性原样保留在plugin1/plugin2中。于是插件的 hooksonExecute等仍按插件数组顺序执行而 instrumentation 的执行顺序由composeInstrumentation([instrumentation1, instrumentation2])独立决定——这正是 README NOTE 所说的 “instrumentation 和 hooks 以不同顺序执行” 的落地方式。官方文档 envelop-plugins.mdx 中也给出了同一技巧的变体composeInstrumentation([instrumentation2, instrumentation1])反向顺序用于把原本靠后的埋点提到最外层。getInstrumentationAndPlugin框架侧的批量剥离工具如果插件数量多到无法手工解构可以用getInstrumentationAndPlugin批量分离export function getInstrumentationAndPluginT, P extends { instrumentation?: T }( plugins: P[], ): { pluginInstrumentation: T[]; plugins: OmitP, instrumentation[] } { const pluginInstrumentation: T[] []; const newPlugins: OmitP, instrumentation[] []; for (const { instrumentation, ...plugin } of plugins) { if (instrumentation) { pluginInstrumentation.push(instrumentation); } newPlugins.push(plugin); } return { pluginInstrumentation, plugins: newPlugins }; }见 instrumentation.ts。它返回两组数据pluginInstrumentation所有非空 instrumentation 的有序数组可直接交给composeInstrumentationplugins剥离了instrumentation属性的插件数组仍可正常传入envelop({ plugins })。其 JSDoc 明确说明用途“如果默认的 instrumentation 组合方式不适合你可以用它轻松定制组合方式”。这是一个纯函数、无副作用方便在任何框架初始化阶段调用。getInstrumented把 instrumentation 落到具体函数上组合好的 instrumentation 只是“配置”真正生效要靠getInstrumented——它把“包装器定义”应用到“被包装函数”上export const getInstrumented TPayload(payload: TPayload) ({ fnTResult, TArgs extends any[]( instrument: ((payload: TPayload, wrapped: () void) void) | undefined, wrapped: (...args: TArgs) TResult, ): (...args: TArgs) TResult { /* 同步包装 */ }, asyncFnTResult, TArgs extends any[]( instrument: | ((payload: TPayload, wrapped: () MaybePromisevoid) MaybePromisevoid) | undefined, wrapped: (...args: TArgs) MaybePromiseTResult, ): (...args: TArgs) MaybePromiseTResult { /* 异步包装 */ }, });见 instrumentation.ts。两个工厂的取舍规则fn同步路径若instrument为undefined则直接返回原函数零开销否则返回新函数调用时先执行instrument(payload, () { result wrapped(...args) })同步拿到结果后返回。要求instrument与其内部wrapped都是同步的。asyncFn异步路径同样支持undefined直通但包装层通过whatwg-node/promise-helpers的handleMaybePromise处理——instrument或内部wrapped任一方可能返回 Promise 时整体返回 Promise且当result是 Promise 时会result.then(() undefined)再向外传递保证链路上所有异步阶段都被正确await。Envelop 核心如何用它每个阶段都过一遍 instrumentation在 create.ts 中可以看到 Envelop 对getInstrumented的实际用法——每次getEnveloped(context)调用都会创建getInstrumented({ context })并把编排器orchestrator暴露的六个阶段函数逐一包上instrumented.fn(instrumentation?.init, orchestrator.init)(context); return { parse: instrumented.fn(instrumentation?.parse, typedOrchestrator.parse(context)), validate: instrumented.fn(instrumentation?.validate, typedOrchestrator.validate(context)), contextFactory: instrumented.fn(instrumentation?.context, typedOrchestrator.contextFactory(context)), execute: instrumented.asyncFn(instrumentation?.execute, typedOrchestrator.execute), subscribe: instrumented.asyncFn(instrumentation?.subscribe, typedOrchestrator.subscribe), schema: typedOrchestrator.getCurrentSchema(), };这里的选择很有讲究init、parse、validate、context四个阶段用同步的fnpayload 是{ context }被包装的钩子执行链本身同步完成而execute、subscribe用asyncFn——因为它们可能返回 Promise 或异步迭代器。这解释了为什么Instrumentation类型中前四者的wrapped是() void后两者是() PromiseOrValuevoid。GraphQL Yoga 侧chain直接驱动插件顺序GraphQL Yoga 服务端在装配插件时直接使用chain增量合并在 server.ts 中if (plugin.instrumentation) { this.instrumentation this.instrumentation ? chain(this.instrumentation, plugin.instrumentation) : plugin.instrumentation; }即按插件注册顺序逐个chain先注册的成为更外层随后在 server.ts 中用getInstrumented({ request })生成instrumented将requestParse、operation、resultProcess等 Yoga 特有的 HTTP 级阶段payload 为{ request }而非{ context }包进请求处理管线。这说明本包的 API 并不绑定 GraphQL 生命周期而是可复用于任何“阶段名 → 包装器”的中间件体系——这也呼应了 README 中提到的 whatwg-node 与 Hive Gateway 场景。Envelop 编排器的默认组合何时你根本不需要这个包理解“何时必须显式使用composeInstrumentation”要先看默认路径。在 orchestrator.ts 中createEnvelopOrchestrator遍历插件数组时if (pluginInstrumentation) { instrumentation instrumentation ? chain(instrumentation, pluginInstrumentation) : pluginInstrumentation; }也就是说所有插件的instrumentation已按插件定义顺序自动chain成一条链合并结果挂在orchestrator.instrumentation上orchestrator.ts 返回再由envelop()取出并在getEnveloped时通过getInstrumented应用到各阶段。因此默认情况下第 1 个插件的 instrumentation 就是最外层依次类推行为与直接composeInstrumentation(plugins.map(p p.instrumentation))等价。只有在“hooks 顺序与 instrumentation 顺序需要解耦”时例如某个 tracing 插件希望自己的埋点包住另一个插件的 hook但插件数组顺序无法调整才需要 README 所示的“剥离 composeInstrumentation重排 挂回合成插件”手法。官方文档 envelop-plugins.mdx 对instrumentation插件属性的说明与 README 结论一致多插件的 instrumentation 默认按定义顺序组合文档还建议当 instrumentation 与 hooks 需要共享数据时使用WeakMap在两者之间传递引用。小结与实践要点围绕 packages/envelop/instrumentation/README.md 的核心内容结合仓库源码可归纳出如下要点API 面chain两两合并第一个为外层、composeInstrumentation数组归约空数组返回undefined、getInstrumentationAndPlugin批量剥离插件的 instrumentation、getInstrumented同步fn/ 异步asyncFn两种包装工厂。顺序语义数组/调用参数从左到右即从外到内outermost → innermost测试断言[1, 2, 3, 4, 4, 3, 2, 1]是该语义的可验证依据。默认行为Envelop 与 Yoga 均已内置chain自动组合常规场景只需在插件上声明instrumentation属性。显式重排通过解构剥离各插件 instrumentation用composeInstrumentation指定任意顺序后挂到{ instrumentation: ... }合成插件上实现 hooks 顺序与埋点顺序解耦。同步/异步边界init、parse、validate、context阶段同步execute、subscribe及 Yoga 的requestParse、operation、resultProcess等阶段允许异步选型fn还是asyncFn须与被包装阶段的 Promise 特性匹配。数据共享instrumentation 与 hook 之间建议用WeakMap传值这一建议出自官方 envelop-plugins.mdx 文档避免污染context。所有结论均可在 src/instrumentation.ts、test/instrumentation.spec.ts、core/src/orchestrator.ts 与 graphql-yoga/src/server.ts 中逐行核对。赞分享后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载相关推荐envelop/core 深度解析GraphQL Yoga 内置 Envelop 核心包的插件机制与编排原理envelop/core 深度解析GraphQL Yoga 内置 Envelop 核心包的插件机制与编排原理 本篇技术指南围绕 monorepo 中的 pa后端API设计GraphQL Yoga 实战在 ESM 项目中以原生 ESM 方式消费 Envelopenvelop/coreGraphQL Yoga 实战在 ESM 项目中以原生 ESM 方式消费 Envelopenvelop/core 本文基于 GraphQL Yoga 仓后端API设计Envelop 插件机制实战GraphQL 执行层包装库的原理与用法详解基于 graphql-yoga 仓库源码Envelop 插件机制实战GraphQL 执行层包装库的原理与用法详解基于 graphql yoga 仓库源码 本文以 Envelop 官方 READM后端API设计上一篇Azure Data Studio 终极多开指南同时管理多个数据库项目的10个技巧下一篇使用 Litestar 组装完整 TODO 应用路由处理器、参数注入与 ASGI 启动实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
m3u8 视频怎么下载?MediaGo 内置浏览器嗅探与 HLS 分片下载完整教程 音视频桌面应用后端 【免费下载链接】mediago 跨平台视频提取工具:支持流媒体下载、视频下载、m3u8 下载及 B站视频下载,提供 Windows 和 Mac 桌面客户端。Cross-platform video extraction tool: Supports streaming download, video download, m3u8 do… · 2026/9/25 3:36:46
Tomcat线程模型与OOM问题深度解析 1. 问题背景与现象分析最近在排查一个线上服务异常时,遇到了一个典型的OOM(OutOfMemoryError)问题。这个案例非常有意思,因为它不仅涉及到内存溢出本身,还引发了Tomcat线程模型的异常表现,最终导致服务不可… · 2026/9/25 6:22:58
基于LLM与Django的智能旅游路线推荐系统设计与实现 1. 项目概述:当旅游规划遇上AI大模型去年帮朋友公司做旅游路线推荐系统时,我深刻体会到传统推荐算法的局限性。用户抱怨"推荐的路线都差不多""根本不考虑我的体力状况",这促使我开始尝试将LLM大模型与路线规划结合。这个… · 2026/9/25 6:22:58
Android Studio Chipmunk Canary 2 实战与避坑指南 简介:Android Studio Chipmunk Canary 2(android-studio-2021.2.1.2)是2021年10月发布的Windows版预览IDE压缩包,适合Android开发者尝鲜新版、验证项目兼容性或学习构建工具链变化。包体总计2000个文件,主要包括637个j… · 2026/9/25 6:22:58
Java学生火车票订票系统:JSP+Servlet+JDBC实现与并发控制 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:22:58
Python字典底层原理:哈希表如何实现O(1)性能 1. 为什么说“Python之哈希表”不是讲数据结构,而是讲你每天都在用的底层引擎“Python之哈希表”这个标题乍看像是一堂枯燥的数据结构课,但如果你真这么理解,就错过了它最硬核的价值——它根本不是在教你怎么手写一个散列表,而是在… · 2026/9/25 6:22:52
ESP32-S3串口避坑指南:UART0陷阱与UART1/UART2实战配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:22:52
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37