@apollo/core-schema

中文文档
0.3.0发布于 2022/04/08MITTypeScriptCommonJS近 30 天下载 4.3万

阿波罗核心Schema处理库

中文使用文档
文档目录15 节
  1. 分析一个方案
  2. 按其全球图表位置查找指令
  3. 在核心图中查找名称
  4. 构建含有隐含范围的文档
  5. 在文档的链接上显示
  6. 文档中的名称标准化
  7. 背景情况
  8. 汇编
  9. 编译器的问题
  10. 在实践中
  11. 设计原则
  12. AST 重点
  13. 纯正而不可改变
  14. 懒惰
  15. 分类值类型

@ apollo/ core-schema

处理核心图案的字典库

通过 npm 安装 :

npm install @apollo/core-schema

从源创建:

npm install
npm test

快点

分析一个方案

import { Schema, gql } from '@apollo/core-schema'

const schema = Schema.basic(gql`${"example.graphql"}
  @link(url: "https://specs.apollo.dev/federation/v1.0")
  @link(url: "https://specs.apollo.dev/inaccessible/v0.1")

  type User @inaccessible {
    id: ID!
  }
`);

expect([...schema]).toMatchInlineSnapshot(`
  Array [
    <>[GraphQL request] 👉@link(url: "https://specs.apollo.dev/federation/v1.0"),
    <#User>[GraphQL request] 👉type User @inaccessible {,
  ]
`);

expect([...schema.scope]).toMatchInlineSnapshot()

expect([...schema.refs]).toMatchInlineSnapshot(`
  Array [
    <>[example.graphql] 👉@link(url: "https://specs.apollo.dev/federation/v1.0"),
    <https://specs.apollo.dev/link/v1.0#@>[example.graphql] 👉@link(url: "https://specs.apollo.dev/federation/v1.0"),
    <https://specs.apollo.dev/link/v1.0#@>[example.graphql] 👉@link(url: "https://specs.apollo.dev/inaccessible/v0.1"),
    <#User>[example.graphql] 👉type User @inaccessible {,
    <https://specs.apollo.dev/inaccessible/v0.1#@>[example.graphql] type User 👉@inaccessible {,
    <#ID>[example.graphql] id: 👉ID!,
  ]
`);

按其全球图表位置查找指令

import {Schema, Defs, GRef, directives} from '@apollo/core-schema'

const schema = Schema.basic(gql `
  extend schema
    @link(url: "https://spec.example.io/hidden/v1.0", as: "private")

  type Product
  type Admin @private
  type User
`)

const HIDDEN = GRef.rootDirective('https://spec.example.io/hidden/v1.0')
function *hiddenDefs(defs: Defs) {
  for (const def of defs) {
    for (const directive of directives(def)) {
      if (directive.gref === HIDDEN) {
        yield def
        break
      }
    }
  }
}

expect([...hiddenDefs(schema)].map(def => def.name))
  .toEqual(['Admin'])

在核心图中查找名称

得到一个 Schema 从文档中 Schema.from 接下来 通过查询文档名称 schema.scope数字 :

import {Schema, GRef, ref} from '@apollo/core-schema'

const doc = Schema.from(gql `
  extend schema
    @link(url: "https://specs.apollo.dev/link/v1.0")
    @link(url: "https://example.com/someSpec/v1.0")
    @link(url: "https://spec.example.io/another/v1.0", as: "renamed")
`)
expect(doc.scope.lookup('@link')).toBe(
  GRef.rootDirective('https://specs.apollo.dev/link/v1.0')
)
expect(doc.scope.lookup('renamed__Type'))).toBe(
  GRef.named('Type', "https://spec.example.io/another/v1.0")
)

构建含有隐含范围的文档

用一套内建文件来解释文档往往有用 链接已经存在于范围.

Scope.from 第二次辩论,所谓的 frame改为: 启用此功能:

const SUBGRAPH_BUILTINS = Schema.from(gql `
  extend schema
    @link(url: "https://specs.apollo.dev/link/v1.0")
    @link(url: "https://specs.apollo.dev/federation/v1.0",
          import: "@key @requires @provides @external")
`)

function subgraph(document: DocumentNode) {
  return Schema.from(document, SUBGRAPH_BUILTINS)
}

subgraph(gql `
  # @key in the next line will be linked to:
  #
  #   https://specs.apollo.dev/federation/v1.0#@key
  type User @key(field: "id") {
    id: ID!
  }
`)

subgraph(gql `
  # this will shadow the built-in link to @key:
  extend schema @link(url: "https://specs.apollo.dev/federation/v2.0",
    import: "@key")

  # @key in the next line will be linked to:
  #
  #   https://specs.apollo.dev/federation/v2.0#@key
  type User @key(field: "id") {
    id: ID!
  }`)
function linksFed2(doc: Schema) {
  for (const link of doc.scope) {
    if (link.gref.graph.satisfies(LinkUrl.from("https://specs.apollo.dev/federation/v2.0"))) {
      // child links federation 2.0
      return true
    }  
  }
  return false
}

expect(
  linksFed2(Schema.basicFrom(gql `
    extend schema @link(url: "https://specs.apollo.dev/federation/v2.0")
  `))
).toBe(true)

expect(
  linksFed2(Schema.basicFrom(gql `
    extend schema @link(url: "https://specs.apollo.dev/federation/v1.9")
  `))
).toBe(false)

expect(
  linksFed2(Schema.basicFrom(gql ``))
).toBe(false)

文档中的名称标准化

您也许想要在文档中扫描指令, 而不必担心用户是否重新命名了它们 .

页:1 schema.standardize(...urls) 方法可以帮助:

const subgraph = Schema.basic(gql `
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        # what weird naming choices!
        import: """
          @key      (as @fkey)
          @requires (as @frequires)
          @provides (as @fprovides)
          @tag      (as @ftag)
        """)

  type User @fkey(fields: "id") {
    id: ID! @ftag(name: "hi") @tag(name: "my tag")
  }

  # note: this is our *own* @tag directive, which looks
  # just like but means something different than
  # federation's @tag:
  directive @tag(name: string) on FIELD_DEFINITION
`);

expect(
  raw(
    // standardize takes LinkUrls and ensures that all references to that schema
    // are prefixed with its standard name
    subgraph.standardize("https://specs.apollo.dev/federation/v2.0").print()
  )
).toMatchInlineSnapshot(`
  extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/id/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.0")

  type User @federation__key(fields: "id") {
    id: ID! @federation__tag(name: "hi") @tag(name: "my tag")
  }

  directive @tag(name: string) on FIELD_DEFINITION
`);

动机

此库是用来帮助您阅读和操作核心计划 .

背景情况

核心计划 可相互参考元素.

例如,这个计划参考federation 2.0并使用 @key 其指令:

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0")

type User @federation__key(fields: "id") {
  id: ID!
}

在这里,我们链接 federation 以它的url 显示 spec。 此链接名称 federation 转到url https://specs.apollo.dev/federation/v2.0,指示核心意识处理器识别像 federation__FieldSet 财务报告和审定财务报表 @federation__key 定义由 https://specs.apollo.dev/federation/v2.0. 互联网档案馆的存檔,存档日期2013-12-22。 。 。 .

@link 推断名称 federation (以及版本) 2.0从url. 也可以明确设置名称:

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0", as: fedv2)

type User @fedv2__key(fields: "id") {
  id: ID!
}

这些名字会让人讨厌 所以 @link 还提供 import 参数,将非前缀名称链接到远程定义:

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: "@key")

type User @key(fields: "id") {
  id: ID!
}

让我们解决冲突 比如说我有这个计划:

type User @key(column: "id") {
  id: ID!
}

directive @key(column: string) on OBJECT

现在说我想把这个计划 变成联邦的子图 联邦已经定义 @key 指令,它会和我自己的冲突 @key 指令,这是无关的.

与 @link我可以给联邦的 @key 命令任何我想要的名字, 避免冲突 :

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: "@fedKey: @key")

type User @fedKey(fields: "id") @key(column: "id") {
  id: ID!
}

directive @key(column: string) on OBJECT

请注意,这也有利于难民署 @link 指令本身:

extend schema
  @coreLink(url: "https://specs.apollo.dev/link/v1.0", as: coreLink)
  @coreLink(url: "https://specs.apollo.dev/federation/v2.0",
        import: "@fedKey: @key")

汇编

上面的例子不是有效的 GraphQL 计划,因为它们并不包含它们所命名的所有元素的定义。 具体地说,它们没有包含联邦指令的定义,也没有包含联邦指令的定义 @link 自己来 如果您把它们输入到一个需要有效的 GraphQL 方案的工具中, 这个工具将会被打破 .

看来我们应该能解决这个问题. @link 非常像一个 import 语句 - 它的存在似乎意味着某种汇编过程,可以某种方式查阅相关定义并插入到文件中.

这个库提供了这样的机制。 其间,它提供了一个框架,用以与 全球图 定义——从中构建计划,从一个文件复制到另一个文件,等等.

编译器的问题

再来一次

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: "@fedKey: @key")

type User @fedKey(fields: "id") {
  id: ID!
}

编译器必须查看此方案,并为文档中引用但未定义的任何元素插入定义。 说我们有一个地图集 里面有一个计划:

extend schema
  # @id is @link's sister, specifying this schema's
  # position within the global graph
  @id(url: "https://specs.apollo.dev/federation/v2.0")

directive @key(fields: FieldSet!) on OBJECT
scalar FieldSet

编辑器需要复制定义 @key 输入文档。 然后它也需要复制定义 FieldSet,从 @key 参考文献 FieldSet. 当它将这些定义插入到文档中时,它需要更改它们的名称,以适应文档的名称空间。 核心计划可以转接 @link 其他核心计划,因此可能涉及增加 @link给其他计划.

这个图书馆揭露了一个编辑模型.

编辑模式

基本办法是:

  1. 读取一个计划,并通过审查其范围来构建其范围 @link 指令。 。 。 范围管理命名空间——它能够查看文档中的任何定义或参考,并将其与全局图表位置(url,基本上是)联系起来。 范围与某一要素在文档中是否有定义完全无关——它的唯一工作是将名称与urls联系起来.
  2. 当复制文档中的节点时,用其全局图位置来注释这些节点及其后人。 我们称之为这个过程 隔离 或者说 非正常化 (因为该数据库携带的元数据) @link 指令被非正常化到整个树上.
  3. 将定义按需要移动而不担心命名空间
  4. 在发放完成的文件之前,收集所有参考文献,生成适当的 @link 信头,以及 重新正常化 它的所有节点, 酌情设置它们的名字.

节点的去正态化和再正态化的过程大多是透明的.

在实践中

您可以构造一个 Schema 从类似 GraphQL 的文档中:

import {Schema, gql} from '@apollo/core-schema'

const schema = Schema.from(gql `
  extend schema
    @id(url: "https://my/schema")
    @link(url: "https://specs.apollo.dev/link/v1.0")
    @link(url: "https://specs.apollo.dev/federation", import: "@key")
    @link(url: "https://myorg.internal/future")

  type User @key(fields: "id") @future
`)

Schema可执行性,产生文件中的每一项定义:

const defs = [...schema]

Schema总是产生离子树。 分离子树中的定义和参考文献 .gref 属性,在全局图中查找节点:

import {GRef} from '@apollo/core-schema'

expect(defs[defs.length - 1].gref).toBe(
  GRef.named('User', 'https://my/schema')
)

(一个"gref"是"g"raph"的"ref".

您可以使用任意机制在文档中插入已解开的节点:

// helper to create a detached @tag directive
function $tag(name: string) {
  return {
    kind: Kind.DIRECTIVE,
    name: "tag",
    arguments: [{
      name: { kind: Kind.NAME, value: "name" },
      value: { kind: Kind.STRING, value: name }
    }],
    gref: GRef.rootDirective("https://specs.apollo.dev/tag/v0.1")
  }
])

// replace @future with @tag(name: "future")
const newSchema = schema.mapDoc(schema =>
  visit(schema.document, {
    Directive(node) {
      if (!hasRef(node)) return
      if (node.gref === GRef.rootDirective("https://myorg.internal/future")) {
        // replace @future with @tag(name: "future")
        return $tag("future")
      }
    }
  }))

终于可以打电话了 compile 重新规范一切并确保适当的 @link 有页眉:

return newSchema.compile()

schema.compile() 使用可选参数, a atlas 委员会将从中设法填补文件中已提及但未列入的任何定义. atlas 它可以是任何可执行的 超越离散的定义。 例如,它可能是另一个 Schema数字 :

const tagSchema = Schema.basic(gql`
  @id(url: "https://specs.apollo.dev/tag/v0.1")
  directive @tag(name: string) repeatable on OBJECT
`)
return newSchema.compile(tagSchema)

您可以使用 Atlas 将多个计划合并成地图集.

设计原则

AST 重点

这个图书馆以AST为中心与计划合作.

这是很好的,因为AST可以代表许多无法代表的情况 GraphQLSchema例如,没有包含所有定义(本库的原则动机!)的图案不能在其中表示 GraphQL* 类结构。 因此,我们只是不尝试: 这个图书馆从来没有打电话 buildSchema我们也不接触注重执行的班级 GraphQLSchema。 。 。 .

此外,与AST合作也使我们有能力 小数 修改文档而不彻底改变结构。 默认情况下,此处执行的操作试图对文档进行最小的修改,并尽可能保留其结构. alas, 图克解析器的局限性意味着我们目前无法保存注释 .

最后,AST节点被解析器赋予了源位并保留该位置,甚至跨越复杂的变换. 这样做有助于错误报告,也将使生成源图相对容易,尽管我们目前没有这样做.

纯正而不可改变

基本上整个库都是在不可移动的数据结构上作为纯函数执行的,从ASTNodes(我们视其为不可移动)开始. 昂贵的操作被纪念.

懒惰

纯/不可改变/记忆设计的一个后果是,我们一般不计算任何东西,直到我们需要它。 例如, Schema.from 甚至不扫描文档的 @link和构造一个范围,直到 schema.scope 已实际使用。 同样地,节点在被访问之前不会被非正常化.

分类值类型

少数类型,尤其是 GRef, (中文(简体) ), LinkUrl,以及 Version- 已经是 扫描也就是说,它们只能通过记忆性函数来创建,这确保了两个等同的例子永远是同一个例子:

expect(LinkUrl.from('https://specs/example/?extraneous&stuff&ignored'))
  .toBe(LinkUrl.from('https://specs/example'))

它们是有效的价值类型,它们可以(和)被使用,例如作为键在 Map编号.

版本与发布记录

@apollo/core-schema 共发布 7 个版本,首次发布于 2021/08/13;过去 12 个月发布了 0 个版本。

版本发布日期距今
0.3.0latest2022/04/084 年前
0.2.32022/03/284 年前
0.2.22022/02/174 年前
0.2.12021/12/174 年前
0.2.02021/11/045 年前
0.1.12021/10/215 年前

关于 @apollo/core-schema 的常见问题

@apollo/core-schema 是什么?

阿波罗核心Schema处理库

如何安装 @apollo/core-schema?

在项目目录运行 npm install @apollo/core-schema,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:graphql(^15 || ^16)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@apollo/core-schema 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@apollo/core-schema 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@apollo/core-schema 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=12.13.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@apollo/core-schema 可以商用吗?

@apollo/core-schema 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@apollo/core-schema 的下载量有多少?

根据 npm 官方下载统计,@apollo/core-schema 在 2026/10/07 – 2026/11/05 这 30 天内被下载 43,293 次,日均约 1,443 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@apollo/core-schema 还在维护吗?

最新版本 0.3.0 发布于 2022/04/08(4 年前);至今共发布 7 个版本,其中过去 12 个月发布了 0 个;首次发布于 2021/08/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。