Graphile Crystal Monorepo 全景指南:从 Gra*fast* 规划执行引擎到 PostGraphile 自动 GraphQL API
2026/9/23 7:28:31 网站建设 项目流程

Graphile Crystal Monorepo 全景指南:从 Grafast规划执行引擎到 PostGraphile 自动 GraphQL API

【免费下载链接】crystal🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址: https://gitcode.com/gh_mirrors/cry/crystal

Graphile Crystal 是 Graphile 团队围绕 GraphQL 技术栈打造的 monorepo 仓库,核心承载两大明星项目:下一代 GraphQL 规划与执行引擎Gra*fast*,以及基于 PostgreSQL 自动生成高性能 GraphQL API 的PostGraphile,同时还包含pg-sql2pg-introspectiongraphile-configgraphile-export等可独立使用的基础库。本文以仓库根目录 README.md 为骨架,结合各子包源码与文档,系统梳理整个技术栈的定位、用法与底层实现,帮助读者快速判断哪些包适合引入自己的项目,以及如何将它们组合成一条高效的 GraphQL 后端链路。

仓库定位:Graphile 的 "Crystal" 全家桶

据根目录 README.md 介绍,这个仓库收纳了 Graphile 旗下几乎所有与 GraphQL 相关的包——包括与 GraphQL 相关的包、以及与"与 GraphQL 相关的包"相关的包。两个主角分别是:

  • Gra*fast*:面向 GraphQL.js 的尖端规划与执行引擎,可作为 GraphQL.js 官方execute方法的即插即用替代品。通过把传统 resolver 迁移到 Gra*fast* 的 "plan resolver"(计划解析器),可以借助 GraphQL 请求的声明式特性,以最高效的方式执行业务逻辑,从而降低服务器负载。
  • PostGraphile:以 PostgreSQL 数据库为核心数据源,自动生成结构良好、高性能的 GraphQL API,主打低投入、高性能、自动最佳实践与可扩展性。

仓库的工程组织方式是 Yarn Workspaces 多包仓库(见 package.json 中workspaces字段),工作区覆盖grafast/*graphile-build/*postgraphile/*utils/*四组包。环境要求为node >= 22yarn >= 1.3.2,且通过packageManager声明使用yarn@4.12.0

提示:需要 PostGraphile V4(旧版)的用户,README 指明请前往legacy分支查看,本仓库主线对应的是 PostGraphile V5 及配套的新一代技术栈。

Gra*fast*:面向 GraphQL 的规划与执行引擎

核心理念:从 resolver 到 plan resolver

grafast/grafast/README.md 详细描述了 Gra*fast* 的工作方式:它理解 GraphQL,并在"你的帮助下"理解业务逻辑,从而以极高效率编排一次 GraphQL 请求的数据需求。

传统 GraphQL.js 的执行方式是为每个字段直接调用 resolver 函数;而 Gra*fast* 引入了plan resolver:每个字段不再直接返回数据,而是描述"要执行该字段所需的抽象步骤(step)"。一次请求中所有字段的 step 会被组合成一个operation plan(操作计划),该计划在执行前可以被整体重写与优化,并且常常能被未来相似的查询直接复用——这是 Gra*fast* 效率优势的核心来源。

关键的一点是,Gra*fast*向后兼容传统 resolver:绝大多数现有 GraphQL.js schema 都能直接交给 Gra*fast* 执行,且通常能获得小幅提速。只有把 resolver 替换为 plan resolver,才能看到真正的效率跃升。此外,Gra*fast* schema 本身就是 GraphQL.js schema,因此 schema-first、code-first 或自动生成等构建方式全都适用。

执行流程

当 Gra*fast* 第一次见到某个 GraphQL 请求时,会经历如下阶段(见 grafast/grafast/README.md):

  1. 规划(plan):分析请求的数据需求、需要执行的步骤、如何把结果写入响应;
  2. 优化(optimize):对"初稿计划"进行重写优化,例如删除冗余或重复的处理步骤、重写并合并处理步骤,以获得最佳性能;
  3. 执行(execute):执行计划并把响应返回给客户端;
  4. 复用:后续与该计划兼容的请求可以直接执行,无需重新规划。

从源码结构看,规划与执行逻辑分布在 grafast/grafast/src/engine/ 下(如executeBucket.tsexecuteOutputPlan.ts等),而对外导出的execute函数定义在 grafast/grafast/src/execute.ts,印证了 README 所述"即插即用替代execute"的接口设计。

使用方式:两行代码迁移

README 给出了两种迁移方式,把从graphql模块导入换成从grafast导入即可:

-import { graphql } from "graphql"; +import { grafast as graphql } from "grafast";
-import { execute } from "graphql"; +import { execute } from "grafast";

任何允许替换execute方法的 GraphQL 服务器(包括所有完整支持 Envelop 的服务器)都可以接入 Gra*fast*。

兼容性要求与建议

要在 Gra*fast* 上正常运行,schema 需要满足以下条件(摘自 grafast/grafast/README.md):

  • 使用GraphQL.js v16+
  • 不得覆盖 GraphQL 默认字段 resolver(该能力暂未支持);
  • 每次请求的context必须是一个对象(需要能作为WeakMap的键使用;如果不需要 context,传{}即可);
  • 仅支持 schema 中显式定义的字段 resolver,通过rootValue传入的 resolver 暂不支持;
  • 对传统 resolver 第四参数resolveInfo的支持未完全对齐,尤其是resolveInfo.path属性目前不支持。

为了最大化收益,README 还给出三条关键实践建议(见 grafast/grafast/README.md):

  • 关键:用 LRU 缓存缓存解析后的 GraphQL document(AST),使同一文档反复复用同一 AST——grafserv会帮你处理,Envelop 用户可用@envelop/parser-cache
  • 不要使用rootValue(改用context);
  • 尽量对 variables 对象做 memoize(例如基于canonicalJSONStringify(variables)做缓存),使相同 variables 命中同一内存对象;
  • 用 LRU 缓存 GraphQLcontext对象,让同一用户复用同一 context(这条相对次要,迁移期仍需使用 DataLoader 时可以放宽)。

这些建议背后的逻辑是:Gra*fast* 的规划结果复用程度越高,性能收益越大。

配套计划类库

Gra*fast* 生态提供了两组针对具体数据源的 plan 类:

  • @dataplan/pg:用于与 PostgreSQL 交互的高度优化 Gra*fast* step 类集合。README 将其定位为 "A collection of extremely highly optimized Gra*fast* step classes for interacting with PostgreSQL",PostGraphile 的 SQL 数据访问正是建立在它之上。
  • @dataplan/json:用于 JSON 编解码的 plan 类。

两者都以独立 npm 包的形式位于grafast/工作区下,可脱离 PostGraphile 单独用于自建 Gra*fast* schema。

PostGraphile:把 PostgreSQL 变成 GraphQL 事实源

工作方式

PostGraphile 的定位是"只写真正带来价值的代码"。开箱即用地分析 PostgreSQL 数据库(表、关系、函数、索引、权限以及你的配置),生成一个完整、一致的 GraphQL schema,并且这个"活的基础"会随数据库演进。在此基础上可以无缝叠加定制与扩展:

  • 用自定义类型和字段扩展 schema(字段可以执行 SQL 或 Node.js 代码);
  • 用数据库权限决定暴露哪些部分,快速、符合人体工学、粒度精细,还能提升安全态势;
  • 用强大的插件与 preset 系统对生成的 schema 施加通用偏好;
  • 用简单的 "tags"(智能注释)微调单个数据库实体:重命名、决定暴露方式、改变类型/呈现/nullability、标注抽象类型(interface/union)、引入额外关系等;
  • 用 "inflection" 系统全局重构生成的命名;
  • 使用第三方插件扩展能力。

PostGraphile 几乎所有功能——从 introspection 到类型生成再到分页参数——都是通过插件实现的,插件 API 面向使用者设计,并提供帮助工厂(helper factories)让常见需求更易用。

在效率方面,PostGraphile 由 Gra*fast* 引擎驱动,README 指出其性能通常优于使用传统 GraphQL.js resolver 的手写 schema。同时它"无锁定":必要时可以通过 graphile-export 把 schema 导出为可执行代码,脱离 PostGraphile 自行维护,仍保留完整规划的执⾏性能优势。

实战示例一:用 SQL 函数与智能注释定制字段

PostGraphile README 给出了一个电商结账场景:数据库只有productspricescart_items等底层表,而前端需要subtotaltax等汇总字段。第一种做法是直接在数据库里处理(见 postgraphile/postgraphile/README.md):

-- 从 GraphQL schema 中隐藏 "prices" 表 comment on table prices is '@behavior -*'; -- 创建 `Product.unitPrice` 字段,获取商品当前价格 create function products_unit_price(p products) returns money as $$ select unit_price from prices where product_id = p.product_id and now() >= valid_from and now() < valid_until; $$ language sql stable; -- 为该字段添加文档 comment on function products_unit_price is 'The unit price at the current time, reflecting promotional discounts.';

这段 SQL 会自动生成Product.unitPrice字段,按时间有效性取当前单价(假设各时间段不重叠;若可能重叠,可在查询中追加order by unit_price asc limit 1)。这正是"用智能注释驱动 schema 生成"的代表性用法。

实战示例二:用 extendSchema 接入 Node.js 业务逻辑

如果业务逻辑更复杂(例如需要查询外部服务计算运费),可以在 TypeScript 侧用extendSchema扩展 schema(见 postgraphile/postgraphile/README.md):

import { extendSchema } from "postgraphile/utils"; import { constant, context, get } from "postgraphile/grafast"; import { batchSummarizeCart } from "./businessLogic/cart"; export default extendSchema((build) => { const { pgExecutor, pgResources: { cartItems, products, prices }, } = build; return { typeDefs: /* GraphQL */ ` extend type Product { "The unit price at the current time, reflecting promotional discounts." unitPrice: Money! } extend type Cart { summary: CartSummary } type CartSummary { subtotal: Money! shipping: Money! tax: Money! total: Money! } `, plans: { Product: { unitPrice($item) { // 找到对应的价格记录 const productId = $item.get("product_id"); const $prices = prices.find({ productId: $productId }); $prices.where(sql`now() >= valid_from and now() < valid_until`); // 恰好一行,取回并返回单价 return $prices.single().get("unit_price"); }, }, Cart: { summary($cart) { const $cartId = $cart.get("id"); return loadOne($cartId, batchSummarizeCart); }, }, // CartSummary 无需 plan resolver,使用默认即可。 }, }; });

配套的批量加载业务逻辑(来自 postgraphile/postgraphile/README.md)展示了如何把 DataLoader 风格的回调接到loadOne/loadMany上,并通过shared: () => context()让 loader 访问运行时 GraphQL context:

// businessLogic/cart.ts import { context } from "postgraphile/grafast"; export const batchSummarizeCart = { // 计划步骤:在 loader 中获取 GraphQL context shared: () => context(), // `cartIds` 是 Cart 标识符的批,`shared` 是运行时 GraphQL context(整批共享) async load(cartIds, { shared }) { const carts = await batchGetCartInfo(shared, cartIds); const cartsWithShipping = await batchCalculateShippingCosts(carts); const cartsWithTax = await batchCalculateTax(cartsWithShipping); return cartIds.map((cartId) => { const cartInfo = cartsWithTax.find((c) => c.cart_id === cartId); const { subtotal, shipping, tax } = cartInfo; const total = subtotal + shipping + tax; return { subtotal, shipping, tax, total }; }); }, };

其中的batchGetCartInfo对整批 cart 只发起一次数据库查询(where carts.id = any($1::int[])),这正是 Gra*fast* 批量执行消除 N+1 问题的体现。

通用工具与基础设施包

除了两大主角,README 还罗列了一批可独立使用或支撑上层的基础包:

graphile-export 与 eslint-plugin-graphile-export

graphile-export 可以在合适的条件下,把内存中动态构建的 GraphQL schema 导出为可直接导入执行的原始 JavaScript 源码——这是 PostGraphile "无锁定"能力的技术基础。eslint-plugin-graphile-export 则是配套 ESLint 插件,帮助开发者编写与 graphile-export 兼容的代码(如ExhaustiveDeps.tsExportInstances.tsExportMethods.tsExportPlans.ts等规则,见其src/目录)。

graphile-config:统一配置层

graphile-config 处理 Graphile 全家桶的插件、preset 与配置文件,是一个通用配置层,提供PluginPreset(别名Config)两个接口。Plugin 对象包含:

  • namestring):插件名,必须唯一,用于skipPlugins等能力;
  • versionstring):semver 兼容版本,通常与package.json一致(也可不同,例如一个模块包含多个插件);
  • description(可选string):CommonMark(Markdown)格式的人类可读描述;
  • provides(可选string[]):该插件提供的 "feature labels",主要用于决定插件(及其 hooks/events)的执行顺序,在已加载插件中必须唯一,缺省时取插件名;
  • after(可选string[]):声明应在指定 feature(若存在)之后加载;
  • before(可选string[]):声明应在指定 feature(若存在)之前加载。

Preset 则把一组插件与各 scope 的选项打包,可同时使用多个 preset,preset 之间也可以互相组合(extends)。解析时按ResolvePresets算法合并:依序解析所有extends,插件按集合合并(每个插件只出现一次)。

graphile-build 与 graphile-build-pg

graphile-build 是一个从 "plugins" 构建 GraphQL.js schema 的系统,特别适合自动生成的 GraphQL API(PostGraphile 即使用它),也适合手写 schema 中连接(connections)、命名等模块化且广泛使用的关注点。graphile-build-pg 则提供理解@dataplan/pg(即 PostgreSQL)服务的插件,可为数据库资源生成类型、关系、变更(mutations)等。

pg-sql2 与 pg-introspection

pg-sql2 是一个使用 tagged template literals 构建高度动态、防 SQL 注入的 PostgreSQL 查询的库,其核心卖点是"强大灵活地构建动态 SQL 而不向注入攻击敞开大门",且把性能作为明确的设计目标。典型用法(见 utils/pg-sql2/README.md):

const { default: sql } = require("pg-sql2"); // 或 import sql from 'pg-sql2'; const tableName = "user"; // ... 之后通过 sql`...` 模板拼接查询

pg-introspection 是 PostgreSQL 的强类型 introspection 库,依据 PostgreSQL 官方文档生成,为每个 introspection 字段提供最新细节。PostGraphile 的数据库分析正是建立在它之上。

jest-serializer-graphql-schema 与 @graphile/lru

jest-serializer-graphql-schema 是一个理解 GraphQL schema 的 Jest 序列化器,避免快照被"""等冗长描述填满。@graphile/lru 是一个"近乎偏执地追求性能"的 LRU 缓存(README 称其可能是 Node.js 中最快的通用 LRU 缓存之一,但功能集极小),并坦率地建议:绝大多数情况下你可能更想要 @isaacs 的lru-cache而非它。

"Crystal" 之名的由来

为什么这个 monorepo 叫 "crystal"?README.md 给出了有趣的答案:Gra*fast*(前身 DataPlanner)最早的项目代号就是 "Graphile Crystal"。团队在正式公开前用 🔮 水晶球 emoji 作为圈内暗号指代该项目。如今 Gra*fast* 已成为规划执行引擎的正式名称,而仓库里又包含不少与 GraphQL 并非严格相关的东西,需要一个不那么 GraphQL 化的名字,于是沿用最初的外号把 monorepo 命名为 Crystal。README 还幽默地澄清:与维护者"水晶婚纪念日"有关的传闻纯属夸大其词。

在仓库中进一步探索

如果你希望深入了解上述技术栈,可以从以下路径继续:

  • Gra*fast* 源码与引擎实现:grafast/grafast/src/、grafast/grafast/src/execute.ts、grafast/grafast/src/engine/;
  • Gra*fast* 官方文档站点源码:grafast/website/;
  • PostGraphile 插件与 preset 实现:postgraphile/postgraphile/src/plugins/、postgraphile/postgraphile/src/presets/;
  • PostGraphile 测试与 schema 导出脚本:postgraphile/postgraphile/tests/、postgraphile/postgraphile/scripts/test-schema-exports.mjs;
  • 配置层与基础库:utils/graphile-config/、utils/pg-sql2/、utils/pg-introspection/、utils/graphile-export/;
  • 仓库构建与测试命令:package.json(build-initbuildtestpostgraphile等脚本)。

小结

Graphile Crystal monorepo 提供了一条从数据库到 GraphQL API 的完整技术链路:pg-introspection负责读懂 PostgreSQL,pg-sql2负责安全地构造 SQL,@dataplan/pg提供数据访问的 plan 步骤,graphile-build/graphile-build-pg负责 schema 生成,graphile-config统一插件与配置,而 Gra*fast* 与 PostGraphile 分别面向"自建高效 GraphQL 引擎"与"PostgreSQL 驱动的高性能 API"两种核心诉求;graphile-export则确保这一切不会把你锁死在框架里。无论你是想直接基于 PostgreSQL 快速上线一个 GraphQL API,还是想为自己的 GraphQL.js schema 引入规划式执行以换取更高效率,都可以在本仓库中找到对应的组件,并按需独立取用。

【免费下载链接】crystal🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址: https://gitcode.com/gh_mirrors/cry/crystal

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询