From 621ba4b63c4a7b7f7b96540e6fab803727ec407b Mon Sep 17 00:00:00 2001 From: Ashi Krishnan Date: Wed, 20 Apr 2022 16:00:18 -0400 Subject: [PATCH 01/13] begin compiling library specs --- .graphs/https/specs.apollo.dev/anatomy.css | 56 + .../https/specs.apollo.dev/apollo-colors.css | 101 + .../https/specs.apollo.dev/apollo-light.css | 324 +++ .../https/specs.apollo.dev/async.js | 0 .../https/specs.apollo.dev/boot.js | 0 .../specs.apollo.dev/federation/v2.0.graphql | 78 + .../specs.apollo.dev/federation/v2.0.html | 1623 +++++++++++ .../inaccessible/v1.0.graphql | 127 + .../specs.apollo.dev/inaccessible/v1.0.html | 1555 +++++++++++ .../https/specs.apollo.dev/index.html | 0 .../https/specs.apollo.dev/inject-logo.js | 0 .graphs/https/specs.apollo.dev/light.css | 610 +++++ .../https/specs.apollo.dev/link/v1.0.graphql | 448 +++ .graphs/https/specs.apollo.dev/link/v1.0.html | 2124 ++++++++++++++ .../https/specs.apollo.dev/load.js | 0 .../https/specs.apollo.dev/main.js | 0 .../https/specs.apollo.dev/markdown.js | 0 .../https/specs.apollo.dev/mermaid.js | 0 .../https/specs.apollo.dev/query.js | 0 .../https/specs.apollo.dev/rendering.js | 0 .../https/specs.apollo.dev/toc.js | 0 federation/v2.0.graphql | 86 + graphs.json | 3 + inaccessible/coreDirectives.graphql | 5 + inaccessible/inaccessible.spec.graphql | 124 + inaccessible/processedSchema.graphql | 28 + inaccessible/schema.graphql | 38 + inaccessible/spec.md | 121 + link/basic.graphql | 14 + link/link-v1.0.graphql | 456 +++ link/prefix-uniqueness.graphql | 25 + link/prefixing.graphql | 46 + netlify.toml | 2 +- package-lock.json | 2437 ++++++++++++++++- package.json | 1 - 35 files changed, 10423 insertions(+), 9 deletions(-) create mode 100644 .graphs/https/specs.apollo.dev/anatomy.css create mode 100644 .graphs/https/specs.apollo.dev/apollo-colors.css create mode 100644 .graphs/https/specs.apollo.dev/apollo-light.css rename async.js => .graphs/https/specs.apollo.dev/async.js (100%) rename boot.js => .graphs/https/specs.apollo.dev/boot.js (100%) create mode 100644 .graphs/https/specs.apollo.dev/federation/v2.0.graphql create mode 100644 .graphs/https/specs.apollo.dev/federation/v2.0.html create mode 100644 .graphs/https/specs.apollo.dev/inaccessible/v1.0.graphql create mode 100644 .graphs/https/specs.apollo.dev/inaccessible/v1.0.html rename index.html => .graphs/https/specs.apollo.dev/index.html (100%) rename inject-logo.js => .graphs/https/specs.apollo.dev/inject-logo.js (100%) create mode 100644 .graphs/https/specs.apollo.dev/light.css create mode 100644 .graphs/https/specs.apollo.dev/link/v1.0.graphql create mode 100644 .graphs/https/specs.apollo.dev/link/v1.0.html rename load.js => .graphs/https/specs.apollo.dev/load.js (100%) rename main.js => .graphs/https/specs.apollo.dev/main.js (100%) rename markdown.js => .graphs/https/specs.apollo.dev/markdown.js (100%) rename mermaid.js => .graphs/https/specs.apollo.dev/mermaid.js (100%) rename query.js => .graphs/https/specs.apollo.dev/query.js (100%) rename rendering.js => .graphs/https/specs.apollo.dev/rendering.js (100%) rename toc.js => .graphs/https/specs.apollo.dev/toc.js (100%) create mode 100644 federation/v2.0.graphql create mode 100644 graphs.json create mode 100644 inaccessible/coreDirectives.graphql create mode 100644 inaccessible/inaccessible.spec.graphql create mode 100644 inaccessible/processedSchema.graphql create mode 100644 inaccessible/schema.graphql create mode 100644 inaccessible/spec.md create mode 100644 link/basic.graphql create mode 100644 link/link-v1.0.graphql create mode 100644 link/prefix-uniqueness.graphql create mode 100644 link/prefixing.graphql diff --git a/.graphs/https/specs.apollo.dev/anatomy.css b/.graphs/https/specs.apollo.dev/anatomy.css new file mode 100644 index 0000000..395f659 --- /dev/null +++ b/.graphs/https/specs.apollo.dev/anatomy.css @@ -0,0 +1,56 @@ +@media (min-width: 720px) { + code.anatomy { + font-size: 110%; + } +} + +code.anatomy { + white-space: inherit; +} + +code.anatomy span { + position: relative; + color: var(--color); +} + +.red { + --color: var(--red-base); +} + +.blue { + --color: var(--blue-base); +} + +.pink { + --color: var(--pink-base); +} + +.yellow { + --color: var(--yellow-base); +} + +.green { + --color: var(--green-base); +} + +.grey { + --color: var(--grey-base); +} + +code.anatomy span > aside { + position: absolute; + top: 100%; + left: 0; + font-size: 50%; + width: calc(100% - 8px); + display: block; + border-bottom: 1px solid var(--color); + border-left: 1px solid var(--color); + padding: 4px; + height: calc(20% + 10px); +} + +code.anatomy span > span > aside { + height: calc(20% + 5px); +} + diff --git a/.graphs/https/specs.apollo.dev/apollo-colors.css b/.graphs/https/specs.apollo.dev/apollo-colors.css new file mode 100644 index 0000000..2793da8 --- /dev/null +++ b/.graphs/https/specs.apollo.dev/apollo-colors.css @@ -0,0 +1,101 @@ +html { + /** Brand colors **/ + --pink-darkest: rgb(102, 31, 78); + --pink-darker: rgb(131, 35, 99); + --pink-dark: rgb(196, 57, 151); + --pink-base: rgb(242, 92, 193); + --pink-light: rgb(255, 163, 224); + --pink-lighter: rgb(255, 212, 241); + --pink-lightest: rgb(255, 230, 247); + --teal-darkest: rgb(31, 102, 100); + --teal-darker: rgb(29, 123, 120); + --teal-dark: rgb(38, 162, 157); + --teal-base: rgb(65, 217, 211); + --teal-light: rgb(139, 246, 242); + --teal-lighter: rgb(198, 255, 253); + --teal-lightest: rgb(230, 255, 254); + --indigo-darkest: rgb(45, 31, 102); + --indigo-darker: rgb(49, 28, 135); + --indigo-dark: rgb(63, 32, 186); + --indigo-base: rgb(113, 86, 217); + --indigo-light: rgb(173, 155, 246); + --indigo-lighter: rgb(217, 207, 255); + --indigo-lightest: rgb(235, 230, 255); + + /** Neutrals **/ + --black-darker: rgb(18, 21, 26); + --black-dark: rgb(20, 23, 28); + --black-base: rgb(25, 28, 35); + --black-light: rgb(34, 38, 46); + --black-lighter: rgb(47, 53, 63); + --grey-darker: rgb(66, 72, 85); + --grey-dark: rgb(90, 98, 112); + --grey-base: rgb(119, 127, 142); + --grey-light: rgb(149, 157, 170); + --grey-lighter: rgb(178, 185, 195); + --silver-darker: rgb(202, 208, 216); + --silver-dark: rgb(222, 226, 231); + --silver-base: rgb(235, 238, 240); + --silver-light: rgb(244, 246, 248); + --silver-lighter: rgb(252, 253, 255); + + /** Interface Colors **/ + --red-darkest: rgb(102, 31, 31); + --red-darker: rgb(120, 28, 28); + --red-dark: rgb(156, 35, 35); + --red-base: rgb(209, 59, 59); + --red-light: rgb(241, 134, 134); + --red-lighter: rgb(255, 195, 195); + --red-lightest: rgb(255, 230, 230); + --green-darkest: rgb(20, 94, 51); + --green-darker: rgb(19, 108, 56); + --green-dark: rgb(28, 132, 72); + --green-base: rgb(54, 173, 104); + --green-light: rgb(126, 217, 164); + --green-lighter: rgb(190, 244, 213); + --green-lightest: rgb(230, 255, 240); + --blue-darkest: rgb(22, 60, 102); + --blue-darker: rgb(15, 65, 122); + --blue-dark: rgb(16, 83, 160); + --blue-base: rgb(32, 117, 214); + --blue-light: rgb(116, 176, 244); + --blue-lighter: rgb(187, 219, 255); + --blue-lightest: rgb(240, 247, 255); + + /** Alternate Colors **/ + --orange-darkest: rgb(102, 63, 31); + --orange-darker: rgb(136, 76, 30); + --orange-dark: rgb(180, 102, 38); + --orange-base: rgb(245, 145, 64); + --orange-light: rgb(255, 193, 143); + --orange-lighter: rgb(255, 226, 202); + --orange-lightest: rgb(255, 241, 230); + --yellow-darkest: rgb(102, 80, 31); + --yellow-darker: rgb(132, 103, 29); + --yellow-dark: rgb(180, 143, 37); + --yellow-base: rgb(244, 208, 63); + --yellow-light: rgb(255, 232, 142); + --yellow-lighter: rgb(255, 244, 202); + --yellow-lightest: rgb(255, 250, 230); + --purple-darkest: rgb(66, 22, 102); + --purple-darker: rgb(82, 21, 132); + --purple-dark: rgb(113, 30, 180); + --purple-base: rgb(162, 61, 245); + --purple-light: rgb(205, 143, 255); + --purple-lighter: rgb(232, 204, 255); + --purple-lightest: rgb(244, 230, 255); + --blilet-darkest: rgb(27, 34, 64); + --blilet-darker: rgb(37, 46, 80); + --blilet-dark: rgb(60, 74, 133); + --blilet-base: rgb(81, 104, 194); + --blilet-light: rgb(122, 146, 240); + --blilet-lighter: rgb(176, 190, 247); + --blilet-lightest: rgb(230, 235, 255); + --midnight-darkest: rgb(6, 15, 47); + --midnight-darker: rgb(27, 34, 64); + --midnight-dark: rgb(56, 61, 91); + --midnight-base: rgb(61, 75, 106); + --midnight-light: rgb(86, 105, 146); + --midnight-lighter: rgb(121, 143, 187); + --midnight-lightest: rgb(180, 195, 219); +} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/apollo-light.css b/.graphs/https/specs.apollo.dev/apollo-light.css new file mode 100644 index 0000000..9752f31 --- /dev/null +++ b/.graphs/https/specs.apollo.dev/apollo-light.css @@ -0,0 +1,324 @@ +@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,400;0,700;1,300;1,700&display=swap'); + +@import "apollo-colors.css"; +@import "anatomy.css"; + +:root { + --body-font-family: Source Sans Pro,sans-serif; + background: var(--body-background-color); + color: var(--body-text-color); + --body-background-color: #FFF; + --body-text-color: var(--grey-dark); + --button-background-color: var(--grey-darker); + --highlight-border-color: var(--yellow-base); + --highlight-background: none; +} + + +/*** Header ***/ +header > h1 { + color: var(--black-lighter); + text-align: center; + font-size: 250%; + margin-bottom: 0; + margin-top: 1em !important; +} + +header h2 { + font-size: 150%; + font-style: italic; + color: var(--black-lighter); + margin-top: 0; + text-align: center; +} + +.spec-data { + margin: auto; +} + +.spec-data td { + border: 0; + color: var(--grey-dark); +} + +.spec-data td:first-of-type { + color: var(--grey-dark); + font-style: italic; + text-align: right; +} + +.spec-data td:first-of-type::after { + content: ": "; +} + +/*** Headers & Text ***/ + +h1,h2,h3,h4,h5,h6 { + color: var(--black-lighter); +} + +h1 { + margin-top: 2rem; +} + +h2 { + margin-top: 2rem; +} + +a { + color: var(--indigo-dark); +} + +.spec-note { + background: none; + border-color: var(--yellow-base); +} + +.spec-note > a:first-of-type { + color: var(--yellow-dark); +} + +/*** Table of Contents ***/ +/***** Inline TOC ******/ +.spec-secid a { + color: var(--indigo-dark); +} + +/***** Sidebar ******/ +.spec-sidebar { + background: none; + backdrop-filter: blur(25px); + box-shadow: none !important; + left: 0; +} + +@media (min-width: 1220px) { + .spec-sidebar { + backdrop-filter: none; + } +} + +article { + display: table; + margin: auto; + box-sizing: border-box; + padding-left: 1em; + padding-right: 1em; + margin-left: auto; + margin-right: auto; +} + +p { + max-width: calc(100vw - 2em); +} + +figure { + max-width: calc(100vw - 2em); +} + +@media (min-width: 720px) { + article { + margin-left: auto; + } +} + +@media (min-width: 1220px) { + article { + padding-left: calc(50vw - var(--sidebar-width) + 5rem); + margin-left: auto; + margin-right: calc(50vw - var(--sidebar-width)); + } +} + +@media (min-width: 1500px) { + article { + padding-left: calc(50vw - var(--sidebar-width)); + } +} + +body { + padding: 0 !important; + margin: 0 !important; +} + +.spec-sidebar .viewing > a::after { + color: var(--yellow-base) +} + +/*** Figures ***/ +figure { + border-color: transparent; +} + +.spec-example { + border-color: var(--indigo-lighter); +} + +.spec-counter-example { + border-color: var(--red-light); +} + +.spec-counter-example > figcaption > a { + color: var(--red-light); +} + +.spec-example > figcaption > a { + color: var(--indigo-light); +} + +.spec-example > pre { + overflow-y: auto; + background: none; +} + +.spec-example-num { + opacity: 1; +} + +/*** Code ***/ +var { + border: thin solid transparent; +} + +var:hover { + background: none; + border-color: var(--highlight-border-color); +} + +code { + padding: .1em .3em; + border-radius: .3em; + font-size: .9em; + color: var(--pink-base); + background: var(--silver-base); +} + +.token.operator { + background: none; +} + +.spec-definition > pre { + padding: 1rem; +} + +.spec-definition > pre > code { + font-family: var(--body-font-family); + font-style: italic; + font-size: 130%; + font-weight: bold; + line-height: 120%; +} + +@media (min-width: 720px) { + .spec-definition > pre { + margin-left: 3em; + transform: scaleX(1.02); + } +} + +h2.spec-definition-head, h3.spec-definition-head { + font-size: inherit; + color: var(--black-darker); +} + + +/*** Selections ***/ +::selection { + background-color: var(--yellow-light); +} + +.selection-link, +.outdated-selection-link { + box-shadow: 2px 2px 5px var(--body-background-color); + color: var(--indigo-dark); +} + +.selection-link { + background-color: var(--indigo-lightest); + border: thin solid var(--indigo-dark); +} + +.selection-link:hover { + background-color: var(--indigo-lightest); + color: var(--indigo-dark); +} + +.outdated-selection-link { + background-color: var(--midnight-base); + border: thin solid var(--red-dark); +} + +.outdated-selection-link:hover { + background-color: var(--red-base); + color: var(--midnight-dark); +} + + +/*** Sidebar ***/ +.spec-sidebar-toggle + label[for="spec-sidebar-toggle"] > .spec-sidebar-button { + color: var(--grey-lighter); + z-index: 10001; + left: 0; + right: auto; + top: auto; + bottom: 0; + border-radius: 50%; + backdrop-filter: blur(10px); +} + +/*** RFC 2119 Requirements ***/ +.spec-requirement { + font-weight: bold; + color: var(--requirement-color); + --requirement-color: var(--green-light); + border-radius: 0.2rem; + transition: color 115ms, background 115ms; + padding: 0 0.1rem 0.2rem 0.1rem; +} + +.spec-requirement:hover { + background: var(--requirement-color); + color: var(--body-background-color); + text-decoration: none; +} + +.spec-requirement.required { + --requirement-color: var(--green-base); +} + +.spec-requirement.not.required { + --requirement-color: var(--red-base); +} + +.spec-requirement.recommended { + --requirement-color: var(--teal-dark); +} + +.spec-requirement.not.recommended { + --requirement-color: var(--orange-base); +} + +.spec-requirement.optional { + --requirement-color: var(--indigo-base); +} + +.spec-toc a:hover { + opacity: 0.8; +} + +.spec-toc a .spec-secid { + color: var(--indigo-dark); +} + +.spec-toc .title { + margin-top: 2em; +} + +.apollo-specs-logo a:hover { + text-decoration: none; +} + +.spec-sidebar { + border-right: 1px solid var(--silver-dark); + overflow-y: auto; +} diff --git a/async.js b/.graphs/https/specs.apollo.dev/async.js similarity index 100% rename from async.js rename to .graphs/https/specs.apollo.dev/async.js diff --git a/boot.js b/.graphs/https/specs.apollo.dev/boot.js similarity index 100% rename from boot.js rename to .graphs/https/specs.apollo.dev/boot.js diff --git a/.graphs/https/specs.apollo.dev/federation/v2.0.graphql b/.graphs/https/specs.apollo.dev/federation/v2.0.graphql new file mode 100644 index 0000000..99e442e --- /dev/null +++ b/.graphs/https/specs.apollo.dev/federation/v2.0.graphql @@ -0,0 +1,78 @@ +extend schema + +extend schema @id(url: "https://specs.apollo.dev/federation/v2.0") + +""" +The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. + +```graphql example -- using {@key} +type Product @key(fields: "upc") { + upc: UPC! + name: String +} +``` + +Multiple keys can be defined on a single object type: + +```graphql example -- defining multiple {@key}s +type Product @key(fields: "upc") @key(fields: "sku") { + upc: UPC! + sku: SKU! + name: String +} +``` + +Note: Repeated directives (in this case, `@key`, used multiple times) require support by the underlying GraphQL implementation. +""" +directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE + +""" +The `@provides` directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example: + +```graphql example -- using {@provides} +type Review @key(fields: "id") { + product: Product @provides(fields: "name") +} + +extend type Product @key(fields: "upc") { + upc: String @external + name: String @external +} +``` + +When fetching `Review.product` from the Reviews service, it is possible to request the `name` with the expectation that the Reviews service can provide it when going from review to product. `Product.name` is an external field on an external type which is why the local type extension of `Product` and annotation of `name` is required. +""" +directive @provides(fields: FieldSet!) on FIELD_DEFINITION + +""" +The `@requires` directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example: + +```graphql example -- using {@requires} +# extended from the Users service +extend type User @key(fields: "id") { + id: ID! @external + email: String @external + reviews: [Review] @requires(fields: "email") +} +``` + +In this case, the Reviews service adds new capabilities to the `User` type by providing a list of `reviews` related to a user. In order to fetch these reviews, the Reviews service needs to know the `email` of the `User` from the Users service in order to look up the reviews. This means the `reviews` field / resolver *requires* the `email` field from the base `User` type. +""" +directive @requires(fields: FieldSet!) on FIELD_DEFINITION + +""" +The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example: + +```graphql example -- using {@external} +# extended from the Users service +extend type User @key(fields: "email") { + email: String @external + reviews: [Review] +} +``` + +This type extension in the Reviews service extends the `User` type from the Users service. It extends it for the purpose of adding a new field called `reviews`, which returns a list of `Review`s. +""" +directive @external on FIELD_DEFINITION + +scalar FieldSet \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/federation/v2.0.html b/.graphs/https/specs.apollo.dev/federation/v2.0.html new file mode 100644 index 0000000..b927d6e --- /dev/null +++ b/.graphs/https/specs.apollo.dev/federation/v2.0.html @@ -0,0 +1,1623 @@ + + + + +federation v2.0 + + + + + + + +
+
+

federation v2.0

+ +
+
+

1@key

+
directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
+

The @key directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface.

+
Example № 1 using @key
type Product @key(fields: "upc") {
+  upc: UPC!
+  name: String
+}
+
+

Multiple keys can be defined on a single object type:

+
Example № 2 defining multiple @keys
type Product @key(fields: "upc") @key(fields: "sku") {
+  upc: UPC!
+  sku: SKU!
+  name: String
+}
+
+
+Note +Repeated directives (in this case, @key, used multiple times) require support by the underlying GraphQL implementation.
+
+
+

2@provides

+
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+

The @provides directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example:

+
Example № 3 using @provides
type Review @key(fields: "id") {
+  product: Product @provides(fields: "name")
+}
+
+extend type Product @key(fields: "upc") {
+  upc: String @external
+  name: String @external
+}
+
+

When fetching Review.product from the Reviews service, it is possible to request the name with the expectation that the Reviews service can provide it when going from review to product. Product.name is an external field on an external type which is why the local type extension of Product and annotation of name is required.

+
+
+

3@requires

+
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
+

The @requires directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example:

+
Example № 4 using @requires
# extended from the Users service
+extend type User @key(fields: "id") {
+  id: ID! @external
+  email: String @external
+  reviews: [Review] @requires(fields: "email")
+}
+
+

In this case, the Reviews service adds new capabilities to the User type by providing a list of reviews related to a user. In order to fetch these reviews, the Reviews service needs to know the email of the User from the Users service in order to look up the reviews. This means the reviews field / resolver requires the email field from the base User type.

+
+
+

4@external

+
directive @external on FIELD_DEFINITION
+

The @external directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example:

+
Example № 5 using @external
# extended from the Users service
+extend type User @key(fields: "email") {
+  email: String @external
+  reviews: [Review]
+}
+
+

This type extension in the Reviews service extends the User type from the Users service. It extends it for the purpose of adding a new field called reviews, which returns a list of Reviews.

+
+
+ + +
+ + +
+ + + diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v1.0.graphql b/.graphs/https/specs.apollo.dev/inaccessible/v1.0.graphql new file mode 100644 index 0000000..74310b2 --- /dev/null +++ b/.graphs/https/specs.apollo.dev/inaccessible/v1.0.graphql @@ -0,0 +1,127 @@ +extend schema @id(url: "https://specs.apollo.dev/inaccessible/v1.0") + +extend schema @id(url: "https://specs.apollo.dev/inaccessible/v1.0") + +""" +The {@inaccessible} directive removes definitions from a schema's API. + +```raw html + + + +
StatusRelease
Version0.1
+ + +``` + +This document defines a [core feature](https://specs.apollo.dev/core) named `inaccessible` for removing fields and types from a core schema. "Types" will be used throughout this document to refer to Object, Interface, and Union types in GraphQL. + +This specification provides machinery to remove fields and types in a *core schema* which have the `@inaccessible` directive applied. + +# How to read this document + +This document uses [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL. + +# Definitions + +## Processor + +This specification makes references to **Processors**. Processors are described in the [Actors section of the `@core` spec](https://specs.apollo.dev/core/v0.2/#sec-Actors) as an actor which can perform transformations on a core schema. In the case of `@inaccessible`, the Processor will be expected to remove various parts of a core schema. + +# Example: Sensitive User Data + +*This section is non-normative.* + +We'll refer to this example of a core schema with sensitive user data throughout the document: + +:::[example](./schema.graphql) -- Core schema example + +The schema above contains both a field (`User.id`) and type (`BankAccount`) that are marked as `@inaccessible`. These symbols should be omitted from the processed schema anywhere they would appear. When the processed schema below is generated from this core schema, notice what has been removed: +* `User.id` field +* `BankAccount` type +* `User.bankAccount` field (because it _returns_ the `BankAccount` type) +* `Account` union's `BankAccount` type + +:::[example](./processedSchema.graphql) -- Core schema after processing + +# Overview + +*This section is non-normative.* It describes the motivation behind the directives defined by this specification. + +A core schema which has been processed according to the inaccessible spec is a queryable graph, intended to be served by a [Data Core](https://specs.apollo.dev/core/v0.2/#sec-Actors). Various use cases require that fields and types should not be visible to or queried for by clients. The `@inaccessible` directive fulfills this requirement, providing schema authors a mechanism to specify which fields and types should be omitted from the processed schema. + +# Basic Requirements + +Schemas using the `inaccessible` core feature must be valid [core schema documents](https://specs.apollo.dev/core/v0.2) with *@core directives* referencing the `core` specification and this specification. + +Here is an example `@core` usage: + +:::[example](./coreDirectives.graphql) -- required @core directives + +As described in the [core schema specification](https://specs.apollo.dev/core/v0.2/#sec-Prefixing), your schema may prefix the `@inaccessible` directive by including an `as` argument to the `@core` directive which references this specification. All references to `@inaccessible` in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema. + +In order to use the directive described by this specification, GraphQL requires you to include the definition in your schema. + +## Producer Responsibilities + +[Producers](https://specs.apollo.dev/core/v0.2/#sec-Actors) MUST include a definition of the directive compatible with the above definition and all usages in the document. + +## Processor Responsibilities + +The Processor is responsible for removing all inaccessible elements from the schema output. Note in the `InaccessibleRemoval` algorithm below that because a Union can belong to another Union's set of types, the removal of a Union type may have an upwards "cascading" effect, causing other Unions to become candidates for removal. + +# Algorithms + +## Is Inaccessible? + +Return true if the named element {element} is inaccessible. + +IsInaccessible(document, element) : + 1. If {IsMarkedInaccessible(document, element)}, **Return** {true} + 2. If {element} is a FieldDefinition, + 1. Let {parent} be the parent definition for {element} + 2. If {IsMarkedInaccessible(document, parent)}, **Return** {true} + 3. **Return** {false} + +Return true iff the named element {element} is marked as inaccessible. + +IsMarkedInaccessible(document, element) : + 1. Let {assignments} be the result of assigning features via {AssignFeatures(document)} + 2. For each Directive {d} on {element}, + 1. If {assignments}`[`{element}`]` is a Directive whose `feature` argument is this spec, **Return** {true} + 3. **Return** {false} + +## Remove Inaccessible Elements + +Given a schema document, return the set of all schema elements which should be removed in the API. + +RemoveInaccessible(document) : + 1. For each named schema element {e} defined in the {document}, + 1. If {IsInaccessible(document, e)} is {true}, {Remove(document, e)} + +Remove(document, element) : + 1. If {element} is a Field or Input Field, + 1. Delete the definition of {element} in {document} + 2. Let {parent} be the parent type of {f} in {document} + 2. If {parent} has no fields, {Remove(document, parent)} + 2. If {element} is a type: + 1. Delete all definitions and extensions of {element} in {document} + 2. If {element} is an output type (object, interface, union, or scalar): + 1. For each Field {f} in {document} where {f} has a Return Type of {element}, {Remove(document, f)} + 3. If {element} is an input type (input object, enum, or scalar): + 1. For each Field {f} in {document} where {f} has any argument of type {element}, {Remove(document, f)} + 2. For each input object type {t} in {document}, + 1. For each input field {f} of {t} where the type of {f} is {element}, {Remove(document, f)} + 4. If {element} is an Object or Union type, + 2. For each Union {u} in {document} where {element} is a member of {u}, + 1. Delete {element} as member of {u} + 2. If {u} has no members, {Remove(document, u)} + 5. If {element} is an Interface type, + 3. For each Object or Interface type {t} which implements {element} in {document}, + 1. Delete {element} as an interface conformance of {t} +""" +schema { + query: Query +} + +directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v1.0.html b/.graphs/https/specs.apollo.dev/inaccessible/v1.0.html new file mode 100644 index 0000000..7046f85 --- /dev/null +++ b/.graphs/https/specs.apollo.dev/inaccessible/v1.0.html @@ -0,0 +1,1555 @@ + + + + +inaccessible v1.0 + + + + + + + +
+
+

inaccessible v1.0

+ +
+
+ + +
+ + +
+ + + diff --git a/index.html b/.graphs/https/specs.apollo.dev/index.html similarity index 100% rename from index.html rename to .graphs/https/specs.apollo.dev/index.html diff --git a/inject-logo.js b/.graphs/https/specs.apollo.dev/inject-logo.js similarity index 100% rename from inject-logo.js rename to .graphs/https/specs.apollo.dev/inject-logo.js diff --git a/.graphs/https/specs.apollo.dev/light.css b/.graphs/https/specs.apollo.dev/light.css new file mode 100644 index 0000000..0e77d1c --- /dev/null +++ b/.graphs/https/specs.apollo.dev/light.css @@ -0,0 +1,610 @@ +@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,700;1,300;1,700&display=swap'); + +html { + --theme: light; + --family-code: 'Source Code Pro'; + --family-text: Source Sans Pro,sans-serif; + background: #FFF; + font-family: var(--family-text); + font-size: 18px; + /* SpaceKit colors from https://space-kit.netlify.app/ */ + /** Brand colors **/ + --pink-darkest: rgb(102, 31, 78); + --pink-darker: rgb(131, 35, 99); + --pink-dark: rgb(196, 57, 151); + --pink-base: rgb(242, 92, 193); + --pink-light: rgb(255, 163, 224); + --pink-lighter: rgb(255, 212, 241); + --pink-lightest: rgb(255, 230, 247); + --teal-darkest: rgb(31, 102, 100); + --teal-darker: rgb(29, 123, 120); + --teal-dark: rgb(38, 162, 157); + --teal-base: rgb(65, 217, 211); + --teal-light: rgb(139, 246, 242); + --teal-lighter: rgb(198, 255, 253); + --teal-lightest: rgb(230, 255, 254); + --indigo-darkest: rgb(45, 31, 102); + --indigo-darker: rgb(49, 28, 135); + --indigo-dark: rgb(63, 32, 186); + --indigo-base: rgb(113, 86, 217); + --indigo-light: rgb(173, 155, 246); + --indigo-lighter: rgb(217, 207, 255); + --indigo-lightest: rgb(235, 230, 255); + + /** Neutrals **/ + --black-darker: rgb(18, 21, 26); + --black-dark: rgb(20, 23, 28); + --black-base: rgb(25, 28, 35); + --black-light: rgb(34, 38, 46); + --black-lighter: rgb(47, 53, 63); + --grey-darker: rgb(66, 72, 85); + --grey-dark: rgb(90, 98, 112); + --grey-base: rgb(119, 127, 142); + --grey-light: rgb(149, 157, 170); + --grey-lighter: rgb(178, 185, 195); + --silver-darker: rgb(202, 208, 216); + --silver-dark: rgb(222, 226, 231); + --silver-base: rgb(235, 238, 240); + --silver-light: rgb(244, 246, 248); + --silver-lighter: rgb(252, 253, 255); + + /** Interface Colors **/ + --red-darkest: rgb(102, 31, 31); + --red-darker: rgb(120, 28, 28); + --red-dark: rgb(156, 35, 35); + --red-base: rgb(209, 59, 59); + --red-light: rgb(241, 134, 134); + --red-lighter: rgb(255, 195, 195); + --red-lightest: rgb(255, 230, 230); + --green-darkest: rgb(20, 94, 51); + --green-darker: rgb(19, 108, 56); + --green-dark: rgb(28, 132, 72); + --green-base: rgb(54, 173, 104); + --green-light: rgb(126, 217, 164); + --green-lighter: rgb(190, 244, 213); + --green-lightest: rgb(230, 255, 240); + --blue-darkest: rgb(22, 60, 102); + --blue-darker: rgb(15, 65, 122); + --blue-dark: rgb(16, 83, 160); + --blue-base: rgb(32, 117, 214); + --blue-light: rgb(116, 176, 244); + --blue-lighter: rgb(187, 219, 255); + --blue-lightest: rgb(240, 247, 255); + + /** Alternate Colors **/ + --orange-darkest: rgb(102, 63, 31); + --orange-darker: rgb(136, 76, 30); + --orange-dark: rgb(180, 102, 38); + --orange-base: rgb(245, 145, 64); + --orange-light: rgb(255, 193, 143); + --orange-lighter: rgb(255, 226, 202); + --orange-lightest: rgb(255, 241, 230); + --yellow-darkest: rgb(102, 80, 31); + --yellow-darker: rgb(132, 103, 29); + --yellow-dark: rgb(180, 143, 37); + --yellow-base: rgb(244, 208, 63); + --yellow-light: rgb(255, 232, 142); + --yellow-lighter: rgb(255, 244, 202); + --yellow-lightest: rgb(255, 250, 230); + --purple-darkest: rgb(66, 22, 102); + --purple-darker: rgb(82, 21, 132); + --purple-dark: rgb(113, 30, 180); + --purple-base: rgb(162, 61, 245); + --purple-light: rgb(205, 143, 255); + --purple-lighter: rgb(232, 204, 255); + --purple-lightest: rgb(244, 230, 255); + --blilet-darkest: rgb(27, 34, 64); + --blilet-darker: rgb(37, 46, 80); + --blilet-dark: rgb(60, 74, 133); + --blilet-base: rgb(81, 104, 194); + --blilet-light: rgb(122, 146, 240); + --blilet-lighter: rgb(176, 190, 247); + --blilet-lightest: rgb(230, 235, 255); + --midnight-darkest: rgb(6, 15, 47); + --midnight-darker: rgb(27, 34, 64); + --midnight-dark: rgb(56, 61, 91); + --midnight-base: rgb(61, 75, 106); + --midnight-light: rgb(86, 105, 146); + --midnight-lighter: rgb(121, 143, 187); + --midnight-lightest: rgb(180, 195, 219); +} + +body { + transition: opacity 1s; +} +/** header **/ + +header h1 { + counter-increment: none; + font-size: 2.5rem; + margin-top: 0; +} + +header h1, header h2 { + counter-increment: none; + text-align: center; +} + +header > .spec-md { + margin: auto; +} + +header > table.spec-md td { + vertical-align: top; +} + +header > table.spec-md td:first-of-type { + color: var(--grey-dark); + font-style: italic; + padding-right: 18px; +} + +header .spec-authors-list { + list-style: none; + display: inline; + margin: 0; + padding: 0; +} + +header .spec-authors-list li { + display: block; +} + +/** Light "theme" **/ + +body { + background: #FFF; + color: var(--grey-dark); + line-height: 130%; +} + +ul > li, ol > li { + margin-top: 1em; +} + +code > ol > li { + margin: 0; +} + +code { + line-height: 100%; + padding: .1em .3em; + border-radius: .3em; + font-size: .9em; + color: var(--pink-base); + background: var(--silver-base); +} + +main { + max-width: 920px; + margin: 20px 100px; +} + +hr { + background-color: var(--grey-darker); +} + +h1 { + color: var(--black-lighter); + font-weight: 400; +} + +h2 { + color: var(--black-lighter); + font-weight: 400; +} + +h3, h4 { + color: var(--grey-dark); + font-weight: 400; +} + +code[class*="language-"], +pre[class*="language-"] { + font-family: var(--family-code); + padding: 0; +} + +figure + p { + margin-top: 0; +} + +a > code { + color: var(--blue-base); +} + +a[href] { + color: var(--indigo-dark); + text-decoration: none; +} + +a[href]:hover { + text-decoration: underline; + text-decoration-skip-ink: auto; +} + +.not-csdl::after { + content: "Note: Code is GraphQL SDL, not CSDL."; + font-size: 80%; + line-height: 125%; + color: var(--yellow-base); + padding: 4px; + border-radius: 9px; + margin-left: 3em; +} + +/** Mermaid diagrams **/ +g.cluster rect { + fill: none !important; + stroke: fuchsia !important; + stroke-dasharray: 5 5; +} + +g.cluster-label { + transform: attr(transform) translateX(100px); +} + + +/** Section numbering **/ + +body { + counter-reset: section subsection listing; +} + +h2 { + counter-increment: section; + counter-reset: subsection listing; +} + +h3 { + counter-increment: subsection; + counter-reset: listing; +} + +h1,h2,h3,h4 { + position: relative; +} + +figure { + margin: 0; + margin-block-start: 2em; + margin-block-end: 2em; +} + +a.a-header { + color: var(--grey-dark); + text-decoration: none; +} + +a.a-header::before { + position: absolute; + top: 0; + transform: translateX(-100%); + margin-left: -20px; + + content: counter(section); + color: var(--grey-darker); + font-weight: normal; + text-align: right; + line-height: inherit; +} + +a.a-header:hover > .a-header-text { + text-decoration: underline; + text-decoration-skip-ink: auto; +} + +@media screen and (max-width: 980px) { + a.a-header::before { + position: static; + margin: 0; + margin-right: 10px; + transform: none; + } +} + +a.a-header:hover::before { + content: "# " counter(section); + text-decoration: none; +} + +h3 > .a-header::before { + content: counter(section) "." counter(subsection) +} + +h3 > a.a-header:hover::before { + content: "# " counter(section) "." counter(subsection); + text-decoration: none; +} + +.mermaid > svg { + display: block; + margin: auto; +} + +figure { + position: relative; +} + +figure > figcaption { + font-style: italic; +} + +figure > figcaption > a.a-header { + color: var(--grey-base); +} + +figure > figcaption > a.a-header::before { + counter-increment: listing; + content: "Fig " counter(section) "." counter(subsection) "." counter(listing); + color: var(--grey-dark); +} + +figure > figcaption > a.a-header:hover::before { + content: "# Fig " counter(section) "." counter(subsection) "." counter(listing); +} + +figure > figcaption > a.code::before { + content: "Code " counter(section) "." counter(subsection) "." counter(listing); +} + +figure > figcaption > a.code:hover::before { + content: "# Code " counter(section) "." counter(subsection) "." counter(listing); +} + +figure pre { + margin-left: 40px !important; + margin-right: 40px !important; + background: none !important; +} + +a.code > p { display: inline; } + + +code.grammar { + white-space: pre; + margin: 0; + margin-bottom: 1em; + display: block; +} + +blockquote { + border-left: 2px solid var(--blue-dark); + padding-left: 9px; +} + +html { + --toc-width: 0; +} + +.toc { + margin-top: 1.4rem; + color: var(--grey-darker); + font-size: 1rem; + line-height: 130%; + padding: 0; + margin-left: 1rem; +} + +.toc a.toc-link { + color: var(--grey-dark); + text-decoration: none; + line-height: 30px; +} + +.toc a:hover { + opacity: 0.8; +} + +.toc code { + color: var(--grey-light); +} + +.toc p, .toc .a-header-text { + margin: 0; + display: inline; +} + + +.toc ol { + padding-left: 32px; +} + +.toc, .toc ol { + list-style: none; +} + +.toc { + counter-reset: toc-sec toc-subsec toc-fig; + --num-width: 50px; +} + +.toc li::marker { + content: attr(data-index-path) " "; +} + +.toc li { + margin-top: 0; +} + +.toc li + li { + margin-top: 0; +} + + +html { margin: 0; padding: 0; } + +body { + --nav-height: 28px; + --nav-z: 1000; + + margin: 0; padding: 0; +} + +#view { + display: flex; + flex-flow: row nowrap; + justify-content: center; +} + +nav { + position: fixed; + font-size: 14px; + z-index: var(--nav-z); + width: 100vw; +} + +nav { + --slice-height: calc(var(--nav-height)); +} + +.slices { + height: var(--slice-height); + line-height: calc(var(--slice-height) * 0.75); + vertical-align: middle; + display: flex; + padding: 9px; + top: 0; left: 0; +} + +slices.top-right { + left: auto; right: 0; +} + +.slice { + height: 100%; + padding: 4px var(--slice-height); + background: var(--silver-light); + text-align: center; + box-sizing: border-box; +} + +label.slice { + background: transparent; +} + +.slices.top-left .slice { + clip-path: polygon( + var(--slice-height) 0, + 100% 0, + calc(100% - var(--slice-height)) 100%, + 0 100%); + margin-left: calc(-1 * var(--slice-height) + 4px); +} + +.slices.top-left .slice:first-child { + clip-path: polygon( + 0 0, + 100% 0, + calc(100% - var(--slice-height)) 100%, + 0 100%); + margin-left: 0; + padding-left: 9px; +} + +.slices.top-right .slice { + clip-path: polygon( + 0 0, + calc(100% - var(--slice-height)) 0, + 100% 100%, + var(--slice-height) 100%); + margin-right: calc(-1 * var(--slice-height) + 4px); +} + +.slices.top-right .slice:last-child { + clip-path: polygon( + 0 0, + 100% 0, + 100% 100%, + var(--slice-height) 100%); + margin-right: 0; + padding-right: 9px; +} + +nav a[href] { + color: var(--indigo-dark); + text-decoration: none; +} + +nav > a[href]:hover { + background: var(--indigo-lightest); + text-decoration: none; +} + +@media screen and (max-width: 1300px) { + .toc.measured { z-index: -100; opacity: 0; } + .toc-placeholder { display: none; } +} + +h2 { + margin: 3em 0 1em; +} + +header > h2 { + font-style: italic; + margin-top: 1em; +} + +h3 { + margin-top: 2em; +} + +pre > code > ol { + counter-reset: line attr(start); + margin-block-end: -1em; +} + +pre > code { + line-height: 150%; +} + +pre > code > ol > li { + counter-increment: line 1; +} + +pre > code > ol > li.highlight::marker { + color: var(--pink-base); +} + +pre > code > ol > li.lowlight { + filter: grayscale(100%); + opacity: 0.4; +} + +.no-linum pre > code > ol > li::marker { + content: ''; +} + +pre > code > ol > li::marker { + content: counter(line) "\09"; + color: var(--midnight-dark); + font-size: 80%; +} + +code.anatomy { + font-size: 24px; + margin: 1em; + display: block; + --color: var(--orange-base); + --depth: 0; + margin-bottom: 128px; +} + + +.counter-example figure { + background: linear-gradient(-90deg, var(--red-darkest), transparent); +} + +#sidenav { + width: 312px; + height: 100vh; + padding: 0 24px; + border-right: 1px solid #DEE2E7; + overflow-y: auto; + position: sticky; + top: 0; +} + +.apollo-specs-logo a:hover { + text-decoration: none; +} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.graphql b/.graphs/https/specs.apollo.dev/link/v1.0.graphql new file mode 100644 index 0000000..1946070 --- /dev/null +++ b/.graphs/https/specs.apollo.dev/link/v1.0.graphql @@ -0,0 +1,448 @@ +extend schema @id(url: "https://specs.apollo.dev/link/v1.0") + +extend schema + +""" +```raw html + + + +
StatusDraft
Version2.0
+ + +``` + +Core schemas provide tools for linking definitions from different GraphQL schemas together into one. + +```graphql example -- linking a directive from another schema +extend schema + # 👇🏽 schemas are identified by a url + @link(url: "https://internal.example.com/admin") + +type Query { + allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced +} +``` + +```graphql example -- importing a directive from another schema +extend schema + # specific definitions can be imported 👇🏽 + @link(url: "https://internal.example.com/admin", import: ["@adminOnly"]) + +type Query { + allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported +} +``` + +This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc. + +# Renaming {@link} + +It is possible to rename {@link} with the same {@link.as} mechanism used for all links: + +```graphql example -- Renaming {@link} to {@linkOther} +schema + @linkOther(url: "https://specs.apollo.dev/link/v1.0", import: [{name: "@link", as: "@linkOther"}]) + @linkOther(url: "https://example.com/example/v1.0") +{ + query: Query +} + +type SomeType { + field: Int @example +} +``` + +# Prefixing + +With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix: + 1. MUST match the name of the feature as derived from the feature's specification URL, + 2. MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name), and + 3. MUST NOT contain the core namespace separator, which is two underscores ({"__"}), and + 4. MUST NOT end with an underscore (which would create ambiguity between whether {"x___y"} is prefix `x_` for element `y` or prefix `x` for element `_y`). + +Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid [GraphQL name](https://spec.graphql.org/draft/#Name). For instance, the `core` specification (which you are currently reading) introduces an element named [{@link}](#@link), and the `join` specification introduces an element named {@join__field} (among others). + +Note that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like `@feature__24hours`. + +A feature's *root directive* is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the `core` specification introduces a {@link} directive. This directive has the same name as the feature ("`core`"), and so requires no prefix. + +```graphql example -- Using the @link directive without changing the prefix +schema + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@link"]) + @link(url: "https://spec.example.com/example/v1.0") { + query: Query +} + +type User { + name: String @example(data: ITEM) +} + +# An enum used to provide structured data to the example spec. +# It is prefixed with the name of the spec. +enum example__Data { + ITEM +} + +directive @example(data: example__Data) on FIELD_DEFINITION + +directive @link(url: String!, as: String) repeatable on SCHEMA +``` + +The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature's name as a prefix. + +## Elements which must be prefixed + +Feature specs MUST prefix the following schema elements: + - the names of any object types, interfaces, unions, enums, or input object types defined by the feature + - the names of any directives introduced in the spec, with the exception of the *root directive*, which must have the same name as the feature + +:::[example](prefixing.graphql) -- Prefixing + +# Versioning + +VersionTag : "v" Version + +Version : Major "." Minor + +Major : NumericIdentifier + +Minor : NumericIdentifier + +NumericIdentifier : "0" + | PositiveDigit Digit* + +Digit : "0" | PositiveDigit + +PositiveDigit : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +Specs are versioned with a **subset** of a [Semantic Version Number](https://semver.org/spec/v2.0.0.html) containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form `v`{Major}`.`{Minor}, where both integers >= 0. + +```text example -- Valid version tags +v2.2 +v1.0 +v1.1 +v0.1 +``` + +As specified by semver, spec authors SHOULD increment the: + +{++ + +- MAJOR version when you make incompatible API changes, +- MINOR version when you add functionality in a backwards compatible manner + +++} + +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. + +As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. + +## Satisfaction + +Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: + +Satisfies(requested, available) : + 1. If {requested}.{Major} ≠ {available}.{Major}, return {false} + 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} + 3. Return {requested}.{Minor} <= {available}.{Minor} + +## Referencing versions and activating implementations + +Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less-deprecated version with a large major version. + +If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can [satisfy](#sec-Satisfaction) the version required by the document. + + +# Processing Schemas + +```mermaid diagram +graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +``` + +A common use case is that of a processor which consumes a valid input schema and generates an output schema. + +The general guidance for processor behavior is: don't react to what you don't understand. + +Specifically, processors: + - SHOULD pass through {@link} directives which reference unknown feature URLs + - SHOULD pass through prefixed directives, types, and other schema elements + - SHOULD pass through directives which are not [associated with](#AssignFeatures) a {@link} feature + +Processors MAY accept configuration which overrides these default behaviors. + +Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. + +# Validations & Algorithms + +This section lays out algorithms for processing core schemas. + +Algorithms described in this section may produce *validation failures* if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified. + +## Bootstrapping + +Determine the name of the core specification within the document. + +It is possible to [rename the core feature](#sec-Renaming-core-itself) within a document. This process determines the actual name for the core feature if one is present. + +- **Fails** the *Has Schema* validation if there are no SchemaDefinitions in the document +- **Fails** the *Has Core Feature* validation if the `core` feature itself is not referenced with a {@link} directive within the document +- **Fails** the *Bootstrap Core Feature Listed First* validation if the reference to the `core` feature is not the first {@link} directive on the document's SchemaDefinition +- **Fails** the *Core Directive Incorrect Definition* validation if the {@link} directive definition does not *match* the directive as defined by this specification. + +For the purposes of this algorithm, a directive's definition in a schema *matches* a definition provided in this specification if: +- Its arguments have the specified names, types, and default values (or lack thereof) +- It is defined as `repeatable` if and only if the specification's definition defines it as `repeatable` +- The set of locations it belongs to is the same set of locations in the specification's definition. + +The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from *matching*: +- The name of the directive (due to [prefixing](#sec-Prefixing)) +- The order of arguments +- The order of locations +- The directive's description string +- Argument description strings +- Directives applied to argument definitions + +Bootstrap(document) : +1. Let {schema} be the only SchemaDefinition in {document}. (Note that legal GraphQL documents [must include at most one SchemaDefinition](http://spec.graphql.org/draft/#sec-Root-Operation-Types).) + 1. ...if no SchemaDefinitions are present in {document}, the ***Has Schema* validation fails**. +1. For each directive {d} on {schema}, + 1. If {d} has a [`url:`](#@link/feature) argument which [parses as a feature URL](#@link/feature), *and* whose identity is {"https://specs.apollo.dev/core/"} *and* whose version is {"v0.1"}, *and either* {d} has an [`as:`](#@link/as) argument whose value is equal to {d}'s name *or* {d} does not have an [`as:`](#@link/as) argument and {d}'s name is `core`: + - If any directive on {schema} listed before {d} has the same name as {d}, the ***Bootstrap Core Feature Listed First* validation fails**. + - If the definition of the directive {d} does not *match* the [definition of {@link} in this specification](#@link), the ***Core Directive Incorrect Definition* validation fails**. + - Otherwise, **Return** {d}'s name. +- If no matching directive was found, the ***Has Core Feature* validation fails**. + +## Feature Collection + +Collect a map of ({featureName}: `String`) -> `Directive`, where `Directive` is a {@link} Directive which introduces the feature named {featureName} into the document. + +- **Fails** the *Name Uniqueness* validation if feature names are not unique within the document. +- **Fails** *Invalid Feature URL* validation for any invalid feature URLs. + +CollectFeatures(document) : + - Let {coreName} be the name of the core feature found via {Bootstrap(document)} + - Let {features} be a map of {featureName}: `String` -> `Directive`, initially empty. + - For each directive {d} named {coreName} on the SchemaDefinition within {document}, + - Let {specifiedFeatureName} and {version} be the result of parsing {d}'s `url:` argument according to the [specified rules for feature URLs](#@link/feature) + - If the `url:` is not present or fails to parse: + - The ***Invalid Feature URL* validation fails** for {d}, + - Let {featureName} be the {d}'s [`as:`](#@link/as) argument or, if the argument is not present, {specifiedFeatureName} + - If {featureName} exists within {features}, the ***Name Uniqueness* validation fails**. + - Insert {featureName} => {d} into {features} + - **Return** {features} + + +Prefixes, whether implicit or explicit, must be unique within a document. Valid: + +:::[example](prefixing.graphql#schema[0]) -- Unique prefixes + +It is also valid to reference multiple versions of the same spec under different prefixes: + +:::[example](prefix-uniqueness.graphql#schema[0]) -- Explicit prefixes allow multiple versions of the same spec to coexist within a Document + +Without the explicit [`as:`](#@link/as), the above would be invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[1]) -- Non-unique prefixes with multiple versions of the same spec + +Different specs with the same prefix are also invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[2]) -- Different specs with non-unique prefixes + +## Assign Features + +Create a map of {element}: *Any Named Element* -> {feature}: `Directive` | {null}, associating every named schema element within the document with a feature directive, or {null} if it is not associated with a feature. + +AssignFeatures(document) : + - Let {features} be the result of collecting features via {CollectFeatures(document)} + - Let {assignments} be a map of ({element}: *Any Named Element*) -> {feature}: `Directive` | {null}, initally empty + - For each named schema element {e} within the {document} + - Let {name} be the name of the {e} + - If {e} is a Directive and {name} is a key within {features}, + - Insert {e} => {features}`[`{name}`]` into {assignments} + - **Continue** to next {e} + - If {name} begins with {"__"}, + - Insert {e} => {null} into {assignments} + - **Continue** to next {e} + - If {name} contains the substring {"__"}, + - Partition {name} into `[`{prefix}, {base}`]` at the first {"__"} (that is, find the shortest {prefix} and longest {base} such that {name} = {prefix} + {"__"} + {base}) + - If {prefix} exists within {features}, insert {e} => {features}`[`{prefix}`]` into {assignments} + - Else, insert {e} => {null} into {assignments} + - **Continue** to next {e} + - Insert {e} => {null} into {assignments} + - **Return** {assignments} + +## Is In API? + +Determine if any schema element is included in the [API](#sec-Parts-of-a-Core-Schema) described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a [name](https://spec.graphql.org/draft/#Name). + +IsInAPI(element) : + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - If {assignments}`[`{element}`]` is {null}, **Return** {true} + - Else, **Return** {false} + +Note: Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification. + +## Is Affected By Feature? + +Determine if a schema element is *affected* by a given feature. + +IsAffected(element, feature): + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - For each directive {d} on {element}, If {assignments}`[`{d}`]` is {feature}, **Return** {true} + - If {element} is a FieldDefinition, + - Let {parent} be the parent ObjectDefinition or InterfaceDefinition for {element} + - If {IsAffected(parent, feature)}, **Return** {true} + - For each argument type {a} declared on {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for argument {a} + - If {IsAffected(t, feature)}, **Return** {true} + - Let {return} be the ObjectDefinition, InterfaceDefinition, or UnionDefinition for {element}'s return type + - If {IsAffected(return, feature)}, **Return** {true} + - If {element} is an InputDefinition, + - For each InputFieldDefinition {field} within {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for the type of {field} + - If {IsAffected(t, feature)}, **Return** {true} + - If {element} is an EnumDefinition, + - For each EnumValueDefinition {value} in {element}, + - If {IsAffected(value, feature)}, **Return** {true} +""" +schema { + query: Query +} + +"""Link a foreign schema by its URL.""" +directive @link( + """ + The foreign schema's URL. + + Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + + Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + + Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + + ```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + + ``` + + The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + + Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + + ```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + + ``` + + All of these are valid arguments to `url`, and their interpretations: + + | url | normalized url | name | version | + | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | + | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | + | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | + | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | + | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | + | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + + If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + """ + url: String! + """ + Change the [namespace prefix](#sec-Prefixing) assigned to this schema. + + The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). + + By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + + Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). + + ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") + { + query: Query + } + + type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) + } + + # Additional specified schema elements must have their prefixes set + # to the new name. + enum eg__Data { + ITEM + } + + # Name transformation must also be applied to definitions pulled in from + # specifications. + directive @eg(data: eg__Data) on FIELD_DEFINITION + + directive @link(url: String!, as: String) repeatable on SCHEMA + ``` + """ + as: String + """Import definitions into the local namespace.""" + import: [Import] + """ + An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + + By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + + This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + """ + for: Purpose +) repeatable on SCHEMA + +"""TK describe an import""" +scalar Import + +""" +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. +""" +enum Purpose { + """ + `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + + Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + + Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + + Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + + More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + """ + SECURITY + """ + `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + + Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + + Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + """ + EXECUTION +} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.html b/.graphs/https/specs.apollo.dev/link/v1.0.html new file mode 100644 index 0000000..e1373f6 --- /dev/null +++ b/.graphs/https/specs.apollo.dev/link/v1.0.html @@ -0,0 +1,2124 @@ + + + + +link v1.0 + + + + + + + +
+
+

link v1.0

+
+ + + +
StatusDraft
Version2.0
+ + +

Core schemas provide tools for linking definitions from different GraphQL schemas together into one.

+
Example № 1 linking a directive from another schema
extend schema
+  #          👇🏽 schemas are identified by a url
+  @link(url: "https://internal.example.com/admin")
+
+type Query {
+  allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced
+}
+
+
Example № 2 importing a directive from another schema
extend schema
+  #               specific definitions can be imported 👇🏽 
+  @link(url: "https://internal.example.com/admin", import: ["@adminOnly"])
+
+type Query {
+  allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported
+}
+
+

This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc.

+
+ +
+ +
+

2Prefixing

+

With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix:

+
    +
  1. MUST match the name of the feature as derived from the feature’s specification URL,
  2. +
  3. MUST be a valid GraphQL name, and
  4. +
  5. MUST NOT contain the core namespace separator, which is two underscores ("__"), and
  6. +
  7. MUST NOT end with an underscore (which would create ambiguity between whether "x___y" is prefix x_ for element y or prefix x for element _y).
  8. +
+

Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid GraphQL name. For instance, the core specification (which you are currently reading) introduces an element named @link, and the join specification introduces an element named @join__field (among others).

+
+Note +that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like @feature__24hours.
+

A feature’s root directive is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the core specification introduces a @link directive. This directive has the same name as the feature (”core”), and so requires no prefix.

+

The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature’s name as a prefix.

+
+

2.1Elements which must be prefixed

+

Feature specs MUST prefix the following schema elements:

+
    +
  • the names of any object types, interfaces, unions, enums, or input object types defined by the feature
  • +
  • the names of any directives introduced in the spec, with the exception of the root directive, which must have the same name as the feature
  • +
+
Example № 5 Prefixing
schema
+  @link(url: "https://specs.apollo.dev/core/v0.1")
+  @link(url: "https://spec.example.com/foreignA/v1.0")
+  @link(url: "https://spec.example.com/foreignB/v2.0", as: "B") {
+  query: Query
+}
+
+"""
+foreignA__SomeType is a type defined by feature A.
+"""
+type foreignA__SomeType {
+  """
+  nativeField is a field defined by foreignA on a type also defined
+  by foreignA (namely foreignA__SomeType)
+  """
+  nativeField: Int @foreignA__fieldDirective
+}
+
+"""
+foreignA__SomeInput is an input specified by feature A
+"""
+input foreignA__SomeInput {
+  """
+  nativeInputField is defined by foreignA
+  """
+  nativeInputField: Int
+}
+
+"""
+foreignA__Items is specified by feature A
+"""
+enum foreignA__Items { ONE, TWO, THREE @B }
+
+"""
+@B is the root directive defined by foreignB
+
+Root directives are named after their feature
+"""
+directive @B on ENUM_VALUE
+
+"""
+@foreignA__fieldDirective is a non-root (prefixed) directive defined by foreignA
+"""
+directive @foreignA__fieldDirective on FIELD_DEFINITION
+
+directive @link(url: String!, as: String) repeatable on SCHEMA
+
+
+
+
+

3Versioning

+ + + + + + +
+PositiveDigit
1|2|3|4|5|6|7|8|9
+
+

Specs are versioned with a subset of a Semantic Version Number containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form vMajor.Minor, where both integers ≥ 0.

+
Example № 6 Valid version tags
v2.2
+v1.0
+v1.1
+v0.1
+
+

As specified by semver, spec authors SHOULD increment the:

+
    +
  • MAJOR version when you make incompatible API changes,
  • +
  • MINOR version when you add functionality in a backwards compatible manner
  • +
+
+

Patch and pre‐release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch‐level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution.

+

As with semver, the 0.x version series is special: there is no expectation of compatibility between versions 0.x and 0.y. For example, a processor must not activate implementation 0.4 to satisfy a requested version of 0.2.

+
+

3.1Satisfaction

+

Given a version requested by a document and an available version of an implementation, the following algorithm will determine if the available version can satisfy the requested version:

+
+Satisfies(requested, available)
    +
  1. If requested.Majoravailable.Major, return false
  2. +
  3. If requested.Major = 0, return requested.Minor = available.Minor
  4. +
  5. Return requested.Minoravailable.Minor
  6. +
+
+
+
+

3.2Referencing versions and activating implementations

+

Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less‐deprecated version with a large major version.

+

If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can satisfy the version required by the document.

+
+
+
+

4Processing Schemas

+
graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +

A common use case is that of a processor which consumes a valid input schema and generates an output schema.

+

The general guidance for processor behavior is: don’t react to what you don’t understand.

+

Specifically, processors:

+
    +
  • SHOULD pass through @link directives which reference unknown feature URLs
  • +
  • SHOULD pass through prefixed directives, types, and other schema elements
  • +
  • SHOULD pass through directives which are not associated with a @link feature
  • +
+

Processors MAY accept configuration which overrides these default behaviors.

+

Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case‐by‐case basis.

+
+
+

5Validations & Algorithms

+

This section lays out algorithms for processing core schemas.

+

Algorithms described in this section may produce validation failures if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified.

+
+

5.1Bootstrapping

+

Determine the name of the core specification within the document.

+

It is possible to rename the core feature within a document. This process determines the actual name for the core feature if one is present.

+
    +
  • Fails the Has Schema validation if there are no SchemaDefinitions in the document
  • +
  • Fails the Has Core Feature validation if the core feature itself is not referenced with a @link directive within the document
  • +
  • Fails the Bootstrap Core Feature Listed First validation if the reference to the core feature is not the first @link directive on the document’s SchemaDefinition
  • +
  • Fails the Core Directive Incorrect Definition validation if the @link directive definition does not match the directive as defined by this specification.
  • +
+

For the purposes of this algorithm, a directive’s definition in a schema matches a definition provided in this specification if:

+
    +
  • Its arguments have the specified names, types, and default values (or lack thereof)
  • +
  • It is defined as repeatable if and only if the specification’s definition defines it as repeatable
  • +
  • The set of locations it belongs to is the same set of locations in the specification’s definition.
  • +
+

The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from matching:

+
    +
  • The name of the directive (due to prefixing)
  • +
  • The order of arguments
  • +
  • The order of locations
  • +
  • The directive’s description string
  • +
  • Argument description strings
  • +
  • Directives applied to argument definitions
  • +
+
+Bootstrap(document)
    +
  1. Let schema be the only SchemaDefinition in document. (Note that legal GraphQL documents must include at most one SchemaDefinition.)
      +
    1. ...if no SchemaDefinitions are present in document, the Has Schema validation fails.
    2. +
    +
  2. +
  3. For each directive d on schema,
      +
    1. If d has a url: argument which parses as a feature URL, and whose identity is "https://specs.apollo.dev/core/" and whose version is "v0.1", and either d has an as: argument whose value is equal to d‘s name or d does not have an as: argument and d‘s name is core:
        +
      1. If any directive on schema listed before d has the same name as d, the Bootstrap Core Feature Listed First validation fails.
      2. +
      3. If the definition of the directive d does not match the definition of @link in this specification, the Core Directive Incorrect Definition validation fails.
      4. +
      5. Otherwise, Return d‘s name.
      6. +
      +
    2. +
    +
  4. +
  5. If no matching directive was found, the Has Core Feature validation fails.
  6. +
+
+
+
+

5.2Feature Collection

+

Collect a map of (featureName: String) → Directive, where Directive is a @link Directive which introduces the feature named featureName into the document.

+
    +
  • Fails the Name Uniqueness validation if feature names are not unique within the document.
  • +
  • Fails Invalid Feature URL validation for any invalid feature URLs.
  • +
+
+CollectFeatures(document)
    +
  1. Let coreName be the name of the core feature found via Bootstrap(document)
  2. +
  3. Let features be a map of featureName: StringDirective, initially empty.
  4. +
  5. For each directive d named coreName on the SchemaDefinition within document,
      +
    1. Let specifiedFeatureName and version be the result of parsing d‘s url: argument according to the specified rules for feature URLs
    2. +
    3. If the url: is not present or fails to parse:
        +
      1. The Invalid Feature URL validation fails for d,
      2. +
      +
    4. +
    5. Let featureName be the d‘s as: argument or, if the argument is not present, specifiedFeatureName
    6. +
    7. If featureName exists within features, the Name Uniqueness validation fails.
    8. +
    9. Insert featureNamed into features
    10. +
    +
  6. +
  7. Return features
  8. +
+
+

Prefixes, whether implicit or explicit, must be unique within a document. Valid:

+
Example № 7 Unique prefixes
schema
+  @link(url: "https://specs.apollo.dev/core/v0.1")
+  @link(url: "https://spec.example.com/foreignA/v1.0")
+  @link(url: "https://spec.example.com/foreignB/v2.0", as: "B") {
+  query: Query
+}
+

It is also valid to reference multiple versions of the same spec under different prefixes:

+
Example № 8 Explicit prefixes allow multiple versions of the same spec to coexist within a Document
schema
+  @link(url: "https://specs.apollo.dev/core/v0.1")
+  @link(url: "https://specs.example.com/A/1.0")               # name is A
+  @link(url: "https://specs.example.com/A/2.0", as: "A2")     # name is A2
+{
+  query: Query
+}
+

Without the explicit as:, the above would be invalid:

+
Counter Example № 9 Non‐unique prefixes with multiple versions of the same spec
schema
+  @link(url: "https://specs.apollo.dev/core/v0.1")
+  @link(url: "https://specs.example.com/A/1.0") # name is A
+  @link(url: "https://specs.example.com/A/2.0") # name is A
+{
+  query: Query
+}
+

Different specs with the same prefix are also invalid:

+
Counter Example № 10 Different specs with non‐unique prefixes
schema
+  @link(url: "https://specs.apollo.dev/core/v0.1")
+  @link(url: "https://specs.example.com/A/1.0")              # name is A
+  @link(url: "https://www.specs.com/specA/1.1", as: "A")     # name is A
+{
+  query: Query
+}
+
+
+

5.3Assign Features

+

Create a map of element: Any Named Elementfeature: Directive | null, associating every named schema element within the document with a feature directive, or null if it is not associated with a feature.

+
+AssignFeatures(document)
    +
  1. Let features be the result of collecting features via CollectFeatures(document)
  2. +
  3. Let assignments be a map of (element: Any Named Element) → feature: Directive | null, initally empty
  4. +
  5. For each named schema element e within the document
      +
    1. Let name be the name of the e
    2. +
    3. If e is a Directive and name is a key within features,
        +
      1. Insert efeatures[name] into assignments
      2. +
      3. Continue to next e
      4. +
      +
    4. +
    5. If name begins with "__",
        +
      1. Insert enull into assignments
      2. +
      3. Continue to next e
      4. +
      +
    6. +
    7. If name contains the substring "__",
        +
      1. Partition name into [prefix, base] at the first "__" (that is, find the shortest prefix and longest base such that name = prefix + "__" + base)
      2. +
      3. If prefix exists within features, insert efeatures[prefix] into assignments
          +
        1. Else, insert enull into assignments
        2. +
        +
      4. +
      5. Continue to next e
      6. +
      +
    8. +
    9. Insert enull into assignments
    10. +
    +
  6. +
  7. Return assignments
  8. +
+
+
+
+

5.4Is In API?

+

Determine if any schema element is included in the API described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a name.

+
+IsInAPI(element)
    +
  1. Let assignments be the result of assigning features to elements via AssignFeatures(document)
  2. +
  3. If assignments[element] is null, Return true
  4. +
  5. Else, Return false
  6. +
+
+
+Note +Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification.
+
+
+

5.5Is Affected By Feature?

+

Determine if a schema element is affected by a given feature.

+
+IsAffected(element, feature)
    +
  1. Let assignments be the result of assigning features to elements via AssignFeatures(document)
  2. +
  3. For each directive d on element, If assignments[d] is feature, Return true
  4. +
  5. If element is a FieldDefinition,
      +
    1. Let parent be the parent ObjectDefinition or InterfaceDefinition for element
    2. +
    3. If IsAffected(parent, feature), Return true
    4. +
    5. For each argument type a declared on element,
        +
      1. Let t be the InputDefinition, EnumDefinition, or ScalarDefinition for argument a
      2. +
      3. If IsAffected(t, feature), Return true
      4. +
      +
    6. +
    7. Let return be the ObjectDefinition, InterfaceDefinition, or UnionDefinition for element‘s return type
    8. +
    9. If IsAffected(return, feature), Return true
    10. +
    +
  6. +
  7. If element is an InputDefinition,
      +
    1. For each InputFieldDefinition field within element,
        +
      1. Let t be the InputDefinition, EnumDefinition, or ScalarDefinition for the type of field
      2. +
      3. If IsAffected(t, feature), Return true
      4. +
      +
    2. +
    +
  8. +
  9. If element is an EnumDefinition,
  10. +
+
+
    +
  • For each EnumValueDefinition value in element,
  • +
  • If IsAffected(value, feature), Return true
  • +
+
+
+ +
+

7Import

+
scalar Import
+
+describe an import
+
+
+

8Purpose

+
enum Purpose {
+  SECURITY
+  EXECUTION
+}
+

The role of a feature referenced with @link.

+

This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail‐open behavior of core schema consumers is undesirable.

+
+Note +we’ll refer to directives from features which are for: SECURITY or for: EXECUTION as “SECURITY directives” and “EXECUTION directives”, respectively.
+
+

§Index

  1. AssignFeatures
  2. Bootstrap
  3. CollectFeatures
  4. Digit
  5. IsAffected
  6. IsInAPI
  7. Major
  8. Minor
  9. NumericIdentifier
  10. PositiveDigit
  11. Satisfies
  12. Version
  13. VersionTag
+ + +
+ + +
+ + + diff --git a/load.js b/.graphs/https/specs.apollo.dev/load.js similarity index 100% rename from load.js rename to .graphs/https/specs.apollo.dev/load.js diff --git a/main.js b/.graphs/https/specs.apollo.dev/main.js similarity index 100% rename from main.js rename to .graphs/https/specs.apollo.dev/main.js diff --git a/markdown.js b/.graphs/https/specs.apollo.dev/markdown.js similarity index 100% rename from markdown.js rename to .graphs/https/specs.apollo.dev/markdown.js diff --git a/mermaid.js b/.graphs/https/specs.apollo.dev/mermaid.js similarity index 100% rename from mermaid.js rename to .graphs/https/specs.apollo.dev/mermaid.js diff --git a/query.js b/.graphs/https/specs.apollo.dev/query.js similarity index 100% rename from query.js rename to .graphs/https/specs.apollo.dev/query.js diff --git a/rendering.js b/.graphs/https/specs.apollo.dev/rendering.js similarity index 100% rename from rendering.js rename to .graphs/https/specs.apollo.dev/rendering.js diff --git a/toc.js b/.graphs/https/specs.apollo.dev/toc.js similarity index 100% rename from toc.js rename to .graphs/https/specs.apollo.dev/toc.js diff --git a/federation/v2.0.graphql b/federation/v2.0.graphql new file mode 100644 index 0000000..ec8415a --- /dev/null +++ b/federation/v2.0.graphql @@ -0,0 +1,86 @@ +@id(url: "https://specs.apollo.dev/federation/v2.0") + +### `@key` + +""" +The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. + +```graphql example -- using {@key} +type Product @key(fields: "upc") { + upc: UPC! + name: String +} +``` + +Multiple keys can be defined on a single object type: + +```graphql example -- defining multiple {@key}s +type Product @key(fields: "upc") @key(fields: "sku") { + upc: UPC! + sku: SKU! + name: String +} +``` + +Note: Repeated directives (in this case, `@key`, used multiple times) require support by the underlying GraphQL implementation. +""" +directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE + + +### `@provides` + +""" +The `@provides` directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example: + +```graphql example -- using {@provides} +type Review @key(fields: "id") { + product: Product @provides(fields: "name") +} + +extend type Product @key(fields: "upc") { + upc: String @external + name: String @external +} +``` + +When fetching `Review.product` from the Reviews service, it is possible to request the `name` with the expectation that the Reviews service can provide it when going from review to product. `Product.name` is an external field on an external type which is why the local type extension of `Product` and annotation of `name` is required. +""" +directive @provides(fields: FieldSet!) on FIELD_DEFINITION + +### `@requires` + +""" +The `@requires` directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example: + +```graphql example -- using {@requires} +# extended from the Users service +extend type User @key(fields: "id") { + id: ID! @external + email: String @external + reviews: [Review] @requires(fields: "email") +} +``` + +In this case, the Reviews service adds new capabilities to the `User` type by providing a list of `reviews` related to a user. In order to fetch these reviews, the Reviews service needs to know the `email` of the `User` from the Users service in order to look up the reviews. This means the `reviews` field / resolver *requires* the `email` field from the base `User` type. +""" +directive @requires(fields: FieldSet!) on FIELD_DEFINITION + + +### `@external` + +""" +The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example: + +```graphql example -- using {@external} +# extended from the Users service +extend type User @key(fields: "email") { + email: String @external + reviews: [Review] +} +``` + +This type extension in the Reviews service extends the `User` type from the Users service. It extends it for the purpose of adding a new field called `reviews`, which returns a list of `Review`s. +""" +directive @external on FIELD_DEFINITION + +scalar FieldSet \ No newline at end of file diff --git a/graphs.json b/graphs.json new file mode 100644 index 0000000..54e3bb7 --- /dev/null +++ b/graphs.json @@ -0,0 +1,3 @@ +{ + "styles": ["./apollo-colors.css", "./apollo-light.css", "./anatomy.css"] +} \ No newline at end of file diff --git a/inaccessible/coreDirectives.graphql b/inaccessible/coreDirectives.graphql new file mode 100644 index 0000000..3b17e1c --- /dev/null +++ b/inaccessible/coreDirectives.graphql @@ -0,0 +1,5 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.2") + @core(feature: "https://specs.apollo.dev/inaccessible/v0.1") { + query: Query +} \ No newline at end of file diff --git a/inaccessible/inaccessible.spec.graphql b/inaccessible/inaccessible.spec.graphql new file mode 100644 index 0000000..2b2d73f --- /dev/null +++ b/inaccessible/inaccessible.spec.graphql @@ -0,0 +1,124 @@ +@id(url: "https://specs.apollo.dev/inaccessible/v1.0") + +""" +The {@inaccessible} directive removes definitions from a schema's API. + +```raw html + + + +
StatusRelease
Version0.1
+ + +``` + +This document defines a [core feature](https://specs.apollo.dev/core) named `inaccessible` for removing fields and types from a core schema. "Types" will be used throughout this document to refer to Object, Interface, and Union types in GraphQL. + +This specification provides machinery to remove fields and types in a *core schema* which have the `@inaccessible` directive applied. + +# How to read this document + +This document uses [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL. + +# Definitions + +## Processor + +This specification makes references to **Processors**. Processors are described in the [Actors section of the `@core` spec](https://specs.apollo.dev/core/v0.2/#sec-Actors) as an actor which can perform transformations on a core schema. In the case of `@inaccessible`, the Processor will be expected to remove various parts of a core schema. + +# Example: Sensitive User Data + +*This section is non-normative.* + +We'll refer to this example of a core schema with sensitive user data throughout the document: + +:::[example](./schema.graphql) -- Core schema example + +The schema above contains both a field (`User.id`) and type (`BankAccount`) that are marked as `@inaccessible`. These symbols should be omitted from the processed schema anywhere they would appear. When the processed schema below is generated from this core schema, notice what has been removed: +* `User.id` field +* `BankAccount` type +* `User.bankAccount` field (because it _returns_ the `BankAccount` type) +* `Account` union's `BankAccount` type + +:::[example](./processedSchema.graphql) -- Core schema after processing + +# Overview + +*This section is non-normative.* It describes the motivation behind the directives defined by this specification. + +A core schema which has been processed according to the inaccessible spec is a queryable graph, intended to be served by a [Data Core](https://specs.apollo.dev/core/v0.2/#sec-Actors). Various use cases require that fields and types should not be visible to or queried for by clients. The `@inaccessible` directive fulfills this requirement, providing schema authors a mechanism to specify which fields and types should be omitted from the processed schema. + +# Basic Requirements + +Schemas using the `inaccessible` core feature must be valid [core schema documents](https://specs.apollo.dev/core/v0.2) with *@core directives* referencing the `core` specification and this specification. + +Here is an example `@core` usage: + +:::[example](./coreDirectives.graphql) -- required @core directives + +As described in the [core schema specification](https://specs.apollo.dev/core/v0.2/#sec-Prefixing), your schema may prefix the `@inaccessible` directive by including an `as` argument to the `@core` directive which references this specification. All references to `@inaccessible` in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema. + +In order to use the directive described by this specification, GraphQL requires you to include the definition in your schema. + +## Producer Responsibilities + +[Producers](https://specs.apollo.dev/core/v0.2/#sec-Actors) MUST include a definition of the directive compatible with the above definition and all usages in the document. + +## Processor Responsibilities + +The Processor is responsible for removing all inaccessible elements from the schema output. Note in the `InaccessibleRemoval` algorithm below that because a Union can belong to another Union's set of types, the removal of a Union type may have an upwards "cascading" effect, causing other Unions to become candidates for removal. + +# Algorithms + +## Is Inaccessible? + +Return true if the named element {element} is inaccessible. + +IsInaccessible(document, element) : + 1. If {IsMarkedInaccessible(document, element)}, **Return** {true} + 2. If {element} is a FieldDefinition, + 1. Let {parent} be the parent definition for {element} + 2. If {IsMarkedInaccessible(document, parent)}, **Return** {true} + 3. **Return** {false} + +Return true iff the named element {element} is marked as inaccessible. + +IsMarkedInaccessible(document, element) : + 1. Let {assignments} be the result of assigning features via {AssignFeatures(document)} + 2. For each Directive {d} on {element}, + 1. If {assignments}`[`{element}`]` is a Directive whose `feature` argument is this spec, **Return** {true} + 3. **Return** {false} + +## Remove Inaccessible Elements + +Given a schema document, return the set of all schema elements which should be removed in the API. + +RemoveInaccessible(document) : + 1. For each named schema element {e} defined in the {document}, + 1. If {IsInaccessible(document, e)} is {true}, {Remove(document, e)} + +Remove(document, element) : + 1. If {element} is a Field or Input Field, + 1. Delete the definition of {element} in {document} + 2. Let {parent} be the parent type of {f} in {document} + 2. If {parent} has no fields, {Remove(document, parent)} + 2. If {element} is a type: + 1. Delete all definitions and extensions of {element} in {document} + 2. If {element} is an output type (object, interface, union, or scalar): + 1. For each Field {f} in {document} where {f} has a Return Type of {element}, {Remove(document, f)} + 3. If {element} is an input type (input object, enum, or scalar): + 1. For each Field {f} in {document} where {f} has any argument of type {element}, {Remove(document, f)} + 2. For each input object type {t} in {document}, + 1. For each input field {f} of {t} where the type of {f} is {element}, {Remove(document, f)} + 4. If {element} is an Object or Union type, + 2. For each Union {u} in {document} where {element} is a member of {u}, + 1. Delete {element} as member of {u} + 2. If {u} has no members, {Remove(document, u)} + 5. If {element} is an Interface type, + 3. For each Object or Interface type {t} which implements {element} in {document}, + 1. Delete {element} as an interface conformance of {t} +""" +schema { query: Query } + + +directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION \ No newline at end of file diff --git a/inaccessible/processedSchema.graphql b/inaccessible/processedSchema.graphql new file mode 100644 index 0000000..159c330 --- /dev/null +++ b/inaccessible/processedSchema.graphql @@ -0,0 +1,28 @@ +directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA + +enum core__Purpose { + EXECUTION + SECURITY +} + +schema + @core(feature: "https://specs.apollo.dev/core/v0.2") +{ + query: Query +} + +type Query { + user(id: String!): User +} + +type User { + name: String! + email: String! + accounts: [Account] +} + +type ForumAccount { + handle: String! +} + +union Account = ForumAccount \ No newline at end of file diff --git a/inaccessible/schema.graphql b/inaccessible/schema.graphql new file mode 100644 index 0000000..96d9f7f --- /dev/null +++ b/inaccessible/schema.graphql @@ -0,0 +1,38 @@ +directive @link(as: String, url: String!, for: link__Purpose) repeatable on SCHEMA +directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION + +enum link__Purpose { + EXECUTION + SECURITY +} + +schema + @link(url: "https://specs.apollo.dev/link/v0.2") + @link(url: "https://specs.apollo.dev/inaccessible/v0.1", for: SECURITY) +{ + query: Query +} + +type Query { + user(id: String!): User +} + +type User { + id: String! @inaccessible + name: String! + email: String! + bankAccount: BankAccount + accounts: [Account] +} + +type BankAccount @inaccessible { + id: String! + accountNumber: String! +} + +type ForumAccount { + handle: String! +} + +union Account = BankAccount | ForumAccount + diff --git a/inaccessible/spec.md b/inaccessible/spec.md new file mode 100644 index 0000000..55fc482 --- /dev/null +++ b/inaccessible/spec.md @@ -0,0 +1,121 @@ +# Inaccessible + +

for removing fields and types from a core schema

+ +```raw html + + + +
StatusRelease
Version0.1
+ + +``` + +This document defines a [core feature](https://specs.apollo.dev/core) named `inaccessible` for removing fields and types from a core schema. "Types" will be used throughout this document to refer to Object, Interface, and Union types in GraphQL. + +This specification provides machinery to remove fields and types in a *core schema* which have the `@inaccessible` directive applied. + +# How to read this document + +This document uses [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL. + +# Definitions + +## Processor + +This specification makes references to **Processors**. Processors are described in the [Actors section of the `@core` spec](https://specs.apollo.dev/core/v0.2/#sec-Actors) as an actor which can perform transformations on a core schema. In the case of `@inaccessible`, the Processor will be expected to remove various parts of a core schema. + +# Example: Sensitive User Data + +*This section is non-normative.* + +We'll refer to this example of a core schema with sensitive user data throughout the document: + +:::[example](./schema.graphql) -- Core schema example + +The schema above contains both a field (`User.id`) and type (`BankAccount`) that are marked as `@inaccessible`. These symbols should be omitted from the processed schema anywhere they would appear. When the processed schema below is generated from this core schema, notice what has been removed: +* `User.id` field +* `BankAccount` type +* `User.bankAccount` field (because it _returns_ the `BankAccount` type) +* `Account` union's `BankAccount` type + +:::[example](./processedSchema.graphql) -- Core schema after processing + +# Overview + +*This section is non-normative.* It describes the motivation behind the directives defined by this specification. + +A core schema which has been processed according to the inaccessible spec is a queryable graph, intended to be served by a [Data Core](https://specs.apollo.dev/core/v0.2/#sec-Actors). Various use cases require that fields and types should not be visible to or queried for by clients. The `@inaccessible` directive fulfills this requirement, providing schema authors a mechanism to specify which fields and types should be omitted from the processed schema. + +# Basic Requirements + +Schemas using the `inaccessible` core feature must be valid [core schema documents](https://specs.apollo.dev/core/v0.2) with *@core directives* referencing the `core` specification and this specification. + +Here is an example `@core` usage: + +:::[example](./coreDirectives.graphql) -- required @core directives + +As described in the [core schema specification](https://specs.apollo.dev/core/v0.2/#sec-Prefixing), your schema may prefix the `@inaccessible` directive by including an `as` argument to the `@core` directive which references this specification. All references to `@inaccessible` in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema. + +In order to use the directive described by this specification, GraphQL requires you to include the definition in your schema. + +:::[definition](inaccessible.spec.graphql) + +## Producer Responsibilities + +[Producers](https://specs.apollo.dev/core/v0.2/#sec-Actors) MUST include a definition of the directive compatible with the above definition and all usages in the document. + +## Processor Responsibilities + +The Processor is responsible for removing all inaccessible elements from the schema output. Note in the `InaccessibleRemoval` algorithm below that because a Union can belong to another Union's set of types, the removal of a Union type may have an upwards "cascading" effect, causing other Unions to become candidates for removal. + +# Algorithms + +## Is Inaccessible? + +Return true if the named element {element} is inaccessible. + +IsInaccessible(document, element) : + 1. If {IsMarkedInaccessible(document, element)}, **Return** {true} + 2. If {element} is a FieldDefinition, + 1. Let {parent} be the parent definition for {element} + 2. If {IsMarkedInaccessible(document, parent)}, **Return** {true} + 3. **Return** {false} + +Return true iff the named element {element} is marked as inaccessible. + +IsMarkedInaccessible(document, element) : + 1. Let {assignments} be the result of assigning features via {AssignFeatures(document)} + 2. For each Directive {d} on {element}, + 1. If {assignments}`[`{element}`]` is a Directive whose `feature` argument is this spec, **Return** {true} + 3. **Return** {false} + +## Remove Inaccessible Elements + +Given a schema document, return the set of all schema elements which should be removed in the API. + +RemoveInaccessible(document) : + 1. For each named schema element {e} defined in the {document}, + 1. If {IsInaccessible(document, e)} is {true}, {Remove(document, e)} + +Remove(document, element) : + 1. If {element} is a Field or Input Field, + 1. Delete the definition of {element} in {document} + 2. Let {parent} be the parent type of {f} in {document} + 2. If {parent} has no fields, {Remove(document, parent)} + 2. If {element} is a type: + 1. Delete all definitions and extensions of {element} in {document} + 2. If {element} is an output type (object, interface, union, or scalar): + 1. For each Field {f} in {document} where {f} has a Return Type of {element}, {Remove(document, f)} + 3. If {element} is an input type (input object, enum, or scalar): + 1. For each Field {f} in {document} where {f} has any argument of type {element}, {Remove(document, f)} + 2. For each input object type {t} in {document}, + 1. For each input field {f} of {t} where the type of {f} is {element}, {Remove(document, f)} + 4. If {element} is an Object or Union type, + 2. For each Union {u} in {document} where {element} is a member of {u}, + 1. Delete {element} as member of {u} + 2. If {u} has no members, {Remove(document, u)} + 5. If {element} is an Interface type, + 3. For each Object or Interface type {t} which implements {element} in {document}, + 1. Delete {element} as an interface conformance of {t} + diff --git a/link/basic.graphql b/link/basic.graphql new file mode 100644 index 0000000..38e6671 --- /dev/null +++ b/link/basic.graphql @@ -0,0 +1,14 @@ +schema + @link(url: "https://specs.apollo.dev/core/v0.1") + @link(url: "https://specs.example.com/example/v1.0") +{ + query: Query +} + +type Query { + field: Int @example +} + +directive @example on FIELD_DEFINITION + +directive @link(url: String!, as: String) repeatable on SCHEMA diff --git a/link/link-v1.0.graphql b/link/link-v1.0.graphql new file mode 100644 index 0000000..11709de --- /dev/null +++ b/link/link-v1.0.graphql @@ -0,0 +1,456 @@ +@id(url: "https://specs.apollo.dev/link/v1.0") + +""" +```raw html + + + +
StatusDraft
Version2.0
+ + +``` + +Core schemas provide tools for linking definitions from different GraphQL schemas together into one. + +```graphql example -- linking a directive from another schema +extend schema + # 👇🏽 schemas are identified by a url + @link(url: "https://internal.example.com/admin") + +type Query { + allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced +} +``` + +```graphql example -- importing a directive from another schema +extend schema + # specific definitions can be imported 👇🏽 + @link(url: "https://internal.example.com/admin", import: ["@adminOnly"]) + +type Query { + allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported +} +``` + +This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc. + +# Renaming {@link} + +It is possible to rename {@link} with the same {@link.as} mechanism used for all links: + +```graphql example -- Renaming {@link} to {@linkOther} +schema + @linkOther(url: "https://specs.apollo.dev/link/v1.0", import: [{name: "@link", as: "@linkOther"}]) + @linkOther(url: "https://example.com/example/v1.0") +{ + query: Query +} + +type SomeType { + field: Int @example +} +``` + +# Prefixing + +With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix: + 1. MUST match the name of the feature as derived from the feature's specification URL, + 2. MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name), and + 3. MUST NOT contain the core namespace separator, which is two underscores ({"__"}), and + 4. MUST NOT end with an underscore (which would create ambiguity between whether {"x___y"} is prefix `x_` for element `y` or prefix `x` for element `_y`). + +Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid [GraphQL name](https://spec.graphql.org/draft/#Name). For instance, the `core` specification (which you are currently reading) introduces an element named [{@link}](#@link), and the `join` specification introduces an element named {@join__field} (among others). + +Note that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like `@feature__24hours`. + +A feature's *root directive* is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the `core` specification introduces a {@link} directive. This directive has the same name as the feature ("`core`"), and so requires no prefix. + +```graphql example -- Using the @link directive without changing the prefix +schema + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@link"]) + @link(url: "https://spec.example.com/example/v1.0") { + query: Query +} + +type User { + name: String @example(data: ITEM) +} + +# An enum used to provide structured data to the example spec. +# It is prefixed with the name of the spec. +enum example__Data { + ITEM +} + +directive @example(data: example__Data) on FIELD_DEFINITION + +directive @link(url: String!, as: String) repeatable on SCHEMA +``` + +The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature's name as a prefix. + +## Elements which must be prefixed + +Feature specs MUST prefix the following schema elements: + - the names of any object types, interfaces, unions, enums, or input object types defined by the feature + - the names of any directives introduced in the spec, with the exception of the *root directive*, which must have the same name as the feature + +:::[example](prefixing.graphql) -- Prefixing + +# Versioning + +VersionTag : "v" Version + +Version : Major "." Minor + +Major : NumericIdentifier + +Minor : NumericIdentifier + +NumericIdentifier : "0" + | PositiveDigit Digit* + +Digit : "0" | PositiveDigit + +PositiveDigit : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +Specs are versioned with a **subset** of a [Semantic Version Number](https://semver.org/spec/v2.0.0.html) containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form `v`{Major}`.`{Minor}, where both integers >= 0. + +```text example -- Valid version tags +v2.2 +v1.0 +v1.1 +v0.1 +``` + +As specified by semver, spec authors SHOULD increment the: + +{++ + +- MAJOR version when you make incompatible API changes, +- MINOR version when you add functionality in a backwards compatible manner + +++} + +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. + +As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. + +## Satisfaction + +Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: + +Satisfies(requested, available) : + 1. If {requested}.{Major} ≠ {available}.{Major}, return {false} + 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} + 3. Return {requested}.{Minor} <= {available}.{Minor} + +## Referencing versions and activating implementations + +Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less-deprecated version with a large major version. + +If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can [satisfy](#sec-Satisfaction) the version required by the document. + + +# Processing Schemas + +```mermaid diagram +graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +``` + +A common use case is that of a processor which consumes a valid input schema and generates an output schema. + +The general guidance for processor behavior is: don't react to what you don't understand. + +Specifically, processors: + - SHOULD pass through {@link} directives which reference unknown feature URLs + - SHOULD pass through prefixed directives, types, and other schema elements + - SHOULD pass through directives which are not [associated with](#AssignFeatures) a {@link} feature + +Processors MAY accept configuration which overrides these default behaviors. + +Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. + +# Validations & Algorithms + +This section lays out algorithms for processing core schemas. + +Algorithms described in this section may produce *validation failures* if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified. + +## Bootstrapping + +Determine the name of the core specification within the document. + +It is possible to [rename the core feature](#sec-Renaming-core-itself) within a document. This process determines the actual name for the core feature if one is present. + +- **Fails** the *Has Schema* validation if there are no SchemaDefinitions in the document +- **Fails** the *Has Core Feature* validation if the `core` feature itself is not referenced with a {@link} directive within the document +- **Fails** the *Bootstrap Core Feature Listed First* validation if the reference to the `core` feature is not the first {@link} directive on the document's SchemaDefinition +- **Fails** the *Core Directive Incorrect Definition* validation if the {@link} directive definition does not *match* the directive as defined by this specification. + +For the purposes of this algorithm, a directive's definition in a schema *matches* a definition provided in this specification if: +- Its arguments have the specified names, types, and default values (or lack thereof) +- It is defined as `repeatable` if and only if the specification's definition defines it as `repeatable` +- The set of locations it belongs to is the same set of locations in the specification's definition. + +The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from *matching*: +- The name of the directive (due to [prefixing](#sec-Prefixing)) +- The order of arguments +- The order of locations +- The directive's description string +- Argument description strings +- Directives applied to argument definitions + +Bootstrap(document) : +1. Let {schema} be the only SchemaDefinition in {document}. (Note that legal GraphQL documents [must include at most one SchemaDefinition](http://spec.graphql.org/draft/#sec-Root-Operation-Types).) + 1. ...if no SchemaDefinitions are present in {document}, the ***Has Schema* validation fails**. +1. For each directive {d} on {schema}, + 1. If {d} has a [`url:`](#@link/feature) argument which [parses as a feature URL](#@link/feature), *and* whose identity is {"https://specs.apollo.dev/core/"} *and* whose version is {"v0.1"}, *and either* {d} has an [`as:`](#@link/as) argument whose value is equal to {d}'s name *or* {d} does not have an [`as:`](#@link/as) argument and {d}'s name is `core`: + - If any directive on {schema} listed before {d} has the same name as {d}, the ***Bootstrap Core Feature Listed First* validation fails**. + - If the definition of the directive {d} does not *match* the [definition of {@link} in this specification](#@link), the ***Core Directive Incorrect Definition* validation fails**. + - Otherwise, **Return** {d}'s name. +- If no matching directive was found, the ***Has Core Feature* validation fails**. + +## Feature Collection + +Collect a map of ({featureName}: `String`) -> `Directive`, where `Directive` is a {@link} Directive which introduces the feature named {featureName} into the document. + +- **Fails** the *Name Uniqueness* validation if feature names are not unique within the document. +- **Fails** *Invalid Feature URL* validation for any invalid feature URLs. + +CollectFeatures(document) : + - Let {coreName} be the name of the core feature found via {Bootstrap(document)} + - Let {features} be a map of {featureName}: `String` -> `Directive`, initially empty. + - For each directive {d} named {coreName} on the SchemaDefinition within {document}, + - Let {specifiedFeatureName} and {version} be the result of parsing {d}'s `url:` argument according to the [specified rules for feature URLs](#@link/feature) + - If the `url:` is not present or fails to parse: + - The ***Invalid Feature URL* validation fails** for {d}, + - Let {featureName} be the {d}'s [`as:`](#@link/as) argument or, if the argument is not present, {specifiedFeatureName} + - If {featureName} exists within {features}, the ***Name Uniqueness* validation fails**. + - Insert {featureName} => {d} into {features} + - **Return** {features} + + +Prefixes, whether implicit or explicit, must be unique within a document. Valid: + +:::[example](prefixing.graphql#schema[0]) -- Unique prefixes + +It is also valid to reference multiple versions of the same spec under different prefixes: + +:::[example](prefix-uniqueness.graphql#schema[0]) -- Explicit prefixes allow multiple versions of the same spec to coexist within a Document + +Without the explicit [`as:`](#@link/as), the above would be invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[1]) -- Non-unique prefixes with multiple versions of the same spec + +Different specs with the same prefix are also invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[2]) -- Different specs with non-unique prefixes + +## Assign Features + +Create a map of {element}: *Any Named Element* -> {feature}: `Directive` | {null}, associating every named schema element within the document with a feature directive, or {null} if it is not associated with a feature. + +AssignFeatures(document) : + - Let {features} be the result of collecting features via {CollectFeatures(document)} + - Let {assignments} be a map of ({element}: *Any Named Element*) -> {feature}: `Directive` | {null}, initally empty + - For each named schema element {e} within the {document} + - Let {name} be the name of the {e} + - If {e} is a Directive and {name} is a key within {features}, + - Insert {e} => {features}`[`{name}`]` into {assignments} + - **Continue** to next {e} + - If {name} begins with {"__"}, + - Insert {e} => {null} into {assignments} + - **Continue** to next {e} + - If {name} contains the substring {"__"}, + - Partition {name} into `[`{prefix}, {base}`]` at the first {"__"} (that is, find the shortest {prefix} and longest {base} such that {name} = {prefix} + {"__"} + {base}) + - If {prefix} exists within {features}, insert {e} => {features}`[`{prefix}`]` into {assignments} + - Else, insert {e} => {null} into {assignments} + - **Continue** to next {e} + - Insert {e} => {null} into {assignments} + - **Return** {assignments} + +## Is In API? + +Determine if any schema element is included in the [API](#sec-Parts-of-a-Core-Schema) described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a [name](https://spec.graphql.org/draft/#Name). + +IsInAPI(element) : + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - If {assignments}`[`{element}`]` is {null}, **Return** {true} + - Else, **Return** {false} + +Note: Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification. + +## Is Affected By Feature? + +Determine if a schema element is *affected* by a given feature. + +IsAffected(element, feature): + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - For each directive {d} on {element}, If {assignments}`[`{d}`]` is {feature}, **Return** {true} + - If {element} is a FieldDefinition, + - Let {parent} be the parent ObjectDefinition or InterfaceDefinition for {element} + - If {IsAffected(parent, feature)}, **Return** {true} + - For each argument type {a} declared on {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for argument {a} + - If {IsAffected(t, feature)}, **Return** {true} + - Let {return} be the ObjectDefinition, InterfaceDefinition, or UnionDefinition for {element}'s return type + - If {IsAffected(return, feature)}, **Return** {true} + - If {element} is an InputDefinition, + - For each InputFieldDefinition {field} within {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for the type of {field} + - If {IsAffected(t, feature)}, **Return** {true} + - If {element} is an EnumDefinition, + - For each EnumValueDefinition {value} in {element}, + - If {IsAffected(value, feature)}, **Return** {true} +""" +schema +{ query: Query } + + +""" +Link a foreign schema by its URL. +""" +directive @link( + """ + The foreign schema's URL. + + Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + + Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + + Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + + ```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + + ``` + + The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + + Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + + ```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + + ``` + + All of these are valid arguments to `url`, and their interpretations: + + | url | normalized url | name | version | + | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | + | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | + | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | + | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | + | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | + | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + + If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + """ + url: String!, + + """ + Change the [namespace prefix](#sec-Prefixing) assigned to this schema. + + The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). + + By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + + Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). + + ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") + { + query: Query + } + + type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) + } + + # Additional specified schema elements must have their prefixes set + # to the new name. + enum eg__Data { + ITEM + } + + # Name transformation must also be applied to definitions pulled in from + # specifications. + directive @eg(data: eg__Data) on FIELD_DEFINITION + + directive @link(url: String!, as: String) repeatable on SCHEMA + ``` + """ + as: String, + + """ + Import definitions into the local namespace. + """ + import: [Import], + + """ + An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + + By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + + This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + """ + for: Purpose) + repeatable on SCHEMA + +""" +TK describe an import +""" +scalar Import + +""" +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. +""" +enum Purpose { + """ + `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + + Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + + Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + + Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + + More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + + Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + + Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + """ + EXECUTION +} diff --git a/link/prefix-uniqueness.graphql b/link/prefix-uniqueness.graphql new file mode 100644 index 0000000..e065e52 --- /dev/null +++ b/link/prefix-uniqueness.graphql @@ -0,0 +1,25 @@ +schema + @link(url: "https://specs.apollo.dev/core/v0.1") + @link(url: "https://specs.example.com/A/1.0") # name is A + @link(url: "https://specs.example.com/A/2.0", as: "A2") # name is A2 +{ + query: Query +} + +schema + @link(url: "https://specs.apollo.dev/core/v0.1") + @link(url: "https://specs.example.com/A/1.0") # name is A + @link(url: "https://specs.example.com/A/2.0") # name is A +{ + query: Query +} + +schema + @link(url: "https://specs.apollo.dev/core/v0.1") + @link(url: "https://specs.example.com/A/1.0") # name is A + @link(url: "https://www.specs.com/specA/1.1", as: "A") # name is A +{ + query: Query +} + +directive @link(url: String!, as: String) repeatable on SCHEMA diff --git a/link/prefixing.graphql b/link/prefixing.graphql new file mode 100644 index 0000000..11c5229 --- /dev/null +++ b/link/prefixing.graphql @@ -0,0 +1,46 @@ +schema + @link(url: "https://specs.apollo.dev/core/v0.1") + @link(url: "https://spec.example.com/foreignA/v1.0") + @link(url: "https://spec.example.com/foreignB/v2.0", as: "B") { + query: Query +} + +""" +foreignA__SomeType is a type defined by feature A. +""" +type foreignA__SomeType { + """ + nativeField is a field defined by foreignA on a type also defined + by foreignA (namely foreignA__SomeType) + """ + nativeField: Int @foreignA__fieldDirective +} + +""" +foreignA__SomeInput is an input specified by feature A +""" +input foreignA__SomeInput { + """ + nativeInputField is defined by foreignA + """ + nativeInputField: Int +} + +""" +foreignA__Items is specified by feature A +""" +enum foreignA__Items { ONE, TWO, THREE @B } + +""" +@B is the root directive defined by foreignB + +Root directives are named after their feature +""" +directive @B on ENUM_VALUE + +""" +@foreignA__fieldDirective is a non-root (prefixed) directive defined by foreignA +""" +directive @foreignA__fieldDirective on FIELD_DEFINITION + +directive @link(url: String!, as: String) repeatable on SCHEMA diff --git a/netlify.toml b/netlify.toml index 940c346..c5ba547 100644 --- a/netlify.toml +++ b/netlify.toml @@ -3,6 +3,6 @@ [build] # This site has no build step at the moment. We just publish the contents. - publish = "/" + publish = "/.graphs/https/specs.apollo.dev" # Redirects are handled in the ./_redirects file, which has a simple format diff --git a/package-lock.json b/package-lock.json index 7b5c7e3..78a52dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2430 @@ { "name": "@apollo/specs.apollo.dev", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@apollo/specs.apollo.dev", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commonmark": "^0.29.3" + }, + "bin": { + "specs.apollo.dev": "bin/library" + }, + "devDependencies": { + "express": "^4.17.1", + "http-server": "^0.12.3", + "jsdom": "^16.4.0", + "puppeteer": "^5.5.0" + } + }, + "node_modules/@types/node": { + "version": "14.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz", + "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==", + "dev": true, + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commonmark": { + "version": "0.29.3", + "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.3.tgz", + "integrity": "sha512-fvt/NdOFKaL2gyhltSy6BC4LxbbxbnPxBMl923ittqO/JBM0wQHaoYZliE4tp26cRxX/ZZtRsJlZzQrVdUkXAA==", + "dependencies": { + "entities": "~2.0", + "mdurl": "~1.0.1", + "minimist": ">=1.2.2", + "string.prototype.repeat": "^0.2.0" + }, + "bin": { + "commonmark": "bin/commonmark" + }, + "engines": { + "node": "*" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "node_modules/devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecstatic": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", + "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", + "deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.", + "dev": true, + "dependencies": { + "he": "^1.1.1", + "mime": "^1.6.0", + "minimist": "^1.1.0", + "url-join": "^2.0.5" + }, + "bin": { + "ecstatic": "lib/ecstatic.js" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/express/node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", + "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", + "dev": true, + "dependencies": { + "basic-auth": "^1.0.3", + "colors": "^1.4.0", + "corser": "^2.0.1", + "ecstatic": "^3.3.2", + "http-proxy": "^1.18.0", + "minimist": "^1.2.5", + "opener": "^1.5.1", + "portfinder": "^1.0.25", + "secure-compare": "3.0.1", + "union": "~0.5.0" + }, + "bin": { + "hs": "bin/http-server", + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsdom": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "dev": true, + "dependencies": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "dependencies": { + "mime-db": "1.44.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dev": true, + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.5.0.tgz", + "integrity": "sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg==", + "deprecated": "Version no longer supported. Upgrade to @latest", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.19" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.12.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", + "dev": true + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string.prototype.repeat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", + "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", + "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "dependencies": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", + "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", + "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", + "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + }, "dependencies": { "@types/node": { "version": "14.14.10", @@ -1609,11 +4031,6 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, - "string.prototype.repeat": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", - "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1631,6 +4048,11 @@ } } }, + "string.prototype.repeat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", + "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -1869,7 +4291,8 @@ "version": "7.4.0", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index a6b48f6..512d65e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "1.0.0", "description": "Apollo Specifications Library", "main": "index.js", - "type": "module", "bin": "./bin/library", "scripts": { "start": "http-server -c-1" From 5824fd05c472b6d9a6875a75e2a9ec12b51bbb4e Mon Sep 17 00:00:00 2001 From: Ashi Krishnan Date: Wed, 20 Apr 2022 16:53:09 -0400 Subject: [PATCH 02/13] correct id for inaccessible v0.1 --- .../{v1.0.graphql => v0.1.graphql} | 4 +- .../inaccessible/{v1.0.html => v0.1.html} | 6 +- inaccessible/inaccessible.spec.graphql | 2 +- inaccessible/spec.md | 117 ------------------ 4 files changed, 6 insertions(+), 123 deletions(-) rename .graphs/https/specs.apollo.dev/inaccessible/{v1.0.graphql => v0.1.graphql} (97%) rename .graphs/https/specs.apollo.dev/inaccessible/{v1.0.html => v0.1.html} (99%) diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v1.0.graphql b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql similarity index 97% rename from .graphs/https/specs.apollo.dev/inaccessible/v1.0.graphql rename to .graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql index 74310b2..9c3021a 100644 --- a/.graphs/https/specs.apollo.dev/inaccessible/v1.0.graphql +++ b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql @@ -1,6 +1,6 @@ -extend schema @id(url: "https://specs.apollo.dev/inaccessible/v1.0") +extend schema @id(url: "https://specs.apollo.dev/inaccessible/v0.1") -extend schema @id(url: "https://specs.apollo.dev/inaccessible/v1.0") +extend schema @id(url: "https://specs.apollo.dev/inaccessible/v0.1") """ The {@inaccessible} directive removes definitions from a schema's API. diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v1.0.html b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html similarity index 99% rename from .graphs/https/specs.apollo.dev/inaccessible/v1.0.html rename to .graphs/https/specs.apollo.dev/inaccessible/v0.1.html index 7046f85..d99adfc 100644 --- a/.graphs/https/specs.apollo.dev/inaccessible/v1.0.html +++ b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html @@ -2,7 +2,7 @@ -inaccessible v1.0 +inaccessible v0.1 + + + + + + +
+
+

id v1.0

+ +
+
+ + +
+
+ +
    +
    + +
    + + + diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql index ef22d4f..f070896 100644 --- a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql +++ b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql @@ -1,10 +1,8 @@ -extend schema @id(url: "https://specs.apollo.dev/inaccessible/v0.1") +extend schema @link(url: "https://specs.apollo.dev/id/v1.0") @id__(url: "https://specs.apollo.dev/inaccessible/v0.1") -extend schema @id(url: "https://specs.apollo.dev/inaccessible/v0.1") +extend schema @id__(url: "https://specs.apollo.dev/inaccessible/v0.1") """ -# Inaccessible -

    for removing elements from a core schema

    ```raw html @@ -61,4 +59,139 @@ schema { query: Query } -directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION \ No newline at end of file +directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION + +"""Link a foreign schema by its URL.""" +directive @link( + """ + The foreign schema's URL. + + Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + + Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + + Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + + ```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + + ``` + + The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + + Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + + ```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + + ``` + + All of these are valid arguments to `url`, and their interpretations: + + | url | normalized url | name | version | + | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | + | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | + | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | + | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | + | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | + | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + + If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + """ + url: String! + """ + Change the [namespace prefix](#sec-Prefixing) assigned to this schema. + + The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). + + By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + + Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). + + ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") + { + query: Query + } + + type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) + } + + # Additional specified schema elements must have their prefixes set + # to the new name. + enum eg__Data { + ITEM + } + + # Name transformation must also be applied to definitions pulled in from + # specifications. + directive @eg(data: eg__Data) on FIELD_DEFINITION + + directive @link(url: String!, as: String) repeatable on SCHEMA + ``` + """ + as: String + """Import definitions into the local namespace.""" + import: [Import] + """ + An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + + By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + + This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + """ + for: Purpose +) repeatable on SCHEMA + +"""TK describe an import""" +scalar Import + +""" +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. +""" +enum Purpose { + """ + `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + + Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + + Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + + Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + + More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + """ + SECURITY + """ + `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + + Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + + Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + """ + EXECUTION +} + +directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html index 557c6a7..4d5ecc6 100644 --- a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html +++ b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html @@ -1533,46 +1533,44 @@

    inaccessible v0.1

    +
    +

    for removing elements from a core schema

    + + + +
    StatusRelease
    Version0.1
    + + +

    This document defines the @inaccessible directive, which marks schema elements which should not be accessible in the public‐facing schema. This version of the spec supports Object, Interface, and Union types.

    +
    -
    -

    1Inaccessible

    -

    for removing elements from a core schema

    - - - -
    StatusRelease
    Version0.1
    - - -

    This document defines the @inaccessible directive, which marks schema elements which should not be accessible in the public‐facing schema. This version of the spec supports Object, Interface, and Union types.

    -
    -
    -

    2How to read this document

    +
    +

    1How to read this document

    This document uses RFC 2119 guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL.

    -
    -

    3Definitions

    -
    -

    3.1Processor

    +
    +

    2Definitions

    +
    +

    2.1Processor

    This specification makes references to Processors. Processors are described in the Actors section of the @core spec as an actor which can perform transformations on a core schema. In the case of @inaccessible, the Processor will be expected to remove various parts of a core schema.

    -
    -

    4Example: Sensitive User Data

    +
    +

    3Example: Sensitive User Data

    This section is non‐normative.

    We’ll refer to this example of a core schema with sensitive user data throughout the document:

    Example № 1 Core schema example
    directive @link(as: String, url: String!, for: link__Purpose) repeatable on SCHEMA
    @@ -1649,8 +1647,8 @@ 

    } union Account = ForumAccount

    -
    -

    4.1Modifications to the API

    +
    +

    3.1Modifications to the API

    Within the API,

    • Field Definitions marked with @inaccessible MUST be excluded
    • @@ -1672,18 +1670,17 @@

      -
      1. 1Inaccessible
      2. -
      3. 2How to read this document
      4. -
      5. 3Definitions - +
        1. 1How to read this document
        2. +
        3. 2Definitions +
            -
          1. 3.1Processor
          2. +
          3. 2.1Processor
        4. -
        5. 4Example: Sensitive User Data - +
        6. 3Example: Sensitive User Data +
            -
          1. 4.1Modifications to the API
          2. +
          3. 3.1Modifications to the API
        diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.graphql b/.graphs/https/specs.apollo.dev/link/v1.0.graphql index 1946070..da2c8a9 100644 --- a/.graphs/https/specs.apollo.dev/link/v1.0.graphql +++ b/.graphs/https/specs.apollo.dev/link/v1.0.graphql @@ -1,4 +1,4 @@ -extend schema @id(url: "https://specs.apollo.dev/link/v1.0") +extend schema @id__(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/id/v1.0") extend schema @@ -6,7 +6,7 @@ extend schema ```raw html - +
        StatusDraft
        Version2.0
        Version1.0
        @@ -69,7 +69,7 @@ A feature's *root directive* is an exception to the prefixing requirements. Feat ```graphql example -- Using the @link directive without changing the prefix schema - @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@link"]) + @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://spec.example.com/example/v1.0") { query: Query } @@ -95,7 +95,7 @@ The prefix MUST NOT be elided within documentation; definitions of schema elemen Feature specs MUST prefix the following schema elements: - the names of any object types, interfaces, unions, enums, or input object types defined by the feature - - the names of any directives introduced in the spec, with the exception of the *root directive*, which must have the same name as the feature + - the names of any directives introduced in the schema, with the exception of the *root directive*, which must have the same name as the schema :::[example](prefixing.graphql) -- Prefixing @@ -134,7 +134,7 @@ As specified by semver, spec authors SHOULD increment the: ++} -Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of GraphQL schemas, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. @@ -445,4 +445,139 @@ enum Purpose { Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. """ EXECUTION -} \ No newline at end of file +} + +"""Link a foreign schema by its URL.""" +directive @link( + """ + The foreign schema's URL. + + Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + + Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + + Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + + ```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + + ``` + + The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + + Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + + ```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + + ``` + + All of these are valid arguments to `url`, and their interpretations: + + | url | normalized url | name | version | + | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | + | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | + | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | + | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | + | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | + | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + + If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + """ + url: String! + """ + Change the [namespace prefix](#sec-Prefixing) assigned to this schema. + + The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). + + By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + + Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). + + ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") + { + query: Query + } + + type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) + } + + # Additional specified schema elements must have their prefixes set + # to the new name. + enum eg__Data { + ITEM + } + + # Name transformation must also be applied to definitions pulled in from + # specifications. + directive @eg(data: eg__Data) on FIELD_DEFINITION + + directive @link(url: String!, as: String) repeatable on SCHEMA + ``` + """ + as: String + """Import definitions into the local namespace.""" + import: [Import] + """ + An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + + By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + + This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + """ + for: Purpose +) repeatable on SCHEMA + +"""TK describe an import""" +scalar Import + +""" +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. +""" +enum Purpose { + """ + `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + + Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + + Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + + Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + + More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + """ + SECURITY + """ + `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + + Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + + Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + """ + EXECUTION +} + +directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.html b/.graphs/https/specs.apollo.dev/link/v1.0.html index e1373f6..a958a05 100644 --- a/.graphs/https/specs.apollo.dev/link/v1.0.html +++ b/.graphs/https/specs.apollo.dev/link/v1.0.html @@ -1536,7 +1536,7 @@

        link v1.0

        - +
        StatusDraft
        Version2.0
        Version1.0
        @@ -1581,15 +1581,12 @@

        link v1.0

      6. 5.5Is Affected By Feature?
      -
    • 6@link
        -
      1. 6.1url: String!
      2. -
      3. 6.2as: String
      4. -
      5. 6.3import: [Import]
      6. -
      7. 6.4for: Purpose
      8. -
      -
    • +
    • 6@link
    • 7Import
    • 8Purpose
    • +
    • 9@link
    • +
    • 10Import
    • +
    • 11Purpose
    • §Index
    • @@ -1624,7 +1621,7 @@

      MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the core specification introduces a @link directive. This directive has the same name as the feature (”core”), and so requires no prefix.

    + +
    +

    10Import

    +
    scalar Import
    +
    +describe an import
    +
    +
    +

    11Purpose

    +
    enum Purpose {
    +  SECURITY
    +  EXECUTION
    +}
    +

    The role of a feature referenced with @link.

    +

    This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail‐open behavior of core schema consumers is undesirable.

    +
    +Note +we’ll refer to directives from features which are for: SECURITY or for: EXECUTION as “SECURITY directives” and “EXECUTION directives”, respectively.
    +

    §Index

    1. AssignFeatures
    2. Bootstrap
    3. CollectFeatures
    4. Digit
    5. IsAffected
    6. IsInAPI
    7. Major
    8. Minor
    9. NumericIdentifier
    10. PositiveDigit
    11. Satisfies
    12. Version
    13. VersionTag
    @@ -2103,17 +2037,12 @@

    5.5Is Affected By Feature? -
  1. 6@link - -
      -
    1. 6.1url: String!
    2. -
    3. 6.2as: String
    4. -
    5. 6.3import: [Import]
    6. -
    7. 6.4for: Purpose
    8. -
    -
  2. +
  3. 6@link
  4. 7Import
  5. 8Purpose
  6. +
  7. 9@link
  8. +
  9. 10Import
  10. +
  11. 11Purpose
  12. §Index
  13. diff --git a/.graphs/https/specs.apollo.dev/tag/v0.1.graphql b/.graphs/https/specs.apollo.dev/tag/v0.1.graphql new file mode 100644 index 0000000..bd03b6c --- /dev/null +++ b/.graphs/https/specs.apollo.dev/tag/v0.1.graphql @@ -0,0 +1,147 @@ +extend schema @link(url: "https://specs.apollo.dev/id/v1.0") @id__(url: "https://specs.apollo.dev/tag/v0.1") + +extend schema @id__(url: "https://specs.apollo.dev/tag/v0.1") + +""" +Apply an arbitrary piece of string metadata to the target. + +The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses `@tag` names to authorize or require approval for changes to parts of the graph. + +:::[example](ownership-example.graphql) +""" +directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION + +"""Link a foreign schema by its URL.""" +directive @link( + """ + The foreign schema's URL. + + Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + + Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + + Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + + ```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + + ``` + + The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + + Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + + ```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + + ``` + + All of these are valid arguments to `url`, and their interpretations: + + | url | normalized url | name | version | + | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | + | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | + | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | + | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | + | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | + | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + + If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + """ + url: String! + """ + Change the [namespace prefix](#sec-Prefixing) assigned to this schema. + + The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). + + By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + + Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). + + ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") + { + query: Query + } + + type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) + } + + # Additional specified schema elements must have their prefixes set + # to the new name. + enum eg__Data { + ITEM + } + + # Name transformation must also be applied to definitions pulled in from + # specifications. + directive @eg(data: eg__Data) on FIELD_DEFINITION + + directive @link(url: String!, as: String) repeatable on SCHEMA + ``` + """ + as: String + """Import definitions into the local namespace.""" + import: [Import] + """ + An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + + By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + + This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + """ + for: Purpose +) repeatable on SCHEMA + +"""TK describe an import""" +scalar Import + +""" +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. +""" +enum Purpose { + """ + `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + + Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + + Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + + Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + + More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + """ + SECURITY + """ + `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + + Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + + Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + """ + EXECUTION +} + +directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/tag/v0.1.html b/.graphs/https/specs.apollo.dev/tag/v0.1.html new file mode 100644 index 0000000..3da2eae --- /dev/null +++ b/.graphs/https/specs.apollo.dev/tag/v0.1.html @@ -0,0 +1,1597 @@ + + + + +tag v0.1 + + + + + + + +
    +
    +

    tag v0.1

    + +
    +
    +

    1@tag

    +
    directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
    +

    Apply an arbitrary piece of string metadata to the target.

    +

    The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses @tag names to authorize or require approval for changes to parts of the graph.

    +
    Example № 1
    directive @tag(name: String!) repeatable on 
    +  | FIELD_DEFINITION
    +  | INTERFACE
    +  | OBJECT
    +  | UNION
    +
    +schema
    +  @core(feature: "https://specs.apollo.dev/core/v0.2")
    +  @core(feature: "https://specs.apollo.dev/tag/v0.1") {
    +  query: Query
    +}
    +
    +type Query {
    +  customer(id: String!): Customer @tag(name: "team-customers")
    +  employee(id: String!): Employee @tag(name: "team-admin")
    +}
    +
    +interface User @tag(name: "team-accounts") {
    +  id: String!
    +  name: String!
    +}
    +
    +type Customer implements User @tag(name: "team-customers") {
    +  id: String!
    +  name: String!
    +  cart: [Product!] @tag(name: "team-shopping-cart")
    +}
    +
    +type Employee implements User @tag(name: "team-admin") {
    +  id: String!
    +  name: String!
    +  ssn: String!
    +}
    +
    +
    +
    + + +
    + + +
    + + + diff --git a/id-v1.0.graphql b/id-v1.0.graphql new file mode 100644 index 0000000..03b8700 --- /dev/null +++ b/id-v1.0.graphql @@ -0,0 +1,3 @@ +@id(url: "https://specs.apollo.dev/id/v1.0") + +directive @id(url: String!) on SCHEMA \ No newline at end of file diff --git a/inaccessible/inaccessible.spec.graphql b/inaccessible/inaccessible-v0.1.graphql similarity index 99% rename from inaccessible/inaccessible.spec.graphql rename to inaccessible/inaccessible-v0.1.graphql index 6e001de..5f03b6c 100644 --- a/inaccessible/inaccessible.spec.graphql +++ b/inaccessible/inaccessible-v0.1.graphql @@ -1,8 +1,6 @@ @id(url: "https://specs.apollo.dev/inaccessible/v0.1") """ -# Inaccessible -

    for removing elements from a core schema

    ```raw html diff --git a/link/link-v1.0.graphql b/link/link-v1.0.graphql index 11709de..ad96bbc 100644 --- a/link/link-v1.0.graphql +++ b/link/link-v1.0.graphql @@ -4,7 +4,7 @@ ```raw html - +
    StatusDraft
    Version2.0
    Version1.0
    @@ -67,7 +67,7 @@ A feature's *root directive* is an exception to the prefixing requirements. Feat ```graphql example -- Using the @link directive without changing the prefix schema - @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@link"]) + @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://spec.example.com/example/v1.0") { query: Query } @@ -93,7 +93,7 @@ The prefix MUST NOT be elided within documentation; definitions of schema elemen Feature specs MUST prefix the following schema elements: - the names of any object types, interfaces, unions, enums, or input object types defined by the feature - - the names of any directives introduced in the spec, with the exception of the *root directive*, which must have the same name as the feature + - the names of any directives introduced in the schema, with the exception of the *root directive*, which must have the same name as the schema :::[example](prefixing.graphql) -- Prefixing @@ -132,7 +132,7 @@ As specified by semver, spec authors SHOULD increment the: ++} -Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of GraphQL schemas, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. diff --git a/package.json b/package.json index 512d65e..defe6a9 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "index.js", "bin": "./bin/library", "scripts": { + "build": "graphc", "start": "http-server -c-1" }, "keywords": [], diff --git a/tag/ownership-example.graphql b/tag/ownership-example.graphql new file mode 100644 index 0000000..57fdeaf --- /dev/null +++ b/tag/ownership-example.graphql @@ -0,0 +1,33 @@ +directive @tag(name: String!) repeatable on + | FIELD_DEFINITION + | INTERFACE + | OBJECT + | UNION + +schema + @core(feature: "https://specs.apollo.dev/core/v0.2") + @core(feature: "https://specs.apollo.dev/tag/v0.1") { + query: Query +} + +type Query { + customer(id: String!): Customer @tag(name: "team-customers") + employee(id: String!): Employee @tag(name: "team-admin") +} + +interface User @tag(name: "team-accounts") { + id: String! + name: String! +} + +type Customer implements User @tag(name: "team-customers") { + id: String! + name: String! + cart: [Product!] @tag(name: "team-shopping-cart") +} + +type Employee implements User @tag(name: "team-admin") { + id: String! + name: String! + ssn: String! +} diff --git a/tag/spec.md b/tag/spec.md new file mode 100644 index 0000000..3bef628 --- /dev/null +++ b/tag/spec.md @@ -0,0 +1,42 @@ +# Tag + +

    for tagging GraphQL schema elements with names

    + +```raw html + + + +
    StatusRelease
    Version0.1
    + + +``` + +This document defines a [core feature](https://specs.apollo.dev/core) named `tag` for labeling schema elements with arbitrary names (or tags). + +This specification provides machinery to apply arbitrary tags to schema elements via the application of `@tag` directive usages. Tags can be applied to field, object, interface, and union definitions. + +# How to read this document + +This document uses [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL. + +## What this document isn't + +This document specifies only the definition of the `@tag` directive. Tags have a number of useful applications including metadata and schema processing, none of which are specified within this document. + +# Example: Team Ownership Metadata + +The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses `@tag` names to authorize or require approval for changes to parts of the graph. + +:::[example](ownership-example.graphql) + +# Overview + +*This section is non-normative.* It describes the motivation behind the directives defined by this specification. + +The `@tag` directive is, in its simplest form, a mechanism for applying arbitrary string metadata to the fields and types of a schema. This metadata is potentially useful throughout the schema's lifecycle, including, but not limited to, processing, static analysis, and documentation. + +# Basic Requirements + +A schema which implements the `@tag` spec MUST provide a definition which is compatible with the definition below: + +:::[definition](spec.graphql) \ No newline at end of file diff --git a/tag/tag-v0.1.graphql b/tag/tag-v0.1.graphql new file mode 100644 index 0000000..7d95e67 --- /dev/null +++ b/tag/tag-v0.1.graphql @@ -0,0 +1,14 @@ +@id(url: "https://specs.apollo.dev/tag/v0.1") + +""" +Apply an arbitrary piece of string metadata to the target. + +The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses `@tag` names to authorize or require approval for changes to parts of the graph. + +:::[example](ownership-example.graphql) +""" +directive @tag(name: String!) repeatable on + | FIELD_DEFINITION + | INTERFACE + | OBJECT + | UNION From 73d7d17fba3f073985f2aa6b26e54e616aae2f5c Mon Sep 17 00:00:00 2001 From: Ashi Krishnan Date: Fri, 22 Apr 2022 12:17:26 -0400 Subject: [PATCH 05/13] wip --- .graphs/https/foo/foo.graphql | 149 ++ .graphs/https/foo/foo.html | 1562 +++++++++++++++++ .../https/specs.apollo.dev/id/v1.0.graphql | 2 - .../specs.apollo.dev/inaccessible/v0.1.html | 4 +- .../https/specs.apollo.dev/link/v1.0.graphql | 406 +---- .graphs/https/specs.apollo.dev/link/v1.0.html | 553 +----- build | 5 + ...nk-v1.0.graphql => link-v1.0.spec.graphql} | 0 link/scope.md | 63 + link/v1.0/algorithms.md | 132 ++ link/{ => v1.0}/basic.graphql | 0 link/v1.0/core-schemas-intro.md | 23 + link/v1.0/link-v1.0.graphql | 196 +++ link/{ => v1.0}/prefix-uniqueness.graphql | 0 link/{ => v1.0}/prefixing.graphql | 0 link/versioning.md | 54 + package-lock.json | 170 +- package.json | 5 +- tag/ownership-example.graphql | 33 - tag/spec.md | 42 - tag/tag-v0.1.graphql | 14 - tag/v0.1 | 1 + 22 files changed, 2456 insertions(+), 958 deletions(-) create mode 100644 .graphs/https/foo/foo.graphql create mode 100644 .graphs/https/foo/foo.html create mode 100755 build rename link/{link-v1.0.graphql => link-v1.0.spec.graphql} (100%) create mode 100644 link/scope.md create mode 100644 link/v1.0/algorithms.md rename link/{ => v1.0}/basic.graphql (100%) create mode 100644 link/v1.0/core-schemas-intro.md create mode 100644 link/v1.0/link-v1.0.graphql rename link/{ => v1.0}/prefix-uniqueness.graphql (100%) rename link/{ => v1.0}/prefixing.graphql (100%) create mode 100644 link/versioning.md delete mode 100644 tag/ownership-example.graphql delete mode 100644 tag/spec.md delete mode 100644 tag/tag-v0.1.graphql create mode 160000 tag/v0.1 diff --git a/.graphs/https/foo/foo.graphql b/.graphs/https/foo/foo.graphql new file mode 100644 index 0000000..ce10c09 --- /dev/null +++ b/.graphs/https/foo/foo.graphql @@ -0,0 +1,149 @@ +extend schema @link(url: "https://specs.apollo.dev/id/v1.0") + +extend schema @id__(url: "https://foo/foo") + +""" +# hello + +a thing + +[x](./link/core-schemas-intro.md) +""" +schema { + query: Query +} + +"""Link a foreign schema by its URL.""" +directive @link( + """ + The foreign schema's URL. + + Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + + Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + + Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + + ```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + + ``` + + The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + + Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + + ```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + + ``` + + All of these are valid arguments to `url`, and their interpretations: + + | url | normalized url | name | version | + | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | + | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | + | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | + | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | + | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | + | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + + If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + """ + url: String! + """ + Change the [namespace prefix](#sec-Prefixing) assigned to this schema. + + The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). + + By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + + Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). + + ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") + { + query: Query + } + + type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) + } + + # Additional specified schema elements must have their prefixes set + # to the new name. + enum eg__Data { + ITEM + } + + # Name transformation must also be applied to definitions pulled in from + # specifications. + directive @eg(data: eg__Data) on FIELD_DEFINITION + + directive @link(url: String!, as: String) repeatable on SCHEMA + ``` + """ + as: String + """Import definitions into the local namespace.""" + import: [Import] + """ + An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + + By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + + This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + """ + for: Purpose +) repeatable on SCHEMA + +"""TK describe an import""" +scalar Import + +""" +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. +""" +enum Purpose { + """ + `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + + Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + + Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + + Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + + More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + """ + SECURITY + """ + `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + + Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + + Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + """ + EXECUTION +} + +directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/foo/foo.html b/.graphs/https/foo/foo.html new file mode 100644 index 0000000..e854520 --- /dev/null +++ b/.graphs/https/foo/foo.html @@ -0,0 +1,1562 @@ + + + + +foo + + + + + + + +
    +
    +

    foo

    + +
    +
    +

    1hello

    +

    a thing

    +

    x

    +
    +
    + + +
    +
    + +
    1. 1hello
    2. +
    +
    + +
    + + + diff --git a/.graphs/https/specs.apollo.dev/id/v1.0.graphql b/.graphs/https/specs.apollo.dev/id/v1.0.graphql index d7572dc..261d694 100644 --- a/.graphs/https/specs.apollo.dev/id/v1.0.graphql +++ b/.graphs/https/specs.apollo.dev/id/v1.0.graphql @@ -2,6 +2,4 @@ extend schema @id(url: "https://specs.apollo.dev/id/v1.0") extend schema @id(url: "https://specs.apollo.dev/id/v1.0") -directive @id(url: String!) on SCHEMA - directive @id(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html index 4d5ecc6..c35ae64 100644 --- a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html +++ b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html @@ -1560,13 +1560,13 @@

    inaccessible v0.1

    1How to read this document

    -

    This document uses RFC 2119 guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL.

    +

    This document uses RFC 2119 guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL.

    2Definitions

    2.1Processor

    -

    This specification makes references to Processors. Processors are described in the Actors section of the @core spec as an actor which can perform transformations on a core schema. In the case of @inaccessible, the Processor will be expected to remove various parts of a core schema.

    +

    This specification makes references to Processors. Processors are described in the Actors section of the @core spec as an actor which can perform transformations on a core schema. In the case of @inaccessible, the Processor will be expected to remove various parts of a core schema.

    diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.graphql b/.graphs/https/specs.apollo.dev/link/v1.0.graphql index da2c8a9..e04bcfa 100644 --- a/.graphs/https/specs.apollo.dev/link/v1.0.graphql +++ b/.graphs/https/specs.apollo.dev/link/v1.0.graphql @@ -12,147 +12,15 @@ extend schema ``` -Core schemas provide tools for linking definitions from different GraphQL schemas together into one. +[intro to core schemas](core-schemas-intro.md) -```graphql example -- linking a directive from another schema -extend schema - # 👇🏽 schemas are identified by a url - @link(url: "https://internal.example.com/admin") - -type Query { - allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced -} -``` - -```graphql example -- importing a directive from another schema -extend schema - # specific definitions can be imported 👇🏽 - @link(url: "https://internal.example.com/admin", import: ["@adminOnly"]) - -type Query { - allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported -} -``` - -This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc. - -# Renaming {@link} - -It is possible to rename {@link} with the same {@link.as} mechanism used for all links: - -```graphql example -- Renaming {@link} to {@linkOther} -schema - @linkOther(url: "https://specs.apollo.dev/link/v1.0", import: [{name: "@link", as: "@linkOther"}]) - @linkOther(url: "https://example.com/example/v1.0") -{ - query: Query -} - -type SomeType { - field: Int @example -} -``` - -# Prefixing - -With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix: - 1. MUST match the name of the feature as derived from the feature's specification URL, - 2. MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name), and - 3. MUST NOT contain the core namespace separator, which is two underscores ({"__"}), and - 4. MUST NOT end with an underscore (which would create ambiguity between whether {"x___y"} is prefix `x_` for element `y` or prefix `x` for element `_y`). - -Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid [GraphQL name](https://spec.graphql.org/draft/#Name). For instance, the `core` specification (which you are currently reading) introduces an element named [{@link}](#@link), and the `join` specification introduces an element named {@join__field} (among others). - -Note that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like `@feature__24hours`. - -A feature's *root directive* is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the `core` specification introduces a {@link} directive. This directive has the same name as the feature ("`core`"), and so requires no prefix. - -```graphql example -- Using the @link directive without changing the prefix -schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0") { - query: Query -} - -type User { - name: String @example(data: ITEM) -} +# Scoping -# An enum used to provide structured data to the example spec. -# It is prefixed with the name of the spec. -enum example__Data { - ITEM -} - -directive @example(data: example__Data) on FIELD_DEFINITION - -directive @link(url: String!, as: String) repeatable on SCHEMA -``` - -The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature's name as a prefix. - -## Elements which must be prefixed - -Feature specs MUST prefix the following schema elements: - - the names of any object types, interfaces, unions, enums, or input object types defined by the feature - - the names of any directives introduced in the schema, with the exception of the *root directive*, which must have the same name as the schema - -:::[example](prefixing.graphql) -- Prefixing +[scope](../scope.md) # Versioning -VersionTag : "v" Version - -Version : Major "." Minor - -Major : NumericIdentifier - -Minor : NumericIdentifier - -NumericIdentifier : "0" - | PositiveDigit Digit* - -Digit : "0" | PositiveDigit - -PositiveDigit : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" - -Specs are versioned with a **subset** of a [Semantic Version Number](https://semver.org/spec/v2.0.0.html) containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form `v`{Major}`.`{Minor}, where both integers >= 0. - -```text example -- Valid version tags -v2.2 -v1.0 -v1.1 -v0.1 -``` - -As specified by semver, spec authors SHOULD increment the: - -{++ - -- MAJOR version when you make incompatible API changes, -- MINOR version when you add functionality in a backwards compatible manner - -++} - -Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of GraphQL schemas, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. - -As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. - -## Satisfaction - -Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: - -Satisfies(requested, available) : - 1. If {requested}.{Major} ≠ {available}.{Major}, return {false} - 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} - 3. Return {requested}.{Minor} <= {available}.{Minor} - -## Referencing versions and activating implementations - -Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less-deprecated version with a large major version. - -If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can [satisfy](#sec-Satisfaction) the version required by the document. - +[Versioning](../versioning.md) # Processing Schemas @@ -177,138 +45,9 @@ Processors MAY accept configuration which overrides these default behaviors. Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. -# Validations & Algorithms - -This section lays out algorithms for processing core schemas. - -Algorithms described in this section may produce *validation failures* if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified. - -## Bootstrapping - -Determine the name of the core specification within the document. - -It is possible to [rename the core feature](#sec-Renaming-core-itself) within a document. This process determines the actual name for the core feature if one is present. - -- **Fails** the *Has Schema* validation if there are no SchemaDefinitions in the document -- **Fails** the *Has Core Feature* validation if the `core` feature itself is not referenced with a {@link} directive within the document -- **Fails** the *Bootstrap Core Feature Listed First* validation if the reference to the `core` feature is not the first {@link} directive on the document's SchemaDefinition -- **Fails** the *Core Directive Incorrect Definition* validation if the {@link} directive definition does not *match* the directive as defined by this specification. - -For the purposes of this algorithm, a directive's definition in a schema *matches* a definition provided in this specification if: -- Its arguments have the specified names, types, and default values (or lack thereof) -- It is defined as `repeatable` if and only if the specification's definition defines it as `repeatable` -- The set of locations it belongs to is the same set of locations in the specification's definition. - -The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from *matching*: -- The name of the directive (due to [prefixing](#sec-Prefixing)) -- The order of arguments -- The order of locations -- The directive's description string -- Argument description strings -- Directives applied to argument definitions - -Bootstrap(document) : -1. Let {schema} be the only SchemaDefinition in {document}. (Note that legal GraphQL documents [must include at most one SchemaDefinition](http://spec.graphql.org/draft/#sec-Root-Operation-Types).) - 1. ...if no SchemaDefinitions are present in {document}, the ***Has Schema* validation fails**. -1. For each directive {d} on {schema}, - 1. If {d} has a [`url:`](#@link/feature) argument which [parses as a feature URL](#@link/feature), *and* whose identity is {"https://specs.apollo.dev/core/"} *and* whose version is {"v0.1"}, *and either* {d} has an [`as:`](#@link/as) argument whose value is equal to {d}'s name *or* {d} does not have an [`as:`](#@link/as) argument and {d}'s name is `core`: - - If any directive on {schema} listed before {d} has the same name as {d}, the ***Bootstrap Core Feature Listed First* validation fails**. - - If the definition of the directive {d} does not *match* the [definition of {@link} in this specification](#@link), the ***Core Directive Incorrect Definition* validation fails**. - - Otherwise, **Return** {d}'s name. -- If no matching directive was found, the ***Has Core Feature* validation fails**. - -## Feature Collection - -Collect a map of ({featureName}: `String`) -> `Directive`, where `Directive` is a {@link} Directive which introduces the feature named {featureName} into the document. - -- **Fails** the *Name Uniqueness* validation if feature names are not unique within the document. -- **Fails** *Invalid Feature URL* validation for any invalid feature URLs. - -CollectFeatures(document) : - - Let {coreName} be the name of the core feature found via {Bootstrap(document)} - - Let {features} be a map of {featureName}: `String` -> `Directive`, initially empty. - - For each directive {d} named {coreName} on the SchemaDefinition within {document}, - - Let {specifiedFeatureName} and {version} be the result of parsing {d}'s `url:` argument according to the [specified rules for feature URLs](#@link/feature) - - If the `url:` is not present or fails to parse: - - The ***Invalid Feature URL* validation fails** for {d}, - - Let {featureName} be the {d}'s [`as:`](#@link/as) argument or, if the argument is not present, {specifiedFeatureName} - - If {featureName} exists within {features}, the ***Name Uniqueness* validation fails**. - - Insert {featureName} => {d} into {features} - - **Return** {features} - - -Prefixes, whether implicit or explicit, must be unique within a document. Valid: - -:::[example](prefixing.graphql#schema[0]) -- Unique prefixes - -It is also valid to reference multiple versions of the same spec under different prefixes: - -:::[example](prefix-uniqueness.graphql#schema[0]) -- Explicit prefixes allow multiple versions of the same spec to coexist within a Document - -Without the explicit [`as:`](#@link/as), the above would be invalid: - -:::[counter-example](prefix-uniqueness.graphql#schema[1]) -- Non-unique prefixes with multiple versions of the same spec - -Different specs with the same prefix are also invalid: - -:::[counter-example](prefix-uniqueness.graphql#schema[2]) -- Different specs with non-unique prefixes - -## Assign Features - -Create a map of {element}: *Any Named Element* -> {feature}: `Directive` | {null}, associating every named schema element within the document with a feature directive, or {null} if it is not associated with a feature. - -AssignFeatures(document) : - - Let {features} be the result of collecting features via {CollectFeatures(document)} - - Let {assignments} be a map of ({element}: *Any Named Element*) -> {feature}: `Directive` | {null}, initally empty - - For each named schema element {e} within the {document} - - Let {name} be the name of the {e} - - If {e} is a Directive and {name} is a key within {features}, - - Insert {e} => {features}`[`{name}`]` into {assignments} - - **Continue** to next {e} - - If {name} begins with {"__"}, - - Insert {e} => {null} into {assignments} - - **Continue** to next {e} - - If {name} contains the substring {"__"}, - - Partition {name} into `[`{prefix}, {base}`]` at the first {"__"} (that is, find the shortest {prefix} and longest {base} such that {name} = {prefix} + {"__"} + {base}) - - If {prefix} exists within {features}, insert {e} => {features}`[`{prefix}`]` into {assignments} - - Else, insert {e} => {null} into {assignments} - - **Continue** to next {e} - - Insert {e} => {null} into {assignments} - - **Return** {assignments} - -## Is In API? +# Algorithms -Determine if any schema element is included in the [API](#sec-Parts-of-a-Core-Schema) described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a [name](https://spec.graphql.org/draft/#Name). - -IsInAPI(element) : - - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} - - If {assignments}`[`{element}`]` is {null}, **Return** {true} - - Else, **Return** {false} - -Note: Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification. - -## Is Affected By Feature? - -Determine if a schema element is *affected* by a given feature. - -IsAffected(element, feature): - - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} - - For each directive {d} on {element}, If {assignments}`[`{d}`]` is {feature}, **Return** {true} - - If {element} is a FieldDefinition, - - Let {parent} be the parent ObjectDefinition or InterfaceDefinition for {element} - - If {IsAffected(parent, feature)}, **Return** {true} - - For each argument type {a} declared on {element}, - - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for argument {a} - - If {IsAffected(t, feature)}, **Return** {true} - - Let {return} be the ObjectDefinition, InterfaceDefinition, or UnionDefinition for {element}'s return type - - If {IsAffected(return, feature)}, **Return** {true} - - If {element} is an InputDefinition, - - For each InputFieldDefinition {field} within {element}, - - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for the type of {field} - - If {IsAffected(t, feature)}, **Return** {true} - - If {element} is an EnumDefinition, - - For each EnumValueDefinition {value} in {element}, - - If {IsAffected(value, feature)}, **Return** {true} +[Algorithms](algorithms.md) """ schema { query: Query @@ -447,137 +186,4 @@ enum Purpose { EXECUTION } -"""Link a foreign schema by its URL.""" -directive @link( - """ - The foreign schema's URL. - - Link URLs serve two main purposes: - - Providing a unique identifier for the foreign schema - - Directing human readers to documentation about the foreign schema - - Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. - - Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): - - ```html diagram -- Basic anatomy of a link URL - - https://spec.example.com/a/b/c/mySchema/v1.0 - - ``` - - The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). - - Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. - - ```html diagram -- Ignoring meaningless parts of a URL - - https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag - - ``` - - All of these are valid arguments to `url`, and their interpretations: - - | url | normalized url | name | version | - | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | - | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | - | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | - | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | - | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | - | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | - - If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. - """ - url: String! - """ - Change the [namespace prefix](#sec-Prefixing) assigned to this schema. - - The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). - - By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. - - Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). - - ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0", as: "eg") - { - query: Query - } - - type User { - # Specifying `as: "eg"` transforms @example into @eg - name: String @eg(data: ITEM) - } - - # Additional specified schema elements must have their prefixes set - # to the new name. - enum eg__Data { - ITEM - } - - # Name transformation must also be applied to definitions pulled in from - # specifications. - directive @eg(data: eg__Data) on FIELD_DEFINITION - - directive @link(url: String!, as: String) repeatable on SCHEMA - ``` - """ - as: String - """Import definitions into the local namespace.""" - import: [Import] - """ - An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. - - By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. - - This behavior is different for {@link}s with a specified purpose: - - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it - - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema - """ - for: Purpose -) repeatable on SCHEMA - -"""TK describe an import""" -scalar Import - -""" -The role of a feature referenced with {@link}. - -This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. - -Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. -""" -enum Purpose { - """ - `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. - - Security-conscious consumers MUST NOT serve a field if: - - the schema definition has **any** unsupported SECURITY directives, - - the field's parent type definition has **any** unsupported SECURITY directives, - - the field's return type definition has **any** unsupported SECURITY directives, or - - the field definition has **any** unsupported SECURITY directives - - Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. - - Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. - - More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. - """ - SECURITY - """ - `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. - - Consumers MUST NOT serve a field if: - - the schema's definition has **any** unsupported EXECUTION directives, - - the field's parent type definition has **any** unsupported EXECUTION directives, - - the field's return type definition has **any** unsupported EXECUTION directives, or - - the field definition has **any** unsupported EXECUTION directives - - Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. - """ - EXECUTION -} - directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.html b/.graphs/https/specs.apollo.dev/link/v1.0.html index a958a05..4dcf9f7 100644 --- a/.graphs/https/specs.apollo.dev/link/v1.0.html +++ b/.graphs/https/specs.apollo.dev/link/v1.0.html @@ -1533,445 +1533,120 @@

    link v1.0

    -
    - - - -
    StatusDraft
    Version1.0
    - - -

    Core schemas provide tools for linking definitions from different GraphQL schemas together into one.

    -
    Example № 1 linking a directive from another schema
    extend schema
    -  #          👇🏽 schemas are identified by a url
    -  @link(url: "https://internal.example.com/admin")
    -
    -type Query {
    -  allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced
    -}
    -
    -
    Example № 2 importing a directive from another schema
    extend schema
    -  #               specific definitions can be imported 👇🏽 
    -  @link(url: "https://internal.example.com/admin", import: ["@adminOnly"])
    -
    -type Query {
    -  allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported
    -}
    -
    -

    This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc.

    -
    -
    +
    diff --git a/build b/build new file mode 100755 index 0000000..a4b9d56 --- /dev/null +++ b/build @@ -0,0 +1,5 @@ +#!/bin/sh + +npx @queerviolet/speck tag/v0.1/tag-v0.1.md > tag/v0.1/index.html +npx @queerviolet/speck core/v0.1/core-v0.1.md > core/v0.1/index.html +npx @queerviolet/speck core/v0.2/core-v0.2.md > core/v0.2/index.html \ No newline at end of file diff --git a/link/link-v1.0.graphql b/link/link-v1.0.spec.graphql similarity index 100% rename from link/link-v1.0.graphql rename to link/link-v1.0.spec.graphql diff --git a/link/scope.md b/link/scope.md new file mode 100644 index 0000000..e844e5e --- /dev/null +++ b/link/scope.md @@ -0,0 +1,63 @@ + +# Renaming link + +It is possible to rename {@link} with the same {@link.as} mechanism used for all links: + +```graphql example -- Renaming {@link} to {@linkOther} +schema + @linkOther(url: "https://specs.apollo.dev/link/v1.0", import: [{name: "@link", as: "@linkOther"}]) + @linkOther(url: "https://example.com/example/v1.0") +{ + query: Query +} + +type SomeType { + field: Int @example +} +``` + +# Prefixing + +With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix: + 1. MUST match the name of the feature as derived from the feature's specification URL, + 2. MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name), and + 3. MUST NOT contain the core namespace separator, which is two underscores ({"__"}), and + 4. MUST NOT end with an underscore (which would create ambiguity between whether {"x___y"} is prefix `x_` for element `y` or prefix `x` for element `_y`). + +Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid [GraphQL name](https://spec.graphql.org/draft/#Name). For instance, the `core` specification (which you are currently reading) introduces an element named [{@link}](#@link), and the `join` specification introduces an element named {@join__field} (among others). + +Note that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like `@feature__24hours`. + +A feature's *root directive* is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the `core` specification introduces a {@link} directive. This directive has the same name as the feature ("`core`"), and so requires no prefix. + +```graphql example -- Using the @link directive without changing the prefix +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0") { + query: Query +} + +type User { + name: String @example(data: ITEM) +} + +# An enum used to provide structured data to the example spec. +# It is prefixed with the name of the spec. +enum example__Data { + ITEM +} + +directive @example(data: example__Data) on FIELD_DEFINITION + +directive @link(url: String!, as: String) repeatable on SCHEMA +``` + +The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature's name as a prefix. + +## Elements which must be prefixed + +Feature specs MUST prefix the following schema elements: + - the names of any object types, interfaces, unions, enums, or input object types defined by the feature + - the names of any directives introduced in the schema, with the exception of the *root directive*, which must have the same name as the schema + +:::[example](prefixing.graphql) -- Prefixing diff --git a/link/v1.0/algorithms.md b/link/v1.0/algorithms.md new file mode 100644 index 0000000..1df3ffc --- /dev/null +++ b/link/v1.0/algorithms.md @@ -0,0 +1,132 @@ +# Validations & Algorithms + +This section lays out algorithms for processing core schemas. + +Algorithms described in this section may produce *validation failures* if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified. + +## Bootstrapping + +Determine the name of the core specification within the document. + +It is possible to [rename the core feature](#sec-Renaming-core-itself) within a document. This process determines the actual name for the core feature if one is present. + +- **Fails** the *Has Schema* validation if there are no SchemaDefinitions in the document +- **Fails** the *Has Core Feature* validation if the `core` feature itself is not referenced with a {@link} directive within the document +- **Fails** the *Bootstrap Core Feature Listed First* validation if the reference to the `core` feature is not the first {@link} directive on the document's SchemaDefinition +- **Fails** the *Core Directive Incorrect Definition* validation if the {@link} directive definition does not *match* the directive as defined by this specification. + +For the purposes of this algorithm, a directive's definition in a schema *matches* a definition provided in this specification if: +- Its arguments have the specified names, types, and default values (or lack thereof) +- It is defined as `repeatable` if and only if the specification's definition defines it as `repeatable` +- The set of locations it belongs to is the same set of locations in the specification's definition. + +The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from *matching*: +- The name of the directive (due to [prefixing](#sec-Prefixing)) +- The order of arguments +- The order of locations +- The directive's description string +- Argument description strings +- Directives applied to argument definitions + +Bootstrap(document) : +1. Let {schema} be the only SchemaDefinition in {document}. (Note that legal GraphQL documents [must include at most one SchemaDefinition](http://spec.graphql.org/draft/#sec-Root-Operation-Types).) + 1. ...if no SchemaDefinitions are present in {document}, the ***Has Schema* validation fails**. +1. For each directive {d} on {schema}, + 1. If {d} has a [`url:`](#@link/feature) argument which [parses as a feature URL](#@link/feature), *and* whose identity is {"https://specs.apollo.dev/core/"} *and* whose version is {"v0.1"}, *and either* {d} has an [`as:`](#@link/as) argument whose value is equal to {d}'s name *or* {d} does not have an [`as:`](#@link/as) argument and {d}'s name is `core`: + - If any directive on {schema} listed before {d} has the same name as {d}, the ***Bootstrap Core Feature Listed First* validation fails**. + - If the definition of the directive {d} does not *match* the [definition of {@link} in this specification](#@link), the ***Core Directive Incorrect Definition* validation fails**. + - Otherwise, **Return** {d}'s name. +- If no matching directive was found, the ***Has Core Feature* validation fails**. + +## Feature Collection + +Collect a map of ({featureName}: `String`) -> `Directive`, where `Directive` is a {@link} Directive which introduces the feature named {featureName} into the document. + +- **Fails** the *Name Uniqueness* validation if feature names are not unique within the document. +- **Fails** *Invalid Feature URL* validation for any invalid feature URLs. + +CollectFeatures(document) : + - Let {coreName} be the name of the core feature found via {Bootstrap(document)} + - Let {features} be a map of {featureName}: `String` -> `Directive`, initially empty. + - For each directive {d} named {coreName} on the SchemaDefinition within {document}, + - Let {specifiedFeatureName} and {version} be the result of parsing {d}'s `url:` argument according to the [specified rules for feature URLs](#@link/feature) + - If the `url:` is not present or fails to parse: + - The ***Invalid Feature URL* validation fails** for {d}, + - Let {featureName} be the {d}'s [`as:`](#@link/as) argument or, if the argument is not present, {specifiedFeatureName} + - If {featureName} exists within {features}, the ***Name Uniqueness* validation fails**. + - Insert {featureName} => {d} into {features} + - **Return** {features} + + +Prefixes, whether implicit or explicit, must be unique within a document. Valid: + +:::[example](prefixing.graphql#schema[0]) -- Unique prefixes + +It is also valid to reference multiple versions of the same spec under different prefixes: + +:::[example](prefix-uniqueness.graphql#schema[0]) -- Explicit prefixes allow multiple versions of the same spec to coexist within a Document + +Without the explicit [`as:`](#@link/as), the above would be invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[1]) -- Non-unique prefixes with multiple versions of the same spec + +Different specs with the same prefix are also invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[2]) -- Different specs with non-unique prefixes + +## Assign Features + +Create a map of {element}: *Any Named Element* -> {feature}: `Directive` | {null}, associating every named schema element within the document with a feature directive, or {null} if it is not associated with a feature. + +AssignFeatures(document) : + - Let {features} be the result of collecting features via {CollectFeatures(document)} + - Let {assignments} be a map of ({element}: *Any Named Element*) -> {feature}: `Directive` | {null}, initally empty + - For each named schema element {e} within the {document} + - Let {name} be the name of the {e} + - If {e} is a Directive and {name} is a key within {features}, + - Insert {e} => {features}`[`{name}`]` into {assignments} + - **Continue** to next {e} + - If {name} begins with {"__"}, + - Insert {e} => {null} into {assignments} + - **Continue** to next {e} + - If {name} contains the substring {"__"}, + - Partition {name} into `[`{prefix}, {base}`]` at the first {"__"} (that is, find the shortest {prefix} and longest {base} such that {name} = {prefix} + {"__"} + {base}) + - If {prefix} exists within {features}, insert {e} => {features}`[`{prefix}`]` into {assignments} + - Else, insert {e} => {null} into {assignments} + - **Continue** to next {e} + - Insert {e} => {null} into {assignments} + - **Return** {assignments} + +## Is In API? + +Determine if any schema element is included in the [API](#sec-Parts-of-a-Core-Schema) described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a [name](https://spec.graphql.org/draft/#Name). + +IsInAPI(element) : + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - If {assignments}`[`{element}`]` is {null}, **Return** {true} + - Else, **Return** {false} + +Note: Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification. + +## Is Affected By Feature? + +Determine if a schema element is *affected* by a given feature. + +IsAffected(element, feature): + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - For each directive {d} on {element}, If {assignments}`[`{d}`]` is {feature}, **Return** {true} + - If {element} is a FieldDefinition, + - Let {parent} be the parent ObjectDefinition or InterfaceDefinition for {element} + - If {IsAffected(parent, feature)}, **Return** {true} + - For each argument type {a} declared on {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for argument {a} + - If {IsAffected(t, feature)}, **Return** {true} + - Let {return} be the ObjectDefinition, InterfaceDefinition, or UnionDefinition for {element}'s return type + - If {IsAffected(return, feature)}, **Return** {true} + - If {element} is an InputDefinition, + - For each InputFieldDefinition {field} within {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for the type of {field} + - If {IsAffected(t, feature)}, **Return** {true} + - If {element} is an EnumDefinition, + - For each EnumValueDefinition {value} in {element}, + - If {IsAffected(value, feature)}, **Return** {true} \ No newline at end of file diff --git a/link/basic.graphql b/link/v1.0/basic.graphql similarity index 100% rename from link/basic.graphql rename to link/v1.0/basic.graphql diff --git a/link/v1.0/core-schemas-intro.md b/link/v1.0/core-schemas-intro.md new file mode 100644 index 0000000..da37b5e --- /dev/null +++ b/link/v1.0/core-schemas-intro.md @@ -0,0 +1,23 @@ +Core schemas provide tools for linking definitions from different GraphQL schemas together into one. + +```graphql example -- linking a directive from another schema +extend schema + # 👇🏽 schemas are identified by a url + @link(url: "https://internal.example.com/admin") + +type Query { + allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced +} +``` + +```graphql example -- importing a directive from another schema +extend schema + # specific definitions can be imported 👇🏽 + @link(url: "https://internal.example.com/admin", import: ["@adminOnly"]) + +type Query { + allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported +} +``` + +This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc. diff --git a/link/v1.0/link-v1.0.graphql b/link/v1.0/link-v1.0.graphql new file mode 100644 index 0000000..f8de95e --- /dev/null +++ b/link/v1.0/link-v1.0.graphql @@ -0,0 +1,196 @@ +@id(url: "https://specs.apollo.dev/link/v1.0") + +""" +```raw html + + + +
    StatusDraft
    Version1.0
    + + +``` + +[intro to core schemas](core-schemas-intro.md) + +# Scoping + +[scope](../scope.md) + +# Versioning + +[Versioning](../versioning.md) + +# Processing Schemas + +```mermaid diagram +graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +``` + +A common use case is that of a processor which consumes a valid input schema and generates an output schema. + +The general guidance for processor behavior is: don't react to what you don't understand. + +Specifically, processors: + - SHOULD pass through {@link} directives which reference unknown feature URLs + - SHOULD pass through prefixed directives, types, and other schema elements + - SHOULD pass through directives which are not [associated with](#AssignFeatures) a {@link} feature + +Processors MAY accept configuration which overrides these default behaviors. + +Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. + +# Algorithms + +[Algorithms](algorithms.md) + +""" +schema +{ query: Query } + + +""" +Link a foreign schema by its URL. +""" +directive @link( + """ + The foreign schema's URL. + + Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + + Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + + Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + + ```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + + ``` + + The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + + Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + + ```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + + ``` + + All of these are valid arguments to `url`, and their interpretations: + + | url | normalized url | name | version | + | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | + | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | + | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | + | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | + | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | + | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + + If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + """ + url: String!, + + """ + Change the [namespace prefix](#sec-Prefixing) assigned to this schema. + + The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). + + By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + + Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). + + ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name + schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") + { + query: Query + } + + type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) + } + + # Additional specified schema elements must have their prefixes set + # to the new name. + enum eg__Data { + ITEM + } + + # Name transformation must also be applied to definitions pulled in from + # specifications. + directive @eg(data: eg__Data) on FIELD_DEFINITION + + directive @link(url: String!, as: String) repeatable on SCHEMA + ``` + """ + as: String, + + """ + Import definitions into the local namespace. + """ + import: [Import], + + """ + An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + + By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + + This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + """ + for: Purpose) + repeatable on SCHEMA + +""" +TK describe an import +""" +scalar Import + +""" +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. +""" +enum Purpose { + """ + `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + + Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + + Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + + Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + + More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + + Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + + Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + """ + EXECUTION +} diff --git a/link/prefix-uniqueness.graphql b/link/v1.0/prefix-uniqueness.graphql similarity index 100% rename from link/prefix-uniqueness.graphql rename to link/v1.0/prefix-uniqueness.graphql diff --git a/link/prefixing.graphql b/link/v1.0/prefixing.graphql similarity index 100% rename from link/prefixing.graphql rename to link/v1.0/prefixing.graphql diff --git a/link/versioning.md b/link/versioning.md new file mode 100644 index 0000000..3967380 --- /dev/null +++ b/link/versioning.md @@ -0,0 +1,54 @@ + +# Versioning + +VersionTag : "v" Version + +Version : Major "." Minor + +Major : NumericIdentifier + +Minor : NumericIdentifier + +NumericIdentifier : "0" + | PositiveDigit Digit* + +Digit : "0" | PositiveDigit + +PositiveDigit : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +Specs are versioned with a **subset** of a [Semantic Version Number](https://semver.org/spec/v2.0.0.html) containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form `v`{Major}`.`{Minor}, where both integers >= 0. + +```text example -- Valid version tags +v2.2 +v1.0 +v1.1 +v0.1 +``` + +As specified by semver, spec authors SHOULD increment the: + +{++ + +- MAJOR version when you make incompatible API changes, +- MINOR version when you add functionality in a backwards compatible manner + +++} + +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of GraphQL schemas, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. + +As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. + +## Satisfaction + +Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: + +Satisfies(requested, available) : + 1. If {requested}.{Major} ≠ {available}.{Major}, return {false} + 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} + 3. Return {requested}.{Minor} <= {available}.{Minor} + +## Referencing versions and activating implementations + +Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less-deprecated version with a large major version. + +If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can [satisfy](#sec-Satisfaction) the version required by the document. diff --git a/package-lock.json b/package-lock.json index 78a52dc..8e57ce7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,12 +15,32 @@ "specs.apollo.dev": "bin/library" }, "devDependencies": { + "@queerviolet/speck": "queerviolet/speck", "express": "^4.17.1", "http-server": "^0.12.3", "jsdom": "^16.4.0", "puppeteer": "^5.5.0" } }, + "node_modules/@queerviolet/speck": { + "version": "2.0.2", + "resolved": "git+ssh://git@github.com/queerviolet/speck.git#c34a337ff933613b590b01681d44608b4696044b", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "graphql": "^15.5.0", + "pegjs": "~0.8.0", + "prismjs": "^1.23.0", + "terser": "5.5.1" + }, + "bin": { + "spec-md": "bin/spec-md" + }, + "engines": { + "node": ">=10.14.2" + } + }, "node_modules/@types/node": { "version": "14.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz", @@ -322,6 +342,12 @@ "node": "*" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -364,6 +390,12 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/commonmark": { "version": "0.29.3", "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.3.tgz", @@ -992,6 +1024,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/graphql": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", + "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1599,6 +1640,18 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, + "node_modules/pegjs": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.8.0.tgz", + "integrity": "sha1-l28GfaE+XFsVAcAXklZoolOBFWE=", + "dev": true, + "bin": { + "pegjs": "bin/pegjs" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -1646,6 +1699,15 @@ "node": ">= 0.8.0" } }, + "node_modules/prismjs": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz", + "integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -2013,11 +2075,20 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "optional": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -2129,6 +2200,32 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz", + "integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -2426,6 +2523,17 @@ } }, "dependencies": { + "@queerviolet/speck": { + "version": "git+ssh://git@github.com/queerviolet/speck.git#c34a337ff933613b590b01681d44608b4696044b", + "dev": true, + "from": "@queerviolet/speck@queerviolet/speck", + "requires": { + "graphql": "^15.5.0", + "pegjs": "~0.8.0", + "prismjs": "^1.23.0", + "terser": "5.5.1" + } + }, "@types/node": { "version": "14.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz", @@ -2666,6 +2774,12 @@ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -2699,6 +2813,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "commonmark": { "version": "0.29.3", "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.3.tgz", @@ -3201,6 +3321,12 @@ "path-is-absolute": "^1.0.0" } }, + "graphql": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", + "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3663,6 +3789,12 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, + "pegjs": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.8.0.tgz", + "integrity": "sha1-l28GfaE+XFsVAcAXklZoolOBFWE=", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -3701,6 +3833,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prismjs": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz", + "integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==", + "dev": true + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -3999,8 +4137,17 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "optional": true + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } }, "sshpk": { "version": "1.16.1", @@ -4084,6 +4231,25 @@ "readable-stream": "^3.1.1" } }, + "terser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz", + "integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/package.json b/package.json index defe6a9..f9a8c24 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "bin": "./bin/library", "scripts": { - "build": "graphc", + "build": "./build", "start": "http-server -c-1" }, "keywords": [], @@ -15,7 +15,8 @@ "express": "^4.17.1", "http-server": "^0.12.3", "jsdom": "^16.4.0", - "puppeteer": "^5.5.0" + "puppeteer": "^5.5.0", + "@queerviolet/speck": "queerviolet/speck" }, "dependencies": { "commonmark": "^0.29.3" diff --git a/tag/ownership-example.graphql b/tag/ownership-example.graphql deleted file mode 100644 index 57fdeaf..0000000 --- a/tag/ownership-example.graphql +++ /dev/null @@ -1,33 +0,0 @@ -directive @tag(name: String!) repeatable on - | FIELD_DEFINITION - | INTERFACE - | OBJECT - | UNION - -schema - @core(feature: "https://specs.apollo.dev/core/v0.2") - @core(feature: "https://specs.apollo.dev/tag/v0.1") { - query: Query -} - -type Query { - customer(id: String!): Customer @tag(name: "team-customers") - employee(id: String!): Employee @tag(name: "team-admin") -} - -interface User @tag(name: "team-accounts") { - id: String! - name: String! -} - -type Customer implements User @tag(name: "team-customers") { - id: String! - name: String! - cart: [Product!] @tag(name: "team-shopping-cart") -} - -type Employee implements User @tag(name: "team-admin") { - id: String! - name: String! - ssn: String! -} diff --git a/tag/spec.md b/tag/spec.md deleted file mode 100644 index 3bef628..0000000 --- a/tag/spec.md +++ /dev/null @@ -1,42 +0,0 @@ -# Tag - -

    for tagging GraphQL schema elements with names

    - -```raw html - - - -
    StatusRelease
    Version0.1
    - - -``` - -This document defines a [core feature](https://specs.apollo.dev/core) named `tag` for labeling schema elements with arbitrary names (or tags). - -This specification provides machinery to apply arbitrary tags to schema elements via the application of `@tag` directive usages. Tags can be applied to field, object, interface, and union definitions. - -# How to read this document - -This document uses [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL. - -## What this document isn't - -This document specifies only the definition of the `@tag` directive. Tags have a number of useful applications including metadata and schema processing, none of which are specified within this document. - -# Example: Team Ownership Metadata - -The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses `@tag` names to authorize or require approval for changes to parts of the graph. - -:::[example](ownership-example.graphql) - -# Overview - -*This section is non-normative.* It describes the motivation behind the directives defined by this specification. - -The `@tag` directive is, in its simplest form, a mechanism for applying arbitrary string metadata to the fields and types of a schema. This metadata is potentially useful throughout the schema's lifecycle, including, but not limited to, processing, static analysis, and documentation. - -# Basic Requirements - -A schema which implements the `@tag` spec MUST provide a definition which is compatible with the definition below: - -:::[definition](spec.graphql) \ No newline at end of file diff --git a/tag/tag-v0.1.graphql b/tag/tag-v0.1.graphql deleted file mode 100644 index 7d95e67..0000000 --- a/tag/tag-v0.1.graphql +++ /dev/null @@ -1,14 +0,0 @@ -@id(url: "https://specs.apollo.dev/tag/v0.1") - -""" -Apply an arbitrary piece of string metadata to the target. - -The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses `@tag` names to authorize or require approval for changes to parts of the graph. - -:::[example](ownership-example.graphql) -""" -directive @tag(name: String!) repeatable on - | FIELD_DEFINITION - | INTERFACE - | OBJECT - | UNION diff --git a/tag/v0.1 b/tag/v0.1 new file mode 160000 index 0000000..7499bee --- /dev/null +++ b/tag/v0.1 @@ -0,0 +1 @@ +Subproject commit 7499bee6709a10da5aaad8949fc9a8cd4bd1d57e From faaf8dda87888fcc0ccd66e4d138cf772929b4d9 Mon Sep 17 00:00:00 2001 From: Ashi Krishnan Date: Sat, 23 Apr 2022 05:24:24 -0400 Subject: [PATCH 06/13] Draft of link v1.0 --- .graphs/https/foo/foo.graphql | 149 -- .graphs/https/foo/foo.html | 1562 -------------- .graphs/https/specs.apollo.dev/anatomy.css | 56 - .../https/specs.apollo.dev/apollo-colors.css | 101 - .../https/specs.apollo.dev/apollo-light.css | 324 --- .graphs/https/specs.apollo.dev/async.js | 37 - .graphs/https/specs.apollo.dev/boot.js | 49 - .../specs.apollo.dev/federation/v2.0.graphql | 213 -- .../https/specs.apollo.dev/id/v1.0.graphql | 5 - .graphs/https/specs.apollo.dev/id/v1.0.html | 1555 -------------- .../inaccessible/v0.1.graphql | 197 -- .graphs/https/specs.apollo.dev/index.html | 200 -- .graphs/https/specs.apollo.dev/inject-logo.js | 40 - .graphs/https/specs.apollo.dev/light.css | 610 ------ .../https/specs.apollo.dev/link/v1.0.graphql | 189 -- .graphs/https/specs.apollo.dev/link/v1.0.html | 1684 --------------- .graphs/https/specs.apollo.dev/load.js | 53 - .graphs/https/specs.apollo.dev/main.js | 17 - .graphs/https/specs.apollo.dev/markdown.js | 87 - .graphs/https/specs.apollo.dev/mermaid.js | 31 - .graphs/https/specs.apollo.dev/query.js | 13 - .graphs/https/specs.apollo.dev/rendering.js | 55 - .../https/specs.apollo.dev/tag/v0.1.graphql | 147 -- .graphs/https/specs.apollo.dev/tag/v0.1.html | 1597 --------------- .graphs/https/specs.apollo.dev/toc.js | 65 - build | 11 +- .../{v2.0.graphql => v2.0/federation-v2.0.md} | 86 +- .../v2.0.html => federation/v2.0/index.html | 563 +---- inaccessible/spec.md | 4 - inaccessible/v0.1/.gitignore | 5 + inaccessible/v0.1/README.md | 32 + .../{ => v0.1}/coreDirectives.graphql | 0 .../inaccessible-v0.1.md} | 38 +- inaccessible/v0.1/inaccessible.spec.graphql | 1 + .../v0.1.html => inaccessible/v0.1/index.html | 590 +----- inaccessible/v0.1/netlify.toml | 6 + inaccessible/v0.1/package.json | 19 + .../{ => v0.1}/processedSchema.graphql | 0 inaccessible/{ => v0.1}/schema.graphql | 10 +- link/{v1.0 => }/prefixing.graphql | 0 link/scope.md | 5 +- link/v1.0/index.html | 1809 +++++++++++++++++ link/v1.0/link-v1.0.graphql | 196 -- link/v1.0/link-v1.0.md | 647 ++++++ link/versioning.md | 4 +- netlify.toml | 2 +- package-lock.json | 6 +- package.json | 4 +- 48 files changed, 2764 insertions(+), 10310 deletions(-) delete mode 100644 .graphs/https/foo/foo.graphql delete mode 100644 .graphs/https/foo/foo.html delete mode 100644 .graphs/https/specs.apollo.dev/anatomy.css delete mode 100644 .graphs/https/specs.apollo.dev/apollo-colors.css delete mode 100644 .graphs/https/specs.apollo.dev/apollo-light.css delete mode 100644 .graphs/https/specs.apollo.dev/async.js delete mode 100644 .graphs/https/specs.apollo.dev/boot.js delete mode 100644 .graphs/https/specs.apollo.dev/federation/v2.0.graphql delete mode 100644 .graphs/https/specs.apollo.dev/id/v1.0.graphql delete mode 100644 .graphs/https/specs.apollo.dev/id/v1.0.html delete mode 100644 .graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql delete mode 100644 .graphs/https/specs.apollo.dev/index.html delete mode 100644 .graphs/https/specs.apollo.dev/inject-logo.js delete mode 100644 .graphs/https/specs.apollo.dev/light.css delete mode 100644 .graphs/https/specs.apollo.dev/link/v1.0.graphql delete mode 100644 .graphs/https/specs.apollo.dev/link/v1.0.html delete mode 100644 .graphs/https/specs.apollo.dev/load.js delete mode 100644 .graphs/https/specs.apollo.dev/main.js delete mode 100644 .graphs/https/specs.apollo.dev/markdown.js delete mode 100644 .graphs/https/specs.apollo.dev/mermaid.js delete mode 100644 .graphs/https/specs.apollo.dev/query.js delete mode 100644 .graphs/https/specs.apollo.dev/rendering.js delete mode 100644 .graphs/https/specs.apollo.dev/tag/v0.1.graphql delete mode 100644 .graphs/https/specs.apollo.dev/tag/v0.1.html delete mode 100644 .graphs/https/specs.apollo.dev/toc.js rename federation/{v2.0.graphql => v2.0/federation-v2.0.md} (72%) rename .graphs/https/specs.apollo.dev/federation/v2.0.html => federation/v2.0/index.html (71%) delete mode 100644 inaccessible/spec.md create mode 100644 inaccessible/v0.1/.gitignore create mode 100644 inaccessible/v0.1/README.md rename inaccessible/{ => v0.1}/coreDirectives.graphql (100%) rename inaccessible/{inaccessible-v0.1.graphql => v0.1/inaccessible-v0.1.md} (61%) create mode 100644 inaccessible/v0.1/inaccessible.spec.graphql rename .graphs/https/specs.apollo.dev/inaccessible/v0.1.html => inaccessible/v0.1/index.html (69%) create mode 100644 inaccessible/v0.1/netlify.toml create mode 100644 inaccessible/v0.1/package.json rename inaccessible/{ => v0.1}/processedSchema.graphql (100%) rename inaccessible/{ => v0.1}/schema.graphql (60%) rename link/{v1.0 => }/prefixing.graphql (100%) create mode 100644 link/v1.0/index.html delete mode 100644 link/v1.0/link-v1.0.graphql create mode 100644 link/v1.0/link-v1.0.md diff --git a/.graphs/https/foo/foo.graphql b/.graphs/https/foo/foo.graphql deleted file mode 100644 index ce10c09..0000000 --- a/.graphs/https/foo/foo.graphql +++ /dev/null @@ -1,149 +0,0 @@ -extend schema @link(url: "https://specs.apollo.dev/id/v1.0") - -extend schema @id__(url: "https://foo/foo") - -""" -# hello - -a thing - -[x](./link/core-schemas-intro.md) -""" -schema { - query: Query -} - -"""Link a foreign schema by its URL.""" -directive @link( - """ - The foreign schema's URL. - - Link URLs serve two main purposes: - - Providing a unique identifier for the foreign schema - - Directing human readers to documentation about the foreign schema - - Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. - - Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): - - ```html diagram -- Basic anatomy of a link URL - - https://spec.example.com/a/b/c/mySchema/v1.0 - - ``` - - The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). - - Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. - - ```html diagram -- Ignoring meaningless parts of a URL - - https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag - - ``` - - All of these are valid arguments to `url`, and their interpretations: - - | url | normalized url | name | version | - | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | - | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | - | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | - | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | - | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | - | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | - - If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. - """ - url: String! - """ - Change the [namespace prefix](#sec-Prefixing) assigned to this schema. - - The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). - - By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. - - Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). - - ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0", as: "eg") - { - query: Query - } - - type User { - # Specifying `as: "eg"` transforms @example into @eg - name: String @eg(data: ITEM) - } - - # Additional specified schema elements must have their prefixes set - # to the new name. - enum eg__Data { - ITEM - } - - # Name transformation must also be applied to definitions pulled in from - # specifications. - directive @eg(data: eg__Data) on FIELD_DEFINITION - - directive @link(url: String!, as: String) repeatable on SCHEMA - ``` - """ - as: String - """Import definitions into the local namespace.""" - import: [Import] - """ - An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. - - By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. - - This behavior is different for {@link}s with a specified purpose: - - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it - - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema - """ - for: Purpose -) repeatable on SCHEMA - -"""TK describe an import""" -scalar Import - -""" -The role of a feature referenced with {@link}. - -This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. - -Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. -""" -enum Purpose { - """ - `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. - - Security-conscious consumers MUST NOT serve a field if: - - the schema definition has **any** unsupported SECURITY directives, - - the field's parent type definition has **any** unsupported SECURITY directives, - - the field's return type definition has **any** unsupported SECURITY directives, or - - the field definition has **any** unsupported SECURITY directives - - Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. - - Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. - - More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. - """ - SECURITY - """ - `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. - - Consumers MUST NOT serve a field if: - - the schema's definition has **any** unsupported EXECUTION directives, - - the field's parent type definition has **any** unsupported EXECUTION directives, - - the field's return type definition has **any** unsupported EXECUTION directives, or - - the field definition has **any** unsupported EXECUTION directives - - Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. - """ - EXECUTION -} - -directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/foo/foo.html b/.graphs/https/foo/foo.html deleted file mode 100644 index e854520..0000000 --- a/.graphs/https/foo/foo.html +++ /dev/null @@ -1,1562 +0,0 @@ - - - - -foo - - - - - - - -
    -
    -

    foo

    - -
    -
    -

    1hello

    -

    a thing

    -

    x

    -
    -
    - - -
    -
    - -
    1. 1hello
    2. -
    -
    - -
    - - - diff --git a/.graphs/https/specs.apollo.dev/anatomy.css b/.graphs/https/specs.apollo.dev/anatomy.css deleted file mode 100644 index 395f659..0000000 --- a/.graphs/https/specs.apollo.dev/anatomy.css +++ /dev/null @@ -1,56 +0,0 @@ -@media (min-width: 720px) { - code.anatomy { - font-size: 110%; - } -} - -code.anatomy { - white-space: inherit; -} - -code.anatomy span { - position: relative; - color: var(--color); -} - -.red { - --color: var(--red-base); -} - -.blue { - --color: var(--blue-base); -} - -.pink { - --color: var(--pink-base); -} - -.yellow { - --color: var(--yellow-base); -} - -.green { - --color: var(--green-base); -} - -.grey { - --color: var(--grey-base); -} - -code.anatomy span > aside { - position: absolute; - top: 100%; - left: 0; - font-size: 50%; - width: calc(100% - 8px); - display: block; - border-bottom: 1px solid var(--color); - border-left: 1px solid var(--color); - padding: 4px; - height: calc(20% + 10px); -} - -code.anatomy span > span > aside { - height: calc(20% + 5px); -} - diff --git a/.graphs/https/specs.apollo.dev/apollo-colors.css b/.graphs/https/specs.apollo.dev/apollo-colors.css deleted file mode 100644 index 2793da8..0000000 --- a/.graphs/https/specs.apollo.dev/apollo-colors.css +++ /dev/null @@ -1,101 +0,0 @@ -html { - /** Brand colors **/ - --pink-darkest: rgb(102, 31, 78); - --pink-darker: rgb(131, 35, 99); - --pink-dark: rgb(196, 57, 151); - --pink-base: rgb(242, 92, 193); - --pink-light: rgb(255, 163, 224); - --pink-lighter: rgb(255, 212, 241); - --pink-lightest: rgb(255, 230, 247); - --teal-darkest: rgb(31, 102, 100); - --teal-darker: rgb(29, 123, 120); - --teal-dark: rgb(38, 162, 157); - --teal-base: rgb(65, 217, 211); - --teal-light: rgb(139, 246, 242); - --teal-lighter: rgb(198, 255, 253); - --teal-lightest: rgb(230, 255, 254); - --indigo-darkest: rgb(45, 31, 102); - --indigo-darker: rgb(49, 28, 135); - --indigo-dark: rgb(63, 32, 186); - --indigo-base: rgb(113, 86, 217); - --indigo-light: rgb(173, 155, 246); - --indigo-lighter: rgb(217, 207, 255); - --indigo-lightest: rgb(235, 230, 255); - - /** Neutrals **/ - --black-darker: rgb(18, 21, 26); - --black-dark: rgb(20, 23, 28); - --black-base: rgb(25, 28, 35); - --black-light: rgb(34, 38, 46); - --black-lighter: rgb(47, 53, 63); - --grey-darker: rgb(66, 72, 85); - --grey-dark: rgb(90, 98, 112); - --grey-base: rgb(119, 127, 142); - --grey-light: rgb(149, 157, 170); - --grey-lighter: rgb(178, 185, 195); - --silver-darker: rgb(202, 208, 216); - --silver-dark: rgb(222, 226, 231); - --silver-base: rgb(235, 238, 240); - --silver-light: rgb(244, 246, 248); - --silver-lighter: rgb(252, 253, 255); - - /** Interface Colors **/ - --red-darkest: rgb(102, 31, 31); - --red-darker: rgb(120, 28, 28); - --red-dark: rgb(156, 35, 35); - --red-base: rgb(209, 59, 59); - --red-light: rgb(241, 134, 134); - --red-lighter: rgb(255, 195, 195); - --red-lightest: rgb(255, 230, 230); - --green-darkest: rgb(20, 94, 51); - --green-darker: rgb(19, 108, 56); - --green-dark: rgb(28, 132, 72); - --green-base: rgb(54, 173, 104); - --green-light: rgb(126, 217, 164); - --green-lighter: rgb(190, 244, 213); - --green-lightest: rgb(230, 255, 240); - --blue-darkest: rgb(22, 60, 102); - --blue-darker: rgb(15, 65, 122); - --blue-dark: rgb(16, 83, 160); - --blue-base: rgb(32, 117, 214); - --blue-light: rgb(116, 176, 244); - --blue-lighter: rgb(187, 219, 255); - --blue-lightest: rgb(240, 247, 255); - - /** Alternate Colors **/ - --orange-darkest: rgb(102, 63, 31); - --orange-darker: rgb(136, 76, 30); - --orange-dark: rgb(180, 102, 38); - --orange-base: rgb(245, 145, 64); - --orange-light: rgb(255, 193, 143); - --orange-lighter: rgb(255, 226, 202); - --orange-lightest: rgb(255, 241, 230); - --yellow-darkest: rgb(102, 80, 31); - --yellow-darker: rgb(132, 103, 29); - --yellow-dark: rgb(180, 143, 37); - --yellow-base: rgb(244, 208, 63); - --yellow-light: rgb(255, 232, 142); - --yellow-lighter: rgb(255, 244, 202); - --yellow-lightest: rgb(255, 250, 230); - --purple-darkest: rgb(66, 22, 102); - --purple-darker: rgb(82, 21, 132); - --purple-dark: rgb(113, 30, 180); - --purple-base: rgb(162, 61, 245); - --purple-light: rgb(205, 143, 255); - --purple-lighter: rgb(232, 204, 255); - --purple-lightest: rgb(244, 230, 255); - --blilet-darkest: rgb(27, 34, 64); - --blilet-darker: rgb(37, 46, 80); - --blilet-dark: rgb(60, 74, 133); - --blilet-base: rgb(81, 104, 194); - --blilet-light: rgb(122, 146, 240); - --blilet-lighter: rgb(176, 190, 247); - --blilet-lightest: rgb(230, 235, 255); - --midnight-darkest: rgb(6, 15, 47); - --midnight-darker: rgb(27, 34, 64); - --midnight-dark: rgb(56, 61, 91); - --midnight-base: rgb(61, 75, 106); - --midnight-light: rgb(86, 105, 146); - --midnight-lighter: rgb(121, 143, 187); - --midnight-lightest: rgb(180, 195, 219); -} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/apollo-light.css b/.graphs/https/specs.apollo.dev/apollo-light.css deleted file mode 100644 index 9752f31..0000000 --- a/.graphs/https/specs.apollo.dev/apollo-light.css +++ /dev/null @@ -1,324 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,400;0,700;1,300;1,700&display=swap'); - -@import "apollo-colors.css"; -@import "anatomy.css"; - -:root { - --body-font-family: Source Sans Pro,sans-serif; - background: var(--body-background-color); - color: var(--body-text-color); - --body-background-color: #FFF; - --body-text-color: var(--grey-dark); - --button-background-color: var(--grey-darker); - --highlight-border-color: var(--yellow-base); - --highlight-background: none; -} - - -/*** Header ***/ -header > h1 { - color: var(--black-lighter); - text-align: center; - font-size: 250%; - margin-bottom: 0; - margin-top: 1em !important; -} - -header h2 { - font-size: 150%; - font-style: italic; - color: var(--black-lighter); - margin-top: 0; - text-align: center; -} - -.spec-data { - margin: auto; -} - -.spec-data td { - border: 0; - color: var(--grey-dark); -} - -.spec-data td:first-of-type { - color: var(--grey-dark); - font-style: italic; - text-align: right; -} - -.spec-data td:first-of-type::after { - content: ": "; -} - -/*** Headers & Text ***/ - -h1,h2,h3,h4,h5,h6 { - color: var(--black-lighter); -} - -h1 { - margin-top: 2rem; -} - -h2 { - margin-top: 2rem; -} - -a { - color: var(--indigo-dark); -} - -.spec-note { - background: none; - border-color: var(--yellow-base); -} - -.spec-note > a:first-of-type { - color: var(--yellow-dark); -} - -/*** Table of Contents ***/ -/***** Inline TOC ******/ -.spec-secid a { - color: var(--indigo-dark); -} - -/***** Sidebar ******/ -.spec-sidebar { - background: none; - backdrop-filter: blur(25px); - box-shadow: none !important; - left: 0; -} - -@media (min-width: 1220px) { - .spec-sidebar { - backdrop-filter: none; - } -} - -article { - display: table; - margin: auto; - box-sizing: border-box; - padding-left: 1em; - padding-right: 1em; - margin-left: auto; - margin-right: auto; -} - -p { - max-width: calc(100vw - 2em); -} - -figure { - max-width: calc(100vw - 2em); -} - -@media (min-width: 720px) { - article { - margin-left: auto; - } -} - -@media (min-width: 1220px) { - article { - padding-left: calc(50vw - var(--sidebar-width) + 5rem); - margin-left: auto; - margin-right: calc(50vw - var(--sidebar-width)); - } -} - -@media (min-width: 1500px) { - article { - padding-left: calc(50vw - var(--sidebar-width)); - } -} - -body { - padding: 0 !important; - margin: 0 !important; -} - -.spec-sidebar .viewing > a::after { - color: var(--yellow-base) -} - -/*** Figures ***/ -figure { - border-color: transparent; -} - -.spec-example { - border-color: var(--indigo-lighter); -} - -.spec-counter-example { - border-color: var(--red-light); -} - -.spec-counter-example > figcaption > a { - color: var(--red-light); -} - -.spec-example > figcaption > a { - color: var(--indigo-light); -} - -.spec-example > pre { - overflow-y: auto; - background: none; -} - -.spec-example-num { - opacity: 1; -} - -/*** Code ***/ -var { - border: thin solid transparent; -} - -var:hover { - background: none; - border-color: var(--highlight-border-color); -} - -code { - padding: .1em .3em; - border-radius: .3em; - font-size: .9em; - color: var(--pink-base); - background: var(--silver-base); -} - -.token.operator { - background: none; -} - -.spec-definition > pre { - padding: 1rem; -} - -.spec-definition > pre > code { - font-family: var(--body-font-family); - font-style: italic; - font-size: 130%; - font-weight: bold; - line-height: 120%; -} - -@media (min-width: 720px) { - .spec-definition > pre { - margin-left: 3em; - transform: scaleX(1.02); - } -} - -h2.spec-definition-head, h3.spec-definition-head { - font-size: inherit; - color: var(--black-darker); -} - - -/*** Selections ***/ -::selection { - background-color: var(--yellow-light); -} - -.selection-link, -.outdated-selection-link { - box-shadow: 2px 2px 5px var(--body-background-color); - color: var(--indigo-dark); -} - -.selection-link { - background-color: var(--indigo-lightest); - border: thin solid var(--indigo-dark); -} - -.selection-link:hover { - background-color: var(--indigo-lightest); - color: var(--indigo-dark); -} - -.outdated-selection-link { - background-color: var(--midnight-base); - border: thin solid var(--red-dark); -} - -.outdated-selection-link:hover { - background-color: var(--red-base); - color: var(--midnight-dark); -} - - -/*** Sidebar ***/ -.spec-sidebar-toggle + label[for="spec-sidebar-toggle"] > .spec-sidebar-button { - color: var(--grey-lighter); - z-index: 10001; - left: 0; - right: auto; - top: auto; - bottom: 0; - border-radius: 50%; - backdrop-filter: blur(10px); -} - -/*** RFC 2119 Requirements ***/ -.spec-requirement { - font-weight: bold; - color: var(--requirement-color); - --requirement-color: var(--green-light); - border-radius: 0.2rem; - transition: color 115ms, background 115ms; - padding: 0 0.1rem 0.2rem 0.1rem; -} - -.spec-requirement:hover { - background: var(--requirement-color); - color: var(--body-background-color); - text-decoration: none; -} - -.spec-requirement.required { - --requirement-color: var(--green-base); -} - -.spec-requirement.not.required { - --requirement-color: var(--red-base); -} - -.spec-requirement.recommended { - --requirement-color: var(--teal-dark); -} - -.spec-requirement.not.recommended { - --requirement-color: var(--orange-base); -} - -.spec-requirement.optional { - --requirement-color: var(--indigo-base); -} - -.spec-toc a:hover { - opacity: 0.8; -} - -.spec-toc a .spec-secid { - color: var(--indigo-dark); -} - -.spec-toc .title { - margin-top: 2em; -} - -.apollo-specs-logo a:hover { - text-decoration: none; -} - -.spec-sidebar { - border-right: 1px solid var(--silver-dark); - overflow-y: auto; -} diff --git a/.graphs/https/specs.apollo.dev/async.js b/.graphs/https/specs.apollo.dev/async.js deleted file mode 100644 index 8712ca1..0000000 --- a/.graphs/https/specs.apollo.dev/async.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Convert a fallible Promise to an infallible Promise<{ ok?: T, err?: Error }>. - * - * @param {Promise} promise - * @return {Promise<{ ok?: T, err: Error }>} - */ -export function result(promise) { - return promise.then(ok => ({ ok }), err => ({ err })) -} - -/** - * Await a DOM event, then detach the listener. - * - * @param {string} event name - * @param {HTMLElement} listen on - * @return {Promise} - */ -export function event(event='load', element=window) { - return new Promise(resolve => { - element.addEventListener(event, fire) - - function fire(evt) { - resolve(evt) - element.removeEventListener(event, fire) - } - }) -} - -/** - * Approximate timeout. - * - * @param {number} ms - * @return {Promise} - */ -export function sleep(ms=1000) { - return new Promise(resolve => setTimeout(resolve, ms)) -} diff --git a/.graphs/https/specs.apollo.dev/boot.js b/.graphs/https/specs.apollo.dev/boot.js deleted file mode 100644 index 1618962..0000000 --- a/.graphs/https/specs.apollo.dev/boot.js +++ /dev/null @@ -1,49 +0,0 @@ -window.process = window.process || {env: {}} - -document.head.appendChild(Object.assign( - document.createElement('style'), { - textContent: ` - body { - background: #FFF; - color: rgb(90, 98, 112); - font-size: 18px; - } - - main[markdown] { - white-space: pre-line; - font-family: monospace; - max-width: 920px; - margin: auto; - } - - #view { - display: flex; - justify-content: center; - flex-flow: row nowrap; - } - ` - }) -) - -document.head.appendChild(Object.assign( - document.createElement('style'), { - id: '__curtain__', - textContent: ` - body { - opacity: 0; - transition: opacity 1s; - } - ` - } -)) - -document.head.appendChild(Object.assign( - document.createElement('script'), { - type: 'module', - async: true, - defer: true, - src: '/main.js', - } -)) - -setTimeout(() => __curtain__ && __curtain__.remove(), 200) \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/federation/v2.0.graphql b/.graphs/https/specs.apollo.dev/federation/v2.0.graphql deleted file mode 100644 index a506049..0000000 --- a/.graphs/https/specs.apollo.dev/federation/v2.0.graphql +++ /dev/null @@ -1,213 +0,0 @@ -extend schema @link(url: "https://specs.apollo.dev/id/v1.0") - -extend schema @id__(url: "https://specs.apollo.dev/federation/v2.0") - -""" -The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. - -```graphql example -- using {@key} -type Product @key(fields: "upc") { - upc: UPC! - name: String -} -``` - -Multiple keys can be defined on a single object type: - -```graphql example -- defining multiple {@key}s -type Product @key(fields: "upc") @key(fields: "sku") { - upc: UPC! - sku: SKU! - name: String -} -``` - -Note: Repeated directives (in this case, `@key`, used multiple times) require support by the underlying GraphQL implementation. -""" -directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE - -""" -The `@provides` directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example: - -```graphql example -- using {@provides} -type Review @key(fields: "id") { - product: Product @provides(fields: "name") -} - -extend type Product @key(fields: "upc") { - upc: String @external - name: String @external -} -``` - -When fetching `Review.product` from the Reviews service, it is possible to request the `name` with the expectation that the Reviews service can provide it when going from review to product. `Product.name` is an external field on an external type which is why the local type extension of `Product` and annotation of `name` is required. -""" -directive @provides(fields: FieldSet!) on FIELD_DEFINITION - -""" -The `@requires` directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example: - -```graphql example -- using {@requires} -# extended from the Users service -extend type User @key(fields: "id") { - id: ID! @external - email: String @external - reviews: [Review] @requires(fields: "email") -} -``` - -In this case, the Reviews service adds new capabilities to the `User` type by providing a list of `reviews` related to a user. In order to fetch these reviews, the Reviews service needs to know the `email` of the `User` from the Users service in order to look up the reviews. This means the `reviews` field / resolver *requires* the `email` field from the base `User` type. -""" -directive @requires(fields: FieldSet!) on FIELD_DEFINITION - -""" -The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example: - -```graphql example -- using {@external} -# extended from the Users service -extend type User @key(fields: "email") { - email: String @external - reviews: [Review] -} -``` - -This type extension in the Reviews service extends the `User` type from the Users service. It extends it for the purpose of adding a new field called `reviews`, which returns a list of `Review`s. -""" -directive @external on FIELD_DEFINITION - -scalar FieldSet - -"""Link a foreign schema by its URL.""" -directive @link( - """ - The foreign schema's URL. - - Link URLs serve two main purposes: - - Providing a unique identifier for the foreign schema - - Directing human readers to documentation about the foreign schema - - Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. - - Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): - - ```html diagram -- Basic anatomy of a link URL - - https://spec.example.com/a/b/c/mySchema/v1.0 - - ``` - - The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). - - Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. - - ```html diagram -- Ignoring meaningless parts of a URL - - https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag - - ``` - - All of these are valid arguments to `url`, and their interpretations: - - | url | normalized url | name | version | - | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | - | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | - | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | - | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | - | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | - | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | - - If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. - """ - url: String! - """ - Change the [namespace prefix](#sec-Prefixing) assigned to this schema. - - The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). - - By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. - - Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). - - ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0", as: "eg") - { - query: Query - } - - type User { - # Specifying `as: "eg"` transforms @example into @eg - name: String @eg(data: ITEM) - } - - # Additional specified schema elements must have their prefixes set - # to the new name. - enum eg__Data { - ITEM - } - - # Name transformation must also be applied to definitions pulled in from - # specifications. - directive @eg(data: eg__Data) on FIELD_DEFINITION - - directive @link(url: String!, as: String) repeatable on SCHEMA - ``` - """ - as: String - """Import definitions into the local namespace.""" - import: [Import] - """ - An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. - - By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. - - This behavior is different for {@link}s with a specified purpose: - - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it - - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema - """ - for: Purpose -) repeatable on SCHEMA - -"""TK describe an import""" -scalar Import - -""" -The role of a feature referenced with {@link}. - -This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. - -Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. -""" -enum Purpose { - """ - `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. - - Security-conscious consumers MUST NOT serve a field if: - - the schema definition has **any** unsupported SECURITY directives, - - the field's parent type definition has **any** unsupported SECURITY directives, - - the field's return type definition has **any** unsupported SECURITY directives, or - - the field definition has **any** unsupported SECURITY directives - - Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. - - Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. - - More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. - """ - SECURITY - """ - `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. - - Consumers MUST NOT serve a field if: - - the schema's definition has **any** unsupported EXECUTION directives, - - the field's parent type definition has **any** unsupported EXECUTION directives, - - the field's return type definition has **any** unsupported EXECUTION directives, or - - the field definition has **any** unsupported EXECUTION directives - - Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. - """ - EXECUTION -} - -directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/id/v1.0.graphql b/.graphs/https/specs.apollo.dev/id/v1.0.graphql deleted file mode 100644 index 261d694..0000000 --- a/.graphs/https/specs.apollo.dev/id/v1.0.graphql +++ /dev/null @@ -1,5 +0,0 @@ -extend schema @id(url: "https://specs.apollo.dev/id/v1.0") - -extend schema @id(url: "https://specs.apollo.dev/id/v1.0") - -directive @id(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/id/v1.0.html b/.graphs/https/specs.apollo.dev/id/v1.0.html deleted file mode 100644 index 6acdda9..0000000 --- a/.graphs/https/specs.apollo.dev/id/v1.0.html +++ /dev/null @@ -1,1555 +0,0 @@ - - - - -id v1.0 - - - - - - - -
    -
    -

    id v1.0

    - -
    -
    - - -
    -
    - -
      -
      - -
      - - - diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql b/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql deleted file mode 100644 index f070896..0000000 --- a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.graphql +++ /dev/null @@ -1,197 +0,0 @@ -extend schema @link(url: "https://specs.apollo.dev/id/v1.0") @id__(url: "https://specs.apollo.dev/inaccessible/v0.1") - -extend schema @id__(url: "https://specs.apollo.dev/inaccessible/v0.1") - -""" -

      for removing elements from a core schema

      - -```raw html - - - -
      StatusRelease
      Version0.1
      - - -``` - -This document defines the `@inaccessible` directive, which marks schema elements which should not be accessible in the public-facing schema. This version of the spec supports Object, Interface, and Union types. - -# How to read this document - -This document uses [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL. - -# Definitions - -## Processor - -This specification makes references to **Processors**. Processors are described in the [Actors section of the `@core` spec](https://specs.apollo.dev/core/v0.2/#sec-Actors) as an actor which can perform transformations on a core schema. In the case of `@inaccessible`, the Processor will be expected to remove various parts of a core schema. - -# Example: Sensitive User Data - -*This section is non-normative.* - -We'll refer to this example of a core schema with sensitive user data throughout the document: - -:::[example](./schema.graphql) -- Core schema example - -The schema above contains both a field (`User.id`) and type (`BankAccount`) that are marked as `@inaccessible`. These symbols should be omitted from the processed schema anywhere they would appear. When the processed schema below is generated from this core schema, notice what has been removed: -* `User.id` field -* `BankAccount` type -* `User.bankAccount` field -* `Account` union's `BankAccount` type - -:::[example](./processedSchema.graphql) -- Core schema after processing - -## Modifications to the API - -Within the API, - -- Field Definitions marked with `@inaccessible` MUST be excluded -- Object types marked with `@inaccessible` MUST be excluded -- Object types marked with `@inaccessible` MUST be excluded from membership in any unions -- Interfaces marked with `@inaccessible` MUST be excluded -- Interfaces marked with `@inaccessible` MUST be excluded from the `extends` clause of all other interfaces -- Union types marked with `@inaccessible` MUST be excluded - -Note applying this process may result in an invalid schema. For example, fields which return `@inaccessible` types which are not themselves marked `@inaccessible` will now return an invalid type which is not present in the schema. This is intentional. `@inaccessible` does NOT cascade. If applying `@inaccessible` results in an invalid schema, the serving process SHOULD apply standard polices to determine whether or how to serve it. Generally, invalid schemas SHOULD NOT be served, though some server configurations—particularly those used for development—MAY OPTIONALLY elect to serve such schemas in a degraded mode. The semantics of such a mode are not within the scope of this spec. -""" -schema { - query: Query -} - -directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION - -"""Link a foreign schema by its URL.""" -directive @link( - """ - The foreign schema's URL. - - Link URLs serve two main purposes: - - Providing a unique identifier for the foreign schema - - Directing human readers to documentation about the foreign schema - - Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. - - Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): - - ```html diagram -- Basic anatomy of a link URL - - https://spec.example.com/a/b/c/mySchema/v1.0 - - ``` - - The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). - - Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. - - ```html diagram -- Ignoring meaningless parts of a URL - - https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag - - ``` - - All of these are valid arguments to `url`, and their interpretations: - - | url | normalized url | name | version | - | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | - | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | - | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | - | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | - | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | - | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | - - If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. - """ - url: String! - """ - Change the [namespace prefix](#sec-Prefixing) assigned to this schema. - - The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). - - By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. - - Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). - - ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0", as: "eg") - { - query: Query - } - - type User { - # Specifying `as: "eg"` transforms @example into @eg - name: String @eg(data: ITEM) - } - - # Additional specified schema elements must have their prefixes set - # to the new name. - enum eg__Data { - ITEM - } - - # Name transformation must also be applied to definitions pulled in from - # specifications. - directive @eg(data: eg__Data) on FIELD_DEFINITION - - directive @link(url: String!, as: String) repeatable on SCHEMA - ``` - """ - as: String - """Import definitions into the local namespace.""" - import: [Import] - """ - An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. - - By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. - - This behavior is different for {@link}s with a specified purpose: - - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it - - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema - """ - for: Purpose -) repeatable on SCHEMA - -"""TK describe an import""" -scalar Import - -""" -The role of a feature referenced with {@link}. - -This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. - -Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. -""" -enum Purpose { - """ - `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. - - Security-conscious consumers MUST NOT serve a field if: - - the schema definition has **any** unsupported SECURITY directives, - - the field's parent type definition has **any** unsupported SECURITY directives, - - the field's return type definition has **any** unsupported SECURITY directives, or - - the field definition has **any** unsupported SECURITY directives - - Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. - - Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. - - More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. - """ - SECURITY - """ - `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. - - Consumers MUST NOT serve a field if: - - the schema's definition has **any** unsupported EXECUTION directives, - - the field's parent type definition has **any** unsupported EXECUTION directives, - - the field's return type definition has **any** unsupported EXECUTION directives, or - - the field definition has **any** unsupported EXECUTION directives - - Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. - """ - EXECUTION -} - -directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/index.html b/.graphs/https/specs.apollo.dev/index.html deleted file mode 100644 index 01dc40f..0000000 --- a/.graphs/https/specs.apollo.dev/index.html +++ /dev/null @@ -1,200 +0,0 @@ - - - -Technical Specifications - - - -
      - -
      -
      -
      - -
      -

      Technical Specifications

      -
      - - ## Specifications - - ### Core Schemas - - core - - GraphQL schemas with interoperable metadata - - - - Core schemas are standard GraphQL schemas which follow the [core specification](core/). The [core spec](core/) provides machinery to reference namespaced, versioned directives which provide specified metadata to schema processors and data cores. - - All other specifications in this library are core-compliant specs, designed to be referenced from core schemas. - - ### Joining and Linking - - join - - for joining subgraphs into a supergraph - - - - ### Tags - - tag - - for tagging GraphQL schema elements with names - - - - ### Filtering - - inaccessible - - for removing fields and types from a core schema - - - - ## Glossary - -
      -
      API schema (or API), n.
      -
      - The subset of a [core schema](#def-core-schema) which is used to validate client operations and may be served to clients via introspection. *Contrast: [machinery](#def-machinery)* -
      - -

      core schema, n.

      -
      - A GraphQL schema document which follows the [core schema specification](/core/). Core schemas provide specified metadata to processors and data cores by referencing features. -
      - -
      machinery, n.
      -
      - The subset of a [core schema](#def-core-schema) which should *not* be served to clients. Machinery typically consists of specified metadata and definitions to support that metadata which assist processing but which which are not relevant to clients. -
      - -

      join, v.

      -
      -
        -
      1. the act of combining multiple GraphQL schemas into a single schema -

        - "Our API joins our auth, users, and movies graphs." -

        - -
      2. the act of combining multiple GraphQL *definitions* into a single definition. -

        - "Our API's User type joins fields from the users and movies graphs." -

        -
      -
      - -
      subgraph, n.
      -
      - A graph which is [joined](#def-join) into a [supergraph](#def-supergraph). - - *aka: federated service graph* -
      - -
      supergraph, n.
      -
      - A graph which [joins](#def-join) multiple [subgraphs](#def-subgraph) together. -
      -
      - - -
      - -
      -
      diff --git a/.graphs/https/specs.apollo.dev/inject-logo.js b/.graphs/https/specs.apollo.dev/inject-logo.js deleted file mode 100644 index 82cbcb8..0000000 --- a/.graphs/https/specs.apollo.dev/inject-logo.js +++ /dev/null @@ -1,40 +0,0 @@ -addEventListener('load', injectLogo) - -function injectLogo() { - const sidebar = document.getElementsByClassName('spec-sidebar')[0]; - sidebar.insertAdjacentHTML( - 'afterbegin', - ` - - - ` - ); -} diff --git a/.graphs/https/specs.apollo.dev/light.css b/.graphs/https/specs.apollo.dev/light.css deleted file mode 100644 index 0e77d1c..0000000 --- a/.graphs/https/specs.apollo.dev/light.css +++ /dev/null @@ -1,610 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,700;1,300;1,700&display=swap'); - -html { - --theme: light; - --family-code: 'Source Code Pro'; - --family-text: Source Sans Pro,sans-serif; - background: #FFF; - font-family: var(--family-text); - font-size: 18px; - /* SpaceKit colors from https://space-kit.netlify.app/ */ - /** Brand colors **/ - --pink-darkest: rgb(102, 31, 78); - --pink-darker: rgb(131, 35, 99); - --pink-dark: rgb(196, 57, 151); - --pink-base: rgb(242, 92, 193); - --pink-light: rgb(255, 163, 224); - --pink-lighter: rgb(255, 212, 241); - --pink-lightest: rgb(255, 230, 247); - --teal-darkest: rgb(31, 102, 100); - --teal-darker: rgb(29, 123, 120); - --teal-dark: rgb(38, 162, 157); - --teal-base: rgb(65, 217, 211); - --teal-light: rgb(139, 246, 242); - --teal-lighter: rgb(198, 255, 253); - --teal-lightest: rgb(230, 255, 254); - --indigo-darkest: rgb(45, 31, 102); - --indigo-darker: rgb(49, 28, 135); - --indigo-dark: rgb(63, 32, 186); - --indigo-base: rgb(113, 86, 217); - --indigo-light: rgb(173, 155, 246); - --indigo-lighter: rgb(217, 207, 255); - --indigo-lightest: rgb(235, 230, 255); - - /** Neutrals **/ - --black-darker: rgb(18, 21, 26); - --black-dark: rgb(20, 23, 28); - --black-base: rgb(25, 28, 35); - --black-light: rgb(34, 38, 46); - --black-lighter: rgb(47, 53, 63); - --grey-darker: rgb(66, 72, 85); - --grey-dark: rgb(90, 98, 112); - --grey-base: rgb(119, 127, 142); - --grey-light: rgb(149, 157, 170); - --grey-lighter: rgb(178, 185, 195); - --silver-darker: rgb(202, 208, 216); - --silver-dark: rgb(222, 226, 231); - --silver-base: rgb(235, 238, 240); - --silver-light: rgb(244, 246, 248); - --silver-lighter: rgb(252, 253, 255); - - /** Interface Colors **/ - --red-darkest: rgb(102, 31, 31); - --red-darker: rgb(120, 28, 28); - --red-dark: rgb(156, 35, 35); - --red-base: rgb(209, 59, 59); - --red-light: rgb(241, 134, 134); - --red-lighter: rgb(255, 195, 195); - --red-lightest: rgb(255, 230, 230); - --green-darkest: rgb(20, 94, 51); - --green-darker: rgb(19, 108, 56); - --green-dark: rgb(28, 132, 72); - --green-base: rgb(54, 173, 104); - --green-light: rgb(126, 217, 164); - --green-lighter: rgb(190, 244, 213); - --green-lightest: rgb(230, 255, 240); - --blue-darkest: rgb(22, 60, 102); - --blue-darker: rgb(15, 65, 122); - --blue-dark: rgb(16, 83, 160); - --blue-base: rgb(32, 117, 214); - --blue-light: rgb(116, 176, 244); - --blue-lighter: rgb(187, 219, 255); - --blue-lightest: rgb(240, 247, 255); - - /** Alternate Colors **/ - --orange-darkest: rgb(102, 63, 31); - --orange-darker: rgb(136, 76, 30); - --orange-dark: rgb(180, 102, 38); - --orange-base: rgb(245, 145, 64); - --orange-light: rgb(255, 193, 143); - --orange-lighter: rgb(255, 226, 202); - --orange-lightest: rgb(255, 241, 230); - --yellow-darkest: rgb(102, 80, 31); - --yellow-darker: rgb(132, 103, 29); - --yellow-dark: rgb(180, 143, 37); - --yellow-base: rgb(244, 208, 63); - --yellow-light: rgb(255, 232, 142); - --yellow-lighter: rgb(255, 244, 202); - --yellow-lightest: rgb(255, 250, 230); - --purple-darkest: rgb(66, 22, 102); - --purple-darker: rgb(82, 21, 132); - --purple-dark: rgb(113, 30, 180); - --purple-base: rgb(162, 61, 245); - --purple-light: rgb(205, 143, 255); - --purple-lighter: rgb(232, 204, 255); - --purple-lightest: rgb(244, 230, 255); - --blilet-darkest: rgb(27, 34, 64); - --blilet-darker: rgb(37, 46, 80); - --blilet-dark: rgb(60, 74, 133); - --blilet-base: rgb(81, 104, 194); - --blilet-light: rgb(122, 146, 240); - --blilet-lighter: rgb(176, 190, 247); - --blilet-lightest: rgb(230, 235, 255); - --midnight-darkest: rgb(6, 15, 47); - --midnight-darker: rgb(27, 34, 64); - --midnight-dark: rgb(56, 61, 91); - --midnight-base: rgb(61, 75, 106); - --midnight-light: rgb(86, 105, 146); - --midnight-lighter: rgb(121, 143, 187); - --midnight-lightest: rgb(180, 195, 219); -} - -body { - transition: opacity 1s; -} -/** header **/ - -header h1 { - counter-increment: none; - font-size: 2.5rem; - margin-top: 0; -} - -header h1, header h2 { - counter-increment: none; - text-align: center; -} - -header > .spec-md { - margin: auto; -} - -header > table.spec-md td { - vertical-align: top; -} - -header > table.spec-md td:first-of-type { - color: var(--grey-dark); - font-style: italic; - padding-right: 18px; -} - -header .spec-authors-list { - list-style: none; - display: inline; - margin: 0; - padding: 0; -} - -header .spec-authors-list li { - display: block; -} - -/** Light "theme" **/ - -body { - background: #FFF; - color: var(--grey-dark); - line-height: 130%; -} - -ul > li, ol > li { - margin-top: 1em; -} - -code > ol > li { - margin: 0; -} - -code { - line-height: 100%; - padding: .1em .3em; - border-radius: .3em; - font-size: .9em; - color: var(--pink-base); - background: var(--silver-base); -} - -main { - max-width: 920px; - margin: 20px 100px; -} - -hr { - background-color: var(--grey-darker); -} - -h1 { - color: var(--black-lighter); - font-weight: 400; -} - -h2 { - color: var(--black-lighter); - font-weight: 400; -} - -h3, h4 { - color: var(--grey-dark); - font-weight: 400; -} - -code[class*="language-"], -pre[class*="language-"] { - font-family: var(--family-code); - padding: 0; -} - -figure + p { - margin-top: 0; -} - -a > code { - color: var(--blue-base); -} - -a[href] { - color: var(--indigo-dark); - text-decoration: none; -} - -a[href]:hover { - text-decoration: underline; - text-decoration-skip-ink: auto; -} - -.not-csdl::after { - content: "Note: Code is GraphQL SDL, not CSDL."; - font-size: 80%; - line-height: 125%; - color: var(--yellow-base); - padding: 4px; - border-radius: 9px; - margin-left: 3em; -} - -/** Mermaid diagrams **/ -g.cluster rect { - fill: none !important; - stroke: fuchsia !important; - stroke-dasharray: 5 5; -} - -g.cluster-label { - transform: attr(transform) translateX(100px); -} - - -/** Section numbering **/ - -body { - counter-reset: section subsection listing; -} - -h2 { - counter-increment: section; - counter-reset: subsection listing; -} - -h3 { - counter-increment: subsection; - counter-reset: listing; -} - -h1,h2,h3,h4 { - position: relative; -} - -figure { - margin: 0; - margin-block-start: 2em; - margin-block-end: 2em; -} - -a.a-header { - color: var(--grey-dark); - text-decoration: none; -} - -a.a-header::before { - position: absolute; - top: 0; - transform: translateX(-100%); - margin-left: -20px; - - content: counter(section); - color: var(--grey-darker); - font-weight: normal; - text-align: right; - line-height: inherit; -} - -a.a-header:hover > .a-header-text { - text-decoration: underline; - text-decoration-skip-ink: auto; -} - -@media screen and (max-width: 980px) { - a.a-header::before { - position: static; - margin: 0; - margin-right: 10px; - transform: none; - } -} - -a.a-header:hover::before { - content: "# " counter(section); - text-decoration: none; -} - -h3 > .a-header::before { - content: counter(section) "." counter(subsection) -} - -h3 > a.a-header:hover::before { - content: "# " counter(section) "." counter(subsection); - text-decoration: none; -} - -.mermaid > svg { - display: block; - margin: auto; -} - -figure { - position: relative; -} - -figure > figcaption { - font-style: italic; -} - -figure > figcaption > a.a-header { - color: var(--grey-base); -} - -figure > figcaption > a.a-header::before { - counter-increment: listing; - content: "Fig " counter(section) "." counter(subsection) "." counter(listing); - color: var(--grey-dark); -} - -figure > figcaption > a.a-header:hover::before { - content: "# Fig " counter(section) "." counter(subsection) "." counter(listing); -} - -figure > figcaption > a.code::before { - content: "Code " counter(section) "." counter(subsection) "." counter(listing); -} - -figure > figcaption > a.code:hover::before { - content: "# Code " counter(section) "." counter(subsection) "." counter(listing); -} - -figure pre { - margin-left: 40px !important; - margin-right: 40px !important; - background: none !important; -} - -a.code > p { display: inline; } - - -code.grammar { - white-space: pre; - margin: 0; - margin-bottom: 1em; - display: block; -} - -blockquote { - border-left: 2px solid var(--blue-dark); - padding-left: 9px; -} - -html { - --toc-width: 0; -} - -.toc { - margin-top: 1.4rem; - color: var(--grey-darker); - font-size: 1rem; - line-height: 130%; - padding: 0; - margin-left: 1rem; -} - -.toc a.toc-link { - color: var(--grey-dark); - text-decoration: none; - line-height: 30px; -} - -.toc a:hover { - opacity: 0.8; -} - -.toc code { - color: var(--grey-light); -} - -.toc p, .toc .a-header-text { - margin: 0; - display: inline; -} - - -.toc ol { - padding-left: 32px; -} - -.toc, .toc ol { - list-style: none; -} - -.toc { - counter-reset: toc-sec toc-subsec toc-fig; - --num-width: 50px; -} - -.toc li::marker { - content: attr(data-index-path) " "; -} - -.toc li { - margin-top: 0; -} - -.toc li + li { - margin-top: 0; -} - - -html { margin: 0; padding: 0; } - -body { - --nav-height: 28px; - --nav-z: 1000; - - margin: 0; padding: 0; -} - -#view { - display: flex; - flex-flow: row nowrap; - justify-content: center; -} - -nav { - position: fixed; - font-size: 14px; - z-index: var(--nav-z); - width: 100vw; -} - -nav { - --slice-height: calc(var(--nav-height)); -} - -.slices { - height: var(--slice-height); - line-height: calc(var(--slice-height) * 0.75); - vertical-align: middle; - display: flex; - padding: 9px; - top: 0; left: 0; -} - -slices.top-right { - left: auto; right: 0; -} - -.slice { - height: 100%; - padding: 4px var(--slice-height); - background: var(--silver-light); - text-align: center; - box-sizing: border-box; -} - -label.slice { - background: transparent; -} - -.slices.top-left .slice { - clip-path: polygon( - var(--slice-height) 0, - 100% 0, - calc(100% - var(--slice-height)) 100%, - 0 100%); - margin-left: calc(-1 * var(--slice-height) + 4px); -} - -.slices.top-left .slice:first-child { - clip-path: polygon( - 0 0, - 100% 0, - calc(100% - var(--slice-height)) 100%, - 0 100%); - margin-left: 0; - padding-left: 9px; -} - -.slices.top-right .slice { - clip-path: polygon( - 0 0, - calc(100% - var(--slice-height)) 0, - 100% 100%, - var(--slice-height) 100%); - margin-right: calc(-1 * var(--slice-height) + 4px); -} - -.slices.top-right .slice:last-child { - clip-path: polygon( - 0 0, - 100% 0, - 100% 100%, - var(--slice-height) 100%); - margin-right: 0; - padding-right: 9px; -} - -nav a[href] { - color: var(--indigo-dark); - text-decoration: none; -} - -nav > a[href]:hover { - background: var(--indigo-lightest); - text-decoration: none; -} - -@media screen and (max-width: 1300px) { - .toc.measured { z-index: -100; opacity: 0; } - .toc-placeholder { display: none; } -} - -h2 { - margin: 3em 0 1em; -} - -header > h2 { - font-style: italic; - margin-top: 1em; -} - -h3 { - margin-top: 2em; -} - -pre > code > ol { - counter-reset: line attr(start); - margin-block-end: -1em; -} - -pre > code { - line-height: 150%; -} - -pre > code > ol > li { - counter-increment: line 1; -} - -pre > code > ol > li.highlight::marker { - color: var(--pink-base); -} - -pre > code > ol > li.lowlight { - filter: grayscale(100%); - opacity: 0.4; -} - -.no-linum pre > code > ol > li::marker { - content: ''; -} - -pre > code > ol > li::marker { - content: counter(line) "\09"; - color: var(--midnight-dark); - font-size: 80%; -} - -code.anatomy { - font-size: 24px; - margin: 1em; - display: block; - --color: var(--orange-base); - --depth: 0; - margin-bottom: 128px; -} - - -.counter-example figure { - background: linear-gradient(-90deg, var(--red-darkest), transparent); -} - -#sidenav { - width: 312px; - height: 100vh; - padding: 0 24px; - border-right: 1px solid #DEE2E7; - overflow-y: auto; - position: sticky; - top: 0; -} - -.apollo-specs-logo a:hover { - text-decoration: none; -} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.graphql b/.graphs/https/specs.apollo.dev/link/v1.0.graphql deleted file mode 100644 index e04bcfa..0000000 --- a/.graphs/https/specs.apollo.dev/link/v1.0.graphql +++ /dev/null @@ -1,189 +0,0 @@ -extend schema @id__(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/id/v1.0") - -extend schema - -""" -```raw html - - - -
      StatusDraft
      Version1.0
      - - -``` - -[intro to core schemas](core-schemas-intro.md) - -# Scoping - -[scope](../scope.md) - -# Versioning - -[Versioning](../versioning.md) - -# Processing Schemas - -```mermaid diagram -graph LR - schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") - proc-->output(["📄 Output Schema"]):::file - classDef file fill:none,color:#22262E; - style proc fill:none,stroke:fuchsia,color:fuchsia; -``` - -A common use case is that of a processor which consumes a valid input schema and generates an output schema. - -The general guidance for processor behavior is: don't react to what you don't understand. - -Specifically, processors: - - SHOULD pass through {@link} directives which reference unknown feature URLs - - SHOULD pass through prefixed directives, types, and other schema elements - - SHOULD pass through directives which are not [associated with](#AssignFeatures) a {@link} feature - -Processors MAY accept configuration which overrides these default behaviors. - -Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. - -# Algorithms - -[Algorithms](algorithms.md) -""" -schema { - query: Query -} - -"""Link a foreign schema by its URL.""" -directive @link( - """ - The foreign schema's URL. - - Link URLs serve two main purposes: - - Providing a unique identifier for the foreign schema - - Directing human readers to documentation about the foreign schema - - Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. - - Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): - - ```html diagram -- Basic anatomy of a link URL - - https://spec.example.com/a/b/c/mySchema/v1.0 - - ``` - - The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). - - Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. - - ```html diagram -- Ignoring meaningless parts of a URL - - https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag - - ``` - - All of these are valid arguments to `url`, and their interpretations: - - | url | normalized url | name | version | - | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | - | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | - | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | - | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | - | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | - | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | - - If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. - """ - url: String! - """ - Change the [namespace prefix](#sec-Prefixing) assigned to this schema. - - The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). - - By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. - - Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). - - ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0", as: "eg") - { - query: Query - } - - type User { - # Specifying `as: "eg"` transforms @example into @eg - name: String @eg(data: ITEM) - } - - # Additional specified schema elements must have their prefixes set - # to the new name. - enum eg__Data { - ITEM - } - - # Name transformation must also be applied to definitions pulled in from - # specifications. - directive @eg(data: eg__Data) on FIELD_DEFINITION - - directive @link(url: String!, as: String) repeatable on SCHEMA - ``` - """ - as: String - """Import definitions into the local namespace.""" - import: [Import] - """ - An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. - - By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. - - This behavior is different for {@link}s with a specified purpose: - - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it - - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema - """ - for: Purpose -) repeatable on SCHEMA - -"""TK describe an import""" -scalar Import - -""" -The role of a feature referenced with {@link}. - -This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. - -Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. -""" -enum Purpose { - """ - `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. - - Security-conscious consumers MUST NOT serve a field if: - - the schema definition has **any** unsupported SECURITY directives, - - the field's parent type definition has **any** unsupported SECURITY directives, - - the field's return type definition has **any** unsupported SECURITY directives, or - - the field definition has **any** unsupported SECURITY directives - - Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. - - Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. - - More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. - """ - SECURITY - """ - `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. - - Consumers MUST NOT serve a field if: - - the schema's definition has **any** unsupported EXECUTION directives, - - the field's parent type definition has **any** unsupported EXECUTION directives, - - the field's return type definition has **any** unsupported EXECUTION directives, or - - the field definition has **any** unsupported EXECUTION directives - - Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. - """ - EXECUTION -} - -directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/link/v1.0.html b/.graphs/https/specs.apollo.dev/link/v1.0.html deleted file mode 100644 index 4dcf9f7..0000000 --- a/.graphs/https/specs.apollo.dev/link/v1.0.html +++ /dev/null @@ -1,1684 +0,0 @@ - - - - -link v1.0 - - - - - - - -
      -
      -

      link v1.0

      - -
      - -
      -

      2Import

      -
      scalar Import
      -
      -describe an import
      -
      -
      -

      3Purpose

      -
      enum Purpose {
      -  SECURITY
      -  EXECUTION
      -}
      -

      The role of a feature referenced with @link.

      -

      This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail‐open behavior of core schema consumers is undesirable.

      -
      -Note -we’ll refer to directives from features which are for: SECURITY or for: EXECUTION as “SECURITY directives” and “EXECUTION directives”, respectively.
      -
      -
      - - -
      - - -
      - - - diff --git a/.graphs/https/specs.apollo.dev/load.js b/.graphs/https/specs.apollo.dev/load.js deleted file mode 100644 index e1d6bd3..0000000 --- a/.graphs/https/specs.apollo.dev/load.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Asynchronously load a non-module script. - * - * @param {string} src - * @return {Promise} - */ -export async function script(src) { - const existing = document.querySelector(`script[src="${src}"]`) - if (existing && existing.__loadingPromise) - return existing.__loadingPromise - const script = document.createElement('script') - script.async = true - script.defer = true - script.setAttribute('::save', 'off') - script.__loadingPromise = new Promise((resolve, reject) => { - script.onload = resolve - script.onerror = reject - }) - script.src = src - document.body.appendChild(script) - return script.__loadingPromise -} - -/** - * Asynchronously load a stylesheet. - * - * @param {string} src - * @param {'append' | 'prepend'} position (default: 'append') - * @return {Promise} - */ -export function styles(src, position = 'append') { - const existing = document.querySelector(`link[rel="stylesheet"][href="${src}"]`) - if (existing && existing.__loadingPromise) - return existing.__loadingPromise - const link = document.createElement('link') - link.rel = 'stylesheet' - link.media = 'print' - link.setAttribute('::save', 'off') - link.__loadingPromise = new Promise((resolve, reject) => { - link.onload = event => { - link.media = 'all' - link.onload = null - resolve(event) - } - link.onerror = reject - }) - link.href = src - if (position === 'append') - document.head.appendChild(link) - else - document.head.prepend(link) - return link.__loadingPromise -} diff --git a/.graphs/https/specs.apollo.dev/main.js b/.graphs/https/specs.apollo.dev/main.js deleted file mode 100644 index 07c74d0..0000000 --- a/.graphs/https/specs.apollo.dev/main.js +++ /dev/null @@ -1,17 +0,0 @@ -import markdown from './markdown.js' -import toc from './toc.js' -import {go} from './rendering.js' - -addEventListener('load', main) - -async function main() { - removeEventListener('load', main) - - if (!document.documentElement.dataset.rendered) { - go(() => Promise.all([ - markdown(), - ])) - } - - toc() -} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/markdown.js b/.graphs/https/specs.apollo.dev/markdown.js deleted file mode 100644 index bea41ee..0000000 --- a/.graphs/https/specs.apollo.dev/markdown.js +++ /dev/null @@ -1,87 +0,0 @@ -import marked from 'https://unpkg.com/marked@^1/lib/marked.esm.js' -import {go} from './rendering.js' - -const reAttr = /#::((\[(?.*)\])|((?[a-z]+)\.(?[a-zA-Z_\-]+)))/ - -const slugger = new marked.Slugger() - -const base = new marked.Renderer -const renderer = { - heading(text, level, _raw) { - const slug = slugger.slug(text) - - return ` - - ${text} - `; - }, - - code(code, lang, escaped) { - const lines = code.split(/\n/g) - let classes = {a: [], pre: [], code: [], div: []} - let attr, caption - while (attr = (lines[0] || '').match(reAttr)) { - lines.shift() - const {tag, class: cssClass} = attr.groups - if (tag) { - classes[tag] && classes[tag].push(cssClass) - } else { - caption = attr.groups.caption - } - } - - const slug = slugger.slug(caption || 'code') - return ` -
      - ${caption ? - `
      ${caption && marked(caption)}
      ` - : ''} - ${ - lang === 'mermaid' - ? `
      ${lines.join('\n')}
      ` - : `
      ${lines.join('\n')}
      ` - } -
      - ` - } - -}; - - -marked.use({ renderer }); - -function render(md) { - const container = document.createElement('div') - container.innerHTML = marked(md) - return container.childNodes -} - -function processMarkdown() { - for (const el of document.querySelectorAll('[markdown]')) { - el.removeAttribute('markdown') - for (const child of el.childNodes) { - if (child.nodeType === Node.TEXT_NODE) { - child.replaceWith(...render(child.textContent)) - } - } - } -} - -let Prism = null - -export default async function() { - await go(processMarkdown) - - if (location.hash) { - const name = location.hash.slice(1) - const viewElement = document.getElementById(name) || - document.getElementsByName(name)[0] - if (viewElement) setTimeout(() => { - viewElement.scrollIntoViewIfNeeded() - }, 100) - } -} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/mermaid.js b/.graphs/https/specs.apollo.dev/mermaid.js deleted file mode 100644 index 0c3f283..0000000 --- a/.graphs/https/specs.apollo.dev/mermaid.js +++ /dev/null @@ -1,31 +0,0 @@ -import {script} from './load.js' -import {addTask} from './rendering.js' - -const init = script('https://unpkg.com/mermaid@^8/dist/mermaid.min.js') - .then(async () => { - const mermaid = window.mermaid - await mermaid.initialize({ - securityLevel: 'loose', - theme: 'dark', - startOnLoad: false, - }) - return mermaid - }) - -async function renderMermaids() { - const done = addTask('mermaid') - try { - const mermaid = await init - // for (const fig of document.querySelectorAll('.mermaid')) { - // // preserve source - // fig.__mermaidSrc = fig.__mermaidSrc || fig.textContent - // } - await mermaid.init() - } finally { - done() - } -} - -export default function() { - renderMermaids() -} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/query.js b/.graphs/https/specs.apollo.dev/query.js deleted file mode 100644 index 0f7137f..0000000 --- a/.graphs/https/specs.apollo.dev/query.js +++ /dev/null @@ -1,13 +0,0 @@ -let match - -const query = new Map( - (match = /^\?(?.*)/.exec(location.search)) - ? match.groups.query.split('&') - .map(pair => { - const [k, v=true] = pair.split('=').map(decodeURIComponent) - return [k, v] - }) - : []) - -export default query -window.__QUERY = query \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/rendering.js b/.graphs/https/specs.apollo.dev/rendering.js deleted file mode 100644 index 6c7da8f..0000000 --- a/.graphs/https/specs.apollo.dev/rendering.js +++ /dev/null @@ -1,55 +0,0 @@ -const tasks = new Set - -let resolveRender = null -export const done = new Promise(resolve => { - resolveRender = resolve -}) - -const queue = [] - -export function addTask(data) { - const task = { data } - tasks.add(task) - return () => { - tasks.delete(task) - didFinishTask() - } -} - -export async function go(func, ...args) { - const task = {func, args} - const done = addTask(task) - try { - return await func.apply(this, args) - } finally { - done() - } -} - -function didFinishTask() { - if (!tasks.size) { - if (queue.length) return drain() - if (typeof window.__didFinishRendering === 'function') { - return window.__didFinishRendering() - } - window.didFinishRendering = true - resolveRender() - } -} - -import query from './query.js' -export function isPre() { - return !!query.get('prerender') -} - -export function enqueue(func) { - queue.push(func) - if (!tasks.size) drain() -} - -async function drain() { - while (queue.length) { - await queue.pop()() - } - didFinishTask() -} \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/tag/v0.1.graphql b/.graphs/https/specs.apollo.dev/tag/v0.1.graphql deleted file mode 100644 index bd03b6c..0000000 --- a/.graphs/https/specs.apollo.dev/tag/v0.1.graphql +++ /dev/null @@ -1,147 +0,0 @@ -extend schema @link(url: "https://specs.apollo.dev/id/v1.0") @id__(url: "https://specs.apollo.dev/tag/v0.1") - -extend schema @id__(url: "https://specs.apollo.dev/tag/v0.1") - -""" -Apply an arbitrary piece of string metadata to the target. - -The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses `@tag` names to authorize or require approval for changes to parts of the graph. - -:::[example](ownership-example.graphql) -""" -directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION - -"""Link a foreign schema by its URL.""" -directive @link( - """ - The foreign schema's URL. - - Link URLs serve two main purposes: - - Providing a unique identifier for the foreign schema - - Directing human readers to documentation about the foreign schema - - Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. - - Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): - - ```html diagram -- Basic anatomy of a link URL - - https://spec.example.com/a/b/c/mySchema/v1.0 - - ``` - - The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). - - Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. - - ```html diagram -- Ignoring meaningless parts of a URL - - https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag - - ``` - - All of these are valid arguments to `url`, and their interpretations: - - | url | normalized url | name | version | - | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | - | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | - | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | - | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | - | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | - | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | - - If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. - """ - url: String! - """ - Change the [namespace prefix](#sec-Prefixing) assigned to this schema. - - The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). - - By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. - - Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). - - ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0", as: "eg") - { - query: Query - } - - type User { - # Specifying `as: "eg"` transforms @example into @eg - name: String @eg(data: ITEM) - } - - # Additional specified schema elements must have their prefixes set - # to the new name. - enum eg__Data { - ITEM - } - - # Name transformation must also be applied to definitions pulled in from - # specifications. - directive @eg(data: eg__Data) on FIELD_DEFINITION - - directive @link(url: String!, as: String) repeatable on SCHEMA - ``` - """ - as: String - """Import definitions into the local namespace.""" - import: [Import] - """ - An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. - - By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. - - This behavior is different for {@link}s with a specified purpose: - - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it - - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema - """ - for: Purpose -) repeatable on SCHEMA - -"""TK describe an import""" -scalar Import - -""" -The role of a feature referenced with {@link}. - -This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. - -Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. -""" -enum Purpose { - """ - `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. - - Security-conscious consumers MUST NOT serve a field if: - - the schema definition has **any** unsupported SECURITY directives, - - the field's parent type definition has **any** unsupported SECURITY directives, - - the field's return type definition has **any** unsupported SECURITY directives, or - - the field definition has **any** unsupported SECURITY directives - - Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. - - Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. - - More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. - """ - SECURITY - """ - `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. - - Consumers MUST NOT serve a field if: - - the schema's definition has **any** unsupported EXECUTION directives, - - the field's parent type definition has **any** unsupported EXECUTION directives, - - the field's return type definition has **any** unsupported EXECUTION directives, or - - the field definition has **any** unsupported EXECUTION directives - - Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. - """ - EXECUTION -} - -directive @id__(url: String!) on SCHEMA \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/tag/v0.1.html b/.graphs/https/specs.apollo.dev/tag/v0.1.html deleted file mode 100644 index 3da2eae..0000000 --- a/.graphs/https/specs.apollo.dev/tag/v0.1.html +++ /dev/null @@ -1,1597 +0,0 @@ - - - - -tag v0.1 - - - - - - - -
      -
      -

      tag v0.1

      - -
      -
      -

      1@tag

      -
      directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
      -

      Apply an arbitrary piece of string metadata to the target.

      -

      The following example demonstrates how team ownership over types and fields can be declaratively expressed via inline metadata. One might imagine a CI workflow which analyzes a schema diff and uses @tag names to authorize or require approval for changes to parts of the graph.

      -
      Example № 1
      directive @tag(name: String!) repeatable on 
      -  | FIELD_DEFINITION
      -  | INTERFACE
      -  | OBJECT
      -  | UNION
      -
      -schema
      -  @core(feature: "https://specs.apollo.dev/core/v0.2")
      -  @core(feature: "https://specs.apollo.dev/tag/v0.1") {
      -  query: Query
      -}
      -
      -type Query {
      -  customer(id: String!): Customer @tag(name: "team-customers")
      -  employee(id: String!): Employee @tag(name: "team-admin")
      -}
      -
      -interface User @tag(name: "team-accounts") {
      -  id: String!
      -  name: String!
      -}
      -
      -type Customer implements User @tag(name: "team-customers") {
      -  id: String!
      -  name: String!
      -  cart: [Product!] @tag(name: "team-shopping-cart")
      -}
      -
      -type Employee implements User @tag(name: "team-admin") {
      -  id: String!
      -  name: String!
      -  ssn: String!
      -}
      -
      -
      -
      - - -
      - - -
      - - - diff --git a/.graphs/https/specs.apollo.dev/toc.js b/.graphs/https/specs.apollo.dev/toc.js deleted file mode 100644 index fc2790e..0000000 --- a/.graphs/https/specs.apollo.dev/toc.js +++ /dev/null @@ -1,65 +0,0 @@ -import {enqueue} from './rendering.js' - -export default function install(win=window) { - enqueue(() => { - const sidenav = document.getElementById('sidenav') - const doc = win.document - createToC(win, doc, sidenav) - }) -} - -export function createToC(win=window, doc=win.document, view=doc.getElementById('sidenav')) { - const [existing] = view.getElementsByClassName('toc') - if (existing) return existing - - const toc = doc.createElement('ol') - toc.className = 'toc' - - const stack = [ - { el: toc, level: -Infinity }, - ] - - const counters = { - header: [0, 0, 0, 0, 0], - fig: 0 - } - - for (const h of doc.querySelectorAll('.a-header')) { - const item = doc.createElement('li') - const a = h.cloneNode(true) - a.removeAttribute('name') - a.setAttribute('class', 'toc-link') - item.appendChild(a) - - const level = h.dataset.headingLevel - 1 - if (level) { - ++counters.header[level] - item.dataset.indexPath = counters.header.slice(1, level + 1).join('.') - - while (level <= top().level) { - counters.header[stack.pop().level + 1] = 0 - counters.fig = 0 - } - top().el.appendChild(item) - - const children = doc.createElement('ol') - item.appendChild(children) - stack.push({ - el: children, level - }) - } else { - ++counters.fig - item.dataset.indexPath = counters.header - .slice(1, 3).join('.') + - '.' + counters.fig - top().el.appendChild(item) - } - } - - view.append(toc) - return toc - - function top() { - return stack[stack.length - 1] - } -} diff --git a/build b/build index a4b9d56..e57e384 100755 --- a/build +++ b/build @@ -1,5 +1,10 @@ #!/bin/sh -npx @queerviolet/speck tag/v0.1/tag-v0.1.md > tag/v0.1/index.html -npx @queerviolet/speck core/v0.1/core-v0.1.md > core/v0.1/index.html -npx @queerviolet/speck core/v0.2/core-v0.2.md > core/v0.2/index.html \ No newline at end of file +PATH=$PATH:node_modules/.bin +spec-md tag/v0.1/tag-v0.1.md > tag/v0.1/index.html +spec-md core/v0.1/core-v0.1.md > core/v0.1/index.html +spec-md core/v0.2/core-v0.2.md > core/v0.2/index.html +spec-md inaccessible/v0.1/inaccessible-v0.1.md > inaccessible/v0.1/index.html +spec-md link/v1.0/link-v1.0.md > link/v1.0/index.html +spec-md join/v0.1/join-v0.1.md > join/v0.1/index.html +spec-md federation/v2.0/federation-v2.0.md > federation/v2.0/index.html diff --git a/federation/v2.0.graphql b/federation/v2.0/federation-v2.0.md similarity index 72% rename from federation/v2.0.graphql rename to federation/v2.0/federation-v2.0.md index ec8415a..7f3eee7 100644 --- a/federation/v2.0.graphql +++ b/federation/v2.0/federation-v2.0.md @@ -1,8 +1,20 @@ -@id(url: "https://specs.apollo.dev/federation/v2.0") +# federation v2.0 + +```raw html + + + +
      StatusRelease
      Version2.0
      + + +``` -### `@key` +#! @key + +```graphql definition +directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE +``` -""" The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. ```graphql example -- using {@key} @@ -23,13 +35,14 @@ type Product @key(fields: "upc") @key(fields: "sku") { ``` Note: Repeated directives (in this case, `@key`, used multiple times) require support by the underlying GraphQL implementation. -""" -directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE -### `@provides` +#! @provides + +```graphql definition +directive @provides(fields: FieldSet!) on FIELD_DEFINITION +``` -""" The `@provides` directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example: ```graphql example -- using {@provides} @@ -45,11 +58,13 @@ extend type Product @key(fields: "upc") { When fetching `Review.product` from the Reviews service, it is possible to request the `name` with the expectation that the Reviews service can provide it when going from review to product. `Product.name` is an external field on an external type which is why the local type extension of `Product` and annotation of `name` is required. """ -directive @provides(fields: FieldSet!) on FIELD_DEFINITION -### `@requires` +#! @requires + +```graphql definition +directive @requires(fields: FieldSet!) on FIELD_DEFINITION +``` -""" The `@requires` directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example: ```graphql example -- using {@requires} @@ -62,13 +77,13 @@ extend type User @key(fields: "id") { ``` In this case, the Reviews service adds new capabilities to the `User` type by providing a list of `reviews` related to a user. In order to fetch these reviews, the Reviews service needs to know the `email` of the `User` from the Users service in order to look up the reviews. This means the `reviews` field / resolver *requires* the `email` field from the base `User` type. -""" -directive @requires(fields: FieldSet!) on FIELD_DEFINITION +#! @external -### `@external` +```graphql definition +directive @external on FIELD_DEFINITION +``` -""" The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example: ```graphql example -- using {@external} @@ -80,7 +95,44 @@ extend type User @key(fields: "email") { ``` This type extension in the Reviews service extends the `User` type from the Users service. It extends it for the purpose of adding a new field called `reviews`, which returns a list of `Review`s. -""" -directive @external on FIELD_DEFINITION -scalar FieldSet \ No newline at end of file +#! FieldSet + +```graphql definition +scalar FieldSet +``` + +A set of fields. + +```graphql example -- Using `FieldSet` with a single field in a `@key` +type User @key(fields: "id") { + id: ID! @external +} +``` + +```graphql example -- Using `FieldSet` with a multiple fields +type User @key(fields: "uid realm") { + uid: String + realm: String +} +``` + +Deeply nested fields are supported with standard GraphQL syntax + +```graphql example -- `FieldSet` with nested fields +type User @key(fields: "contact { email }") { + contact: Contact +} + +type Contact { + email: String +} +``` + +Field arguments are not supported. + +```graphql counter-example -- `FieldSet` does not support field arguments +type User @key(fields: "emails(first: 1)") { + emails(first: Int): [String] +} +``` \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/federation/v2.0.html b/federation/v2.0/index.html similarity index 71% rename from .graphs/https/specs.apollo.dev/federation/v2.0.html rename to federation/v2.0/index.html index b927d6e..91803ee 100644 --- a/.graphs/https/specs.apollo.dev/federation/v2.0.html +++ b/federation/v2.0/index.html @@ -1039,500 +1039,20 @@ cursor: help; } - - - +

      federation v2.0

      +
      + + + +
      StatusRelease
      Version2.0
      + + +

      1@key

      -
      directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
      +
      directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
      +

      The @key directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface.

      Example № 1 using @key
      type Product @key(fields: "upc") {
         upc: UPC!
      -  name: String
      +  name: String
       }
       

      Multiple keys can be defined on a single object type:

      Example № 2 defining multiple @keys
      type Product @key(fields: "upc") @key(fields: "sku") {
         upc: UPC!
         sku: SKU!
      -  name: String
      +  name: String
       }
       
      @@ -1565,44 +1087,76 @@

      2@provides

      -
      directive @provides(fields: FieldSet!) on FIELD_DEFINITION
      +
      directive @provides(fields: FieldSet!) on FIELD_DEFINITION
      +

      The @provides directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example:

      Example № 3 using @provides
      type Review @key(fields: "id") {
      -  product: Product @provides(fields: "name")
      +  product: Product @provides(fields: "name")
       }
       
       extend type Product @key(fields: "upc") {
      -  upc: String @external
      -  name: String @external
      +  upc: String @external
      +  name: String @external
       }
       
      -

      When fetching Review.product from the Reviews service, it is possible to request the name with the expectation that the Reviews service can provide it when going from review to product. Product.name is an external field on an external type which is why the local type extension of Product and annotation of name is required.

      +

      When fetching Review.product from the Reviews service, it is possible to request the name with the expectation that the Reviews service can provide it when going from review to product. Product.name is an external field on an external type which is why the local type extension of Product and annotation of name is required. “””

      3@requires

      -
      directive @requires(fields: FieldSet!) on FIELD_DEFINITION
      +
      directive @requires(fields: FieldSet!) on FIELD_DEFINITION
      +

      The @requires directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example:

      Example № 4 using @requires
      # extended from the Users service
       extend type User @key(fields: "id") {
      -  id: ID! @external
      -  email: String @external
      -  reviews: [Review] @requires(fields: "email")
      +  id: ID! @external
      +  email: String @external
      +  reviews: [Review] @requires(fields: "email")
       }
       

      In this case, the Reviews service adds new capabilities to the User type by providing a list of reviews related to a user. In order to fetch these reviews, the Reviews service needs to know the email of the User from the Users service in order to look up the reviews. This means the reviews field / resolver requires the email field from the base User type.

      4@external

      -
      directive @external on FIELD_DEFINITION
      +
      directive @external on FIELD_DEFINITION
      +

      The @external directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example:

      Example № 5 using @external
      # extended from the Users service
       extend type User @key(fields: "email") {
      -  email: String @external
      -  reviews: [Review]
      +  email: String @external
      +  reviews: [Review]
       }
       

      This type extension in the Reviews service extends the User type from the Users service. It extends it for the purpose of adding a new field called reviews, which returns a list of Reviews.

      +
      +

      5FieldSet

      +
      scalar FieldSet
      +
      +

      A set of fields.

      +
      Example № 6 Using FieldSet with a single field in a @key
      type User @key(fields: "id") {
      +  id: ID! @external
      +}
      +
      +
      Example № 7 Using FieldSet with a multiple fields
      type User @key(fields: "uid realm") {
      +  uid: String
      +  realm: String
      +}
      +
      +

      Deeply nested fields are supported with standard GraphQL syntax

      +
      Example № 8 FieldSet with nested fields
      type User @key(fields: "contact { email }") {
      +  contact: Contact
      +}
      +
      +type Contact {
      +  email: String
      +}
      +
      +

      Field arguments are not supported.

      +
      Counter Example № 9 FieldSet does not support field arguments
      type User @key(fields: "emails(first: 1)") {
      +  emails(first: Int): [String]
      +}
      +
      +
      @@ -1614,6 +1168,7 @@

      2@provides
    1. 3@requires
    2. 4@external
    3. +
    4. 5FieldSet
    5. diff --git a/inaccessible/spec.md b/inaccessible/spec.md deleted file mode 100644 index e931f7c..0000000 --- a/inaccessible/spec.md +++ /dev/null @@ -1,4 +0,0 @@ -# Inaccessible - -

      for removing fields and types from a core schema

      - diff --git a/inaccessible/v0.1/.gitignore b/inaccessible/v0.1/.gitignore new file mode 100644 index 0000000..64270c5 --- /dev/null +++ b/inaccessible/v0.1/.gitignore @@ -0,0 +1,5 @@ +node_modules +package-lock.json +.DS_Store +.dist +*.swp diff --git a/inaccessible/v0.1/README.md b/inaccessible/v0.1/README.md new file mode 100644 index 0000000..4bc07a7 --- /dev/null +++ b/inaccessible/v0.1/README.md @@ -0,0 +1,32 @@ +# Spec Template + +## Getting Started + +1. Click the "Use this template" button on this repository to create a copy of it and name the new repository `specs-{{spec_name}}`, per convention. +1. Search for usages of `%%SPEC-.*?%%` tokens within this repository and replace them with appropriate names (e.g., `%%SPEC-NAME%%`, `%%SPEC-TITLE%%` and `%%SPEC-VERSION%%`). +1. Setup the new repository with Netlify (estimated about 5 minutes) + 1. Go to [Netlify App](https://app.netlify.com/teams/apollo/sites) + 1. Click “New Site From Git” button + 1. Choose GitHub + 1. Authorize + 1. Choose `apollographql` org + 1. Search for `specs-{{spec_name}}` + 1. It probably won’t come up + 1. Choose “Configure Netlify on GitHub” + 1. On the “Install Netlify” screen choose `apollographql` + 1. Scroll to the bottom of the App page to where you see the option for “Only select repositories” inside “Repository access” + 1. Click “Select repositories” + 1. Type `specs-{{spec_name}}` again, then click the matching name. + 1. Click on “Save” + 1. Then, back on Netlify, click on “specs-tag” in the “Continuous Deployment: GitHub App” box. + 1. Leave all the defaults as they are and press “Deploy site” + 1. Click on “Site Settings” + 1. Press “Change Site Name” + 1. Type `apollo-specs-{{spec_name}}` as the name and press “Save” + 1. The site should now work at `https://apollo-specs-{{spec_name}}.netlify.app/` + 1. Click on “Build and Deploy” on the left menu + 1. Under “Branches” press “Edit Settings” + 1. Change the “Branch deploys” option to “All” and press “Save” +1. Setup proxying redirects to the new sub-spec site [on the `specs` repo](https://github.com/apollographql/specs/blob/main/_redirects). This will make it available at `https://specs.apollo.dev/{{spec_name}}`. +1. Run `npm run dev` to watch and rebuild. Just use a browser to view `.dist/index.html` to see the rendered page. +1. Write the actual specifications. _Use other specifications (like [the `core` specification](https://github.com/apollographql/specs-core)) as your guide._ diff --git a/inaccessible/coreDirectives.graphql b/inaccessible/v0.1/coreDirectives.graphql similarity index 100% rename from inaccessible/coreDirectives.graphql rename to inaccessible/v0.1/coreDirectives.graphql diff --git a/inaccessible/inaccessible-v0.1.graphql b/inaccessible/v0.1/inaccessible-v0.1.md similarity index 61% rename from inaccessible/inaccessible-v0.1.graphql rename to inaccessible/v0.1/inaccessible-v0.1.md index 5f03b6c..9156aa0 100644 --- a/inaccessible/inaccessible-v0.1.graphql +++ b/inaccessible/v0.1/inaccessible-v0.1.md @@ -1,6 +1,5 @@ -@id(url: "https://specs.apollo.dev/inaccessible/v0.1") +# Inaccessible -"""

      for removing elements from a core schema

      ```raw html @@ -40,9 +39,33 @@ The schema above contains both a field (`User.id`) and type (`BankAccount`) that :::[example](./processedSchema.graphql) -- Core schema after processing -## Modifications to the API +# Overview -Within the API, +*This section is non-normative.* It describes the motivation behind the directives defined by this specification. + +A core schema which has been processed according to the inaccessible spec is a queryable graph, intended to be served by a [Data Core](https://specs.apollo.dev/core/v0.2/#sec-Actors). Various use cases require that fields and types should not be visible to or queried for by clients. The `@inaccessible` directive fulfills this requirement, providing schema authors a mechanism to specify which fields and types should be omitted from the processed schema. + +# Basic Requirements + +Schemas using {@inaccessible} must be valid [core schema documents](https://specs.apollo.dev/core/v0.2) and must reference [this specification](#). + +Here is an example `@core` usage: + +:::[example](./coreDirectives.graphql) -- required @core directives + +As described in the [core schema specification](https://specs.apollo.dev/core/v0.2/#sec-Prefixing), your schema may rename the `@inaccessible` directive by including an `as` argument to the `@core` directive which references this specification. All references to `@inaccessible` in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema. + +In order to use the directive described by this specification, GraphQL requires you to include the definition in your schema. + +:::[definition](inaccessible.spec.graphql) + +## Producer Responsibilities + +[Producers](https://specs.apollo.dev/core/v0.2/#sec-Actors) MUST include a definition of the directive compatible with the above definition and all usages in the document. + +## Processor Responsibilities + +The Processor is responsible for excluding all inaccessible elements from the API. Within the API, - Field Definitions marked with `@inaccessible` MUST be excluded - Object types marked with `@inaccessible` MUST be excluded @@ -51,9 +74,4 @@ Within the API, - Interfaces marked with `@inaccessible` MUST be excluded from the `extends` clause of all other interfaces - Union types marked with `@inaccessible` MUST be excluded -Note applying this process may result in an invalid schema. For example, fields which return `@inaccessible` types which are not themselves marked `@inaccessible` will now return an invalid type which is not present in the schema. This is intentional. `@inaccessible` does NOT cascade. If applying `@inaccessible` results in an invalid schema, the serving process SHOULD apply standard polices to determine whether or how to serve it. Generally, invalid schemas SHOULD NOT be served, though some server configurations—particularly those used for development—MAY OPTIONALLY elect to serve such schemas in a degraded mode. The semantics of such a mode are not within the scope of this spec. -""" -schema { query: Query } - - -directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION \ No newline at end of file +Note applying this process may result in an invalid schema. For example, fields which return `@inaccessible` types which are not themselves marked `@inaccessible` will now return an invalid type which is not present in the schema. This is intentional. `@inaccessible` does NOT cascade. If applying `@inaccessible` results in an invalid schema, the serving process SHOULD apply standard polices to determine whether or how to serve it. Generally, invalid schemas SHOULD NOT be served, though some server configurations—particularly those used for development—MAY OPTIONALLY elect to serve such schemas in a degraded mode. The semantics of such a mode are not within the scope of this spec. \ No newline at end of file diff --git a/inaccessible/v0.1/inaccessible.spec.graphql b/inaccessible/v0.1/inaccessible.spec.graphql new file mode 100644 index 0000000..218729f --- /dev/null +++ b/inaccessible/v0.1/inaccessible.spec.graphql @@ -0,0 +1 @@ +directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION \ No newline at end of file diff --git a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html b/inaccessible/v0.1/index.html similarity index 69% rename from .graphs/https/specs.apollo.dev/inaccessible/v0.1.html rename to inaccessible/v0.1/index.html index c35ae64..5482806 100644 --- a/.graphs/https/specs.apollo.dev/inaccessible/v0.1.html +++ b/inaccessible/v0.1/index.html @@ -2,7 +2,7 @@ -inaccessible v0.1 +Inaccessible - - - +
      -

      inaccessible v0.1

      +

      Inaccessible

      for removing elements from a core schema

      @@ -1551,8 +1063,11 @@

      inaccessible v0.1

    6. 2.1Processor
    7. -
    8. 3Example: Sensitive User Data
        -
      1. 3.1Modifications to the API
      2. +
      3. 3Example: Sensitive User Data
      4. +
      5. 4Overview
      6. +
      7. 5Basic Requirements
          +
        1. 5.1Producer Responsibilities
        2. +
        3. 5.2Processor Responsibilities
      @@ -1573,43 +1088,43 @@

      3Example: Sensitive User Data

      This section is non‐normative.

      We’ll refer to this example of a core schema with sensitive user data throughout the document:

      -
      Example № 1 Core schema example
      directive @link(as: String, url: String!, for: link__Purpose) repeatable on SCHEMA
      -directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
      +
      Example № 1 Core schema example
      directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
      +directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
       
      -enum link__Purpose {
      +enum core__Purpose {
         EXECUTION
         SECURITY
       }
       
       schema
      -  @link(url: "https://specs.apollo.dev/link/v0.2")
      -  @link(url: "https://specs.apollo.dev/inaccessible/v0.1", for: SECURITY)
      +  @core(feature: "https://specs.apollo.dev/core/v0.2")
      +  @core(feature: "https://specs.apollo.dev/inaccessible/v0.1", for: SECURITY)
       {
      -  query: Query
      +  query: Query
       }
       
       type Query {
      -  user(id: String!): User
      +  user(id: String!): User
       }
       
       type User {
      -  id: String! @inaccessible
      -  name: String!
      -  email: String!
      -  bankAccount: BankAccount
      -  accounts: [Account]
      +  id: String! @inaccessible
      +  name: String!
      +  email: String!
      +  bankAccount: BankAccount @inaccessible
      +  accounts: [Account]
       }
       
       type BankAccount @inaccessible {
      -  id: String!
      -  accountNumber: String!
      +  id: String!
      +  accountNumber: String!
       }
       
       type ForumAccount {
      -  handle: String!
      +  handle: String!
       }
       
      -union Account = BankAccount | ForumAccount
      +union Account = BankAccount | ForumAccount
       
       

      The schema above contains both a field (User.id) and type (BankAccount) that are marked as @inaccessible. These symbols should be omitted from the processed schema anywhere they would appear. When the processed schema below is generated from this core schema, notice what has been removed:

      @@ -1619,9 +1134,9 @@

      Example № 2 Core schema after processing
      directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
      +
      Example № 2 Core schema after processing
      directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
       
      -enum core__Purpose {
      +enum core__Purpose {
         EXECUTION
         SECURITY
       }
      @@ -1629,27 +1144,49 @@ 

      schema @core(feature: "https://specs.apollo.dev/core/v0.2") { - query: Query + query: Query } type Query { - user(id: String!): User + user(id: String!): User } type User { - name: String! - email: String! - accounts: [Account] + name: String! + email: String! + accounts: [Account] } type ForumAccount { - handle: String! + handle: String! } -union Account = ForumAccount

      -
      -

      3.1Modifications to the API

      -

      Within the API,

      +union Account = ForumAccount
      +

      +
      +

      4Overview

      +

      This section is non‐normative. It describes the motivation behind the directives defined by this specification.

      +

      A core schema which has been processed according to the inaccessible spec is a queryable graph, intended to be served by a Data Core. Various use cases require that fields and types should not be visible to or queried for by clients. The @inaccessible directive fulfills this requirement, providing schema authors a mechanism to specify which fields and types should be omitted from the processed schema.

      +
      +
      +

      5Basic Requirements

      +

      Schemas using @inaccessible must be valid core schema documents and must reference this specification.

      +

      Here is an example @core usage:

      +
      Example № 3 required @core directives
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.2")
      +  @core(feature: "https://specs.apollo.dev/inaccessible/v0.1") {
      +  query: Query
      +}
      +

      As described in the core schema specification, your schema may rename the @inaccessible directive by including an as argument to the @core directive which references this specification. All references to @inaccessible in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema.

      +

      In order to use the directive described by this specification, GraphQL requires you to include the definition in your schema.

      +
      directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
      +
      +

      5.1Producer Responsibilities

      +

      Producers MUST include a definition of the directive compatible with the above definition and all usages in the document.

      +
      +
      +

      5.2Processor Responsibilities

      +

      The Processor is responsible for excluding all inaccessible elements from the API. Within the API,

      • Field Definitions marked with @inaccessible MUST be excluded
      • Object types marked with @inaccessible MUST be excluded
      • @@ -1669,7 +1206,7 @@

        - +
        1. 1How to read this document
        2. 2Definitions @@ -1677,10 +1214,13 @@

          2.1Processor

        -
      • 3Example: Sensitive User Data - +
      • 3Example: Sensitive User Data
      • +
      • 4Overview
      • +
      • 5Basic Requirements +
          -
        1. 3.1Modifications to the API
        2. +
        3. 5.1Producer Responsibilities
        4. +
        5. 5.2Processor Responsibilities
      • diff --git a/inaccessible/v0.1/netlify.toml b/inaccessible/v0.1/netlify.toml new file mode 100644 index 0000000..0175f7e --- /dev/null +++ b/inaccessible/v0.1/netlify.toml @@ -0,0 +1,6 @@ +# Netlify Admin: https://app.netlify.com/sites/apollo-specs-inaccessible/ +# Docs: https://docs.netlify.com/configure-builds/file-based-configuration/ + +[build] + command = "npm run build" + publish = ".dist/" diff --git a/inaccessible/v0.1/package.json b/inaccessible/v0.1/package.json new file mode 100644 index 0000000..a047a10 --- /dev/null +++ b/inaccessible/v0.1/package.json @@ -0,0 +1,19 @@ +{ + "name": "spec-site", + "private": true, + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "rsync -avz --exclude .dist . .dist && spec-md spec.md > .dist/index.html", + "dev": "npm run build || true && chokidar '**/*' -i '.dist' -c 'npm run build'" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@queerviolet/speck": "https://github.com/queerviolet/speck.git#main", + "chokidar-cli": "^2.1.0", + "watch": "^1.0.2" + } +} diff --git a/inaccessible/processedSchema.graphql b/inaccessible/v0.1/processedSchema.graphql similarity index 100% rename from inaccessible/processedSchema.graphql rename to inaccessible/v0.1/processedSchema.graphql diff --git a/inaccessible/schema.graphql b/inaccessible/v0.1/schema.graphql similarity index 60% rename from inaccessible/schema.graphql rename to inaccessible/v0.1/schema.graphql index 96d9f7f..036a01f 100644 --- a/inaccessible/schema.graphql +++ b/inaccessible/v0.1/schema.graphql @@ -1,14 +1,14 @@ -directive @link(as: String, url: String!, for: link__Purpose) repeatable on SCHEMA +directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION -enum link__Purpose { +enum core__Purpose { EXECUTION SECURITY } schema - @link(url: "https://specs.apollo.dev/link/v0.2") - @link(url: "https://specs.apollo.dev/inaccessible/v0.1", for: SECURITY) + @core(feature: "https://specs.apollo.dev/core/v0.2") + @core(feature: "https://specs.apollo.dev/inaccessible/v0.1", for: SECURITY) { query: Query } @@ -21,7 +21,7 @@ type User { id: String! @inaccessible name: String! email: String! - bankAccount: BankAccount + bankAccount: BankAccount @inaccessible accounts: [Account] } diff --git a/link/v1.0/prefixing.graphql b/link/prefixing.graphql similarity index 100% rename from link/v1.0/prefixing.graphql rename to link/prefixing.graphql diff --git a/link/scope.md b/link/scope.md index e844e5e..2fbc80f 100644 --- a/link/scope.md +++ b/link/scope.md @@ -1,7 +1,8 @@ +Blah blah blah # Renaming link -It is possible to rename {@link} with the same {@link.as} mechanism used for all links: +It is possible to rename {@link} with the same [`as`](#@link/as) mechanism used for all links: ```graphql example -- Renaming {@link} to {@linkOther} schema @@ -60,4 +61,4 @@ Feature specs MUST prefix the following schema elements: - the names of any object types, interfaces, unions, enums, or input object types defined by the feature - the names of any directives introduced in the schema, with the exception of the *root directive*, which must have the same name as the schema -:::[example](prefixing.graphql) -- Prefixing + diff --git a/link/v1.0/index.html b/link/v1.0/index.html new file mode 100644 index 0000000..3625bac --- /dev/null +++ b/link/v1.0/index.html @@ -0,0 +1,1809 @@ + + + + +link v1.0 + + + + + +
        +
        +

        link v1.0

        +
        +

    9. + + +
      StatusDraft
      Version1.0
      + + +

      Core schemas provide tools for linking definitions from different GraphQL schemas together into one.

      +
      Example № 1 linking a directive from another schema
      extend schema
      +  # you link @link by @linking link 
      +  @link(url: "https://specs.apollo.dev/link/v1.0")
      +  #          👇🏽 schemas are identified by a url
      +  @link(url: "https://internal.example.com/admin")
      +
      +type Query {
      +  allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced
      +}
      +
      +
      Example № 2 importing a directive from another schema
      extend schema
      +  @link(url: "https://specs.apollo.dev/link/v1.0")
      +  #          specific definitions can be imported 👇🏽 
      +  @link(url: "https://internal.example.com/admin", import: ["@adminOnly"])
      +
      +type Query {
      +  allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported
      +}
      +
      +

      This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc.

      +
      + +
      +
      +

      1Global Graph References

      +

      Within a core schema document, every top‐level definition and reference (for example, every named type and directive) has a position in the global graph. This position is captured by a global graph reference.

      +

      A global graph reference (or gref) is a (link url, element) pair.

      +

      The URL may be null, to represent local definitions within a schema that is not bound to a URL. Otherwise it must be a valid link url in canonical form.

      +

      The element may be:

      +
        +
      • Directive(name) — a directive
      • +
      • Type(name) — a named type of any kind
      • +
      • Schema(name?) — a further link to yet another schema. name may be null to indicate the schema itself.
      • +
      +

      Implementations may represent these internally in a variety of ways. They also have a canonical string form, which is used in Import

      +
        +
      • Directive(name) is represented with “@name
      • +
      • Type(name) is simply represented with “name
      • +
      • Schema(name) is represented with “name::”
      • +
      +

      Types and directives are separated in this way because that’s how GraphQL does it—you can have a User type and a @User directive in the same schema (though GraphQL naming conventions would suggest againt it).

      +
      +

      1.1URL representation

      +

      Since link urls explicitly cannot have fragments, we can represent global graph references as a URL with a fragment. For example, a reference to this document’s @link directive can be rendered as https://specs.apollo.dev/link/v1.0#@link. A reference to this document’s Import scalar can be rendered as https://specs.apollo.dev/link/v1.0#Import.

      +

      This is particularly convenient for providing links to documentation. We’ll use this form in examples throughout this document.

      +
      +
      +
      +

      2Attribution

      +

      As mentioned previously, core schemas connect every definition, directive usage, and named type reference with a global graph reference. Taking the example from the introduction:

      +
      Example № 3 global graph references (shown in URL form)
      extend schema
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://specs.apollo.dev/link/v1.0")
      +
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://internal.example.com/admin")
      +
      +#   👇🏽 🌍 #Query (note: this schema has no url, so this gref's url part is null)
      +type Query {
      +  #               👇🏽 🌍 https://internal.example.com/admin#@adminOnly
      +  allUsers: [User] @admin__adminOnly
      +  #         🖕🏽 🌍 #User
      +}
      +
      +

      This attribution does not change if we import some names, even renaming them:

      +
      Example № 4 global graph references with imports
      extend schema
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://specs.apollo.dev/link/v1.0")
      +
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://internal.example.com/admin", import: [{ name: "@adminOnly", as: "@admin" }])
      +
      +#   👇🏽 🌍 #Query (note: this schema has no url, so the url part is null)
      +type Query {
      +  #               👇🏽 🌍 https://internal.example.com/admin#@adminOnly
      +  allUsers: [User] @admin
      +  #         🖕🏽 🌍 #User
      +}
      +
      +

      This indirection is the whole point. A core‐aware security layer scanning the document for fields marked with https://internal.example.com/admin#@adminOnly will find it, regardless of its local name within the document.

      +

      Attribution functions the same for all references and definitions within the document. For example, if we include a definition of the @admin directive from the example above (as we must, if the schema is to be fully valid) it too will be attributed to the foreign schema:

      +
      Example № 5 global graph references for definitions
      extend schema
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://specs.apollo.dev/link/v1.0")
      +
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://internal.example.com/admin", import: [{ name: "@adminOnly", as: "@admin" }])
      +
      +#   👇🏽 🌍 #Query (note: this schema has no url, so the url part is null)
      +type Query {
      +  #               👇🏽 🌍 https://internal.example.com/admin#@adminOnly
      +  allUsers: [User] @admin
      +  #         🖕🏽 🌍 #User
      +}
      +
      +#        👇🏽 🌍 https://internal.example.com/admin#@adminOnly
      +directive @admin on FIELD_DEFINITION
      +
      +
      +

      2.1Identifying the document's own URL

      +

      The document’s own URL can be specified with the @id directive:

      +
      Example № 6 global graph references with @id
      extend schema
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@id
      +  @id(url: "https://api.example.com/myself")
      +
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@id"])
      +
      +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link
      +  @link(url: "https://internal.example.com/admin", import: [{ name: "@adminOnly", as: "@admin" }])
      +
      +#   👇🏽 🌍 https://api.example.com/myself#Query
      +type Query {
      +  #               👇🏽 🌍 https://internal.example.com/admin#@adminOnly
      +  allUsers: [User] @admin
      +  #         🖕🏽 🌍 https://api.example.com/myself#User
      +}
      +
      +

      Using @id is not, strictly speaking, necessary. A URL can be associated with a document in any number of ways (for example, a processor could associate the schema with the URL where it found it. However, using @id makes the document self‐describing; core‐aware processors will correctly attribute definitions and references within such documents, regardless of where they were found.

      +
      +
      +
      +

      3Scope

      +

      Core schemas have a document‐wide scope. A document’s scope is a map of ElementBinding. The scope is constructed from a document’s @link and @id directives and is used to attribute definitions and references within the document.

      +

      Elements are the same as in global graph references. When used as scope keys, they carry the following meanings:

      +
        +
      • Schema(name) — a schema @linked from the document. name can be used as a prefix for definitions and references within the document, and name MUST either be a valid prefix or null, indicating the present schema.
      • +
      • Directive(name) — a directive imported into the document
      • +
      • Type(name) — a type imported into the document
      • +
      +

      A Binding contains:

      +
        +
      • gref: GRef — the global graph reference which is the target of the binding
      • +
      • implicit: Bool — indicating whether the binding was explicitly imported or created implicitly. Implicit bindings may be overwritten by explicit bindings and will not be formed if an explicit binding for the item alreaady exists
      • +
      +

      Similar to a gref‘s elements, different types of scoped items can have the same name without conflict. For example, a scope can contain both a type and schema named “User”, although this should generally be avoided if possible.

      +

      The global graph reference mapped to the target MUST match the item’s type—a scope cannot map a schema to a directive, for instance. The algorithms provided in this document ensure this is always the case.

      + +
      +

      3.2Entry added by @id

      +

      @id adds just one entry into the scope, binding the current document to the given URL:

      +
      Example № 13 @id adds a self‐binding into the scope
        @id(url: "https://example.com/myself")
      +  # 1. Schema() -> https://example.com/myself (explicit)
      +
      +
      +
      +

      3.3Bootstrapping

      +

      Documents can @link link itself. Indeed, if they MUST do so if they use @link at all and are intended to be fully valid:

      +

      The bootstrapping @link MUST be the first @link in the document. Other directives may precede it, including directives which have been @linked.

      +

      There is otherwise nothing special or restricted about these “bootstrapping links”. Documents MAY rename @link—either with as: or import: or both:

      +
      +
      +
      +

      4Fully Valid Core Schemas

      +

      For a document to be a fully valid core schema:

      +
        +
      1. It MUST be a valid GraphQL schema. Amongst other things, this means that it MUST contain definitions for all types and directives it references, including those from foreign schemas
      2. +
      3. If it includes any @links, it MUST include a bootstrap link which MUST precede all other @links in the document
      4. +
      5. It MAY include an @id, particularly if other schemas are meant to link to it (e.g. if the document provide directives for use on other schemas)
      6. +
      +

      Good news: this means that every valid GraphQL schema which does not use @link is automatically a fully valid core schema. Otherwise‐valid schemas which use @link simply have to include a bootstrap to become fully valid core schemas.

      +
      +Note +The bootstrap link is required in order to properly identify the version of the link spec in use.
      +
      +
      +

      5Definitions

      + +
      +

      5.2@id

      +
      directive @id(url: String!) on SCHEMA
      +
      +

      Identify the current document by its URL. The URL is interpreted identically to @link‘s url: argument.

      +
      +
      +

      5.3Import

      +
      scalar Import
      +
      +

      An element, possibly aliased, to import into the document.

      +

      Import can take the form of a string:

      +
      Example № 20 import a string name
        @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@link", "Purpose"])
      +
      +

      or an object with name and (optionally as):

      +
      Example № 21 import an aliased name
        @link(url: "https://example.com/, import: [{
      +    name: "@example",
      +    as: "@eg"
      +  }, { name: "Purpose", as: "LinkPurpose" }])
      +
      +

      name and as MUST be of the same type:

      +
      Counter Example № 22 incorrectly importing a type as a directive
        @link(url: "https://example.com/, import: [{
      +    name: "SomeType",
      +    as: "@someDirective"
      +  }])
      +
      +

      Imports cannot currently reference transitive schemas:

      +
      Counter Example № 23 incorrectly importing a transitive schema reference
        @link(url: "https://example.com/, import: "otherSchema::")
      +
      +
      +Note +Future versions may support this.
      +
      +
      +

      5.4Purpose

      +

      The role of a feature referenced with @link.

      +

      This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail‐open behavior of core schema consumers is undesirable.

      +
      +Note +we’ll refer to directives from features which are for: SECURITY or for: EXECUTION as “SECURITY directives” and “EXECUTION directives”, respectively.
      +
      +

      5.4.1SECURITY

      +

      SECURITY links provide metadata necessary to securely resolve fields. For instance, a hypothetical auth feature may provide an @auth directive to flag fields which require authorization. If a data core does not support the auth feature and serves those fields anyway, these fields will be accessible without authorization, compromising security.

      +

      Security‐conscious consumers MUST NOT serve a field if:

      +
        +
      • the schema definition has any unsupported SECURITY directives,
      • +
      • the field’s parent type definition has any unsupported SECURITY directives,
      • +
      • the field’s return type definition has any unsupported SECURITY directives, or
      • +
      • the field definition has any unsupported SECURITY directives
      • +
      +

      Such fields are not securely resolvable. Security‐conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it.

      +

      Less security‐conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development.

      +

      More security‐conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema.

      +
      +
      +

      5.4.2EXECUTION

      +

      EXECUTION features provide metadata necessary to correctly resolve fields. For instance, a hypothetical ts feature may provide a @ts__resolvers annotation which references a TypeScript module of field resolvers. A consumer which does not support the ts feature will be unable to correctly resolve such fields.

      +

      Consumers MUST NOT serve a field if:

      +
        +
      • the schema’s definition has any unsupported EXECUTION directives,
      • +
      • the field’s parent type definition has any unsupported EXECUTION directives,
      • +
      • the field’s return type definition has any unsupported EXECUTION directives, or
      • +
      • the field definition has any unsupported EXECUTION directives
      • +
      +

      Such fields are unresolvable. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema.

      +
      +
      +
      +
      +

      6Appendix: Validations & Algorithms

      +
      +

      6.1Construct the document's scope

      +

      Visit every @link and @id within the document to construct the document’s scope.

      +
      +Note +This algorithm Reports errors. Depending on their needs, implementations MAY decide to fail immediately in the face of these errors, or may elect to continue processing the document
      +
      +ConstructScope(document, baseScope)
        +
      1. Let scope be baseScope if provided. Otherwise, it begins as an empty map of ElementBinding
      2. +
      3. For each schema definition or schema extension schemaDef in document,
          +
        1. For each directive dir on schemaDef,
            +
          1. If Locate(scope, dir) is the gref https://specs.apollo.dev/link/v1.0#@link...
          2. +
          3. ...or LocateBound(scope, dir) is null and IsBootstrap(dir) Then
              +
            1. For each (element, binding) from BindingsFromLink(dir)
                +
              1. If binding is implicit and element exists in scope and is explicit, Then Continue
              2. +
              3. If binding is implicit and element exists in scope and is implicit...
              4. +
              5. Or If binding is explicit and element exists in scope and is explicit Then
                  +
                1. Report ❌ NameConflict
                2. +
                +
              6. +
              7. Insert elementbinding into scope
              8. +
              +
            2. +
            +
          4. +
          +
        2. +
        +
      4. +
      5. For each schema definition or schema extension schemaDef in document,
          +
        1. For each directive dir on schemaDef,
            +
          1. If Locate(scope, dir) is the gref https://specs.apollo.dev/link/v1.0#@id...
              +
            1. If directive does not have a url argument or its url argument is an invalid URL
                +
              1. Fail ❌ BadId
              2. +
              +
            2. +
            3. Let url be the canonical form of directive‘s url
            4. +
            5. Insert Schema() ⇒ Binding(gref: GRef(url, Schema()), implicit: false)
            6. +
            +
          2. +
          +
        2. +
        +
      6. +
      7. Return scope
      8. +
      +
      +
      + +
      +

      6.3Detecting a bootstrap directive

      +

      Returns true if a directive is a bootstrap. Otherwise, returns false.

      +
      +IsBootstrap(directive)
        +
      1. If directive does not have a string‐valued url argument, Then Return false
      2. +
      3. Let url be the canonical form of the directive‘s url argument, with all meaningless components stripped
      4. +
      5. If url is https://specs.apollo.dev/link/v1.0,
          +
        1. Let testScope be an empty map of ElementBinding
        2. +
        3. For each (element, binding) from BindingsFromLink(directive)
            +
          1. Insert elementbinding into testScope
          2. +
          +
        4. +
        5. If Locate(testScope, directive) is a binding with the gref https://specs.apollo.dev/link/v1.0#@link, Then
            +
          1. Return true
          2. +
          +
        6. +
        +
      6. +
      7. Otherwise, Return false
      8. +
      +
      +
      +
      +

      6.4Locating definitions and references

      +

      Locate a definition or reference within the document’s scope, returning a gref.

      +

      defOrRef must be one of:

      +
        +
      • a definition node with a name
      • +
      • an extension node with a name
      • +
      • a directive
      • +
      +
      +Locate(scope, defOrRef)
        +
      1. If LocateBound(scope, defOrRef) is not null Then Return LocateBound(scope, defOrRef)
      2. +
      3. Let selfReference be the item Schema()
      4. +
      5. Let myself be the URL of the schema returned from Lookup(scope, selfReference), or Schema(null) if none was found
      6. +
      7. Let name be the name of defOrRef
      8. +
      9. If defOrRef is a named type reference, extension, or definition, Then
          +
        1. Return the gref (myself, Type(name))
        2. +
        +
      10. +
      11. Otherwise, Return the gref (myself, Directive(name))
      12. +
      +
      +
      +LocateBound(scope, defOrRef)
        +
      1. Let (schema, element) be the pair returned from GetPathFrom(defOrRef)
      2. +
      3. If schema is not null and exists in scope, Then
          +
        1. Let foundGraph be the URL of the binding found by looking up schema in scope
        2. +
        3. Return the gref (foundGraph, element)
        4. +
        +
      4. +
      5. If schema is null and element exists in scope, Then
          +
        1. Let foundElement be the gref of the binding found by looking up element in scope,
        2. +
        3. Return foundElement
        4. +
        +
      6. +
      7. Otherwise, Return null
      8. +
      +
      +
      +
      +
      +

      7Appendix: Versioning

      + + + + + + +
      +PositiveDigit
      1|2|3|4|5|6|7|8|9
      +
      +

      Specs are versioned with a subset of a Semantic Version Number containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form vMajor.Minor, where both integers ≥ 0.

      +
      Example № 24 Valid version tags
      v2.2
      +v1.0
      +v1.1
      +v0.1
      +
      +

      As specified by semver, spec authors SHOULD increment the:

      +
        +
      • MAJOR version when you make incompatible API changes,
      • +
      • MINOR version when you add functionality in a backwards compatible manner
      • +
      +
      +

      Patch and pre‐release qualifiers are judged to be not particularly meaningful in the context of GraphQL schemas, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch‐level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution.

      +

      As with semver, the 0.x version series is special: there is no expectation of compatibility between versions 0.x and 0.y. For example, a processor must not activate implementation 0.4 to satisfy a requested version of 0.2.

      +
      +

      7.1Satisfaction

      +

      Given a version requested by a document and an available version of an implementation, the following algorithm will determine if the available version can satisfy the requested version:

      +
      +Satisfies(requested, available)
        +
      1. If requested.Majoravailable.Major, return false
      2. +
      3. If requested.Major = 0, return requested.Minor = available.Minor
      4. +
      5. Return requested.Minoravailable.Minor
      6. +
      +
      +
      +
      +

      §Index

      1. BindingsFromLink
      2. ConstructScope
      3. Digit
      4. IsBootstrap
      5. Locate
      6. LocateBound
      7. Major
      8. Minor
      9. NumericIdentifier
      10. PositiveDigit
      11. Satisfies
      12. Version
      13. VersionTag
      + + +
      + + +
      + + + diff --git a/link/v1.0/link-v1.0.graphql b/link/v1.0/link-v1.0.graphql deleted file mode 100644 index f8de95e..0000000 --- a/link/v1.0/link-v1.0.graphql +++ /dev/null @@ -1,196 +0,0 @@ -@id(url: "https://specs.apollo.dev/link/v1.0") - -""" -```raw html - - - -
      StatusDraft
      Version1.0
      - - -``` - -[intro to core schemas](core-schemas-intro.md) - -# Scoping - -[scope](../scope.md) - -# Versioning - -[Versioning](../versioning.md) - -# Processing Schemas - -```mermaid diagram -graph LR - schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") - proc-->output(["📄 Output Schema"]):::file - classDef file fill:none,color:#22262E; - style proc fill:none,stroke:fuchsia,color:fuchsia; -``` - -A common use case is that of a processor which consumes a valid input schema and generates an output schema. - -The general guidance for processor behavior is: don't react to what you don't understand. - -Specifically, processors: - - SHOULD pass through {@link} directives which reference unknown feature URLs - - SHOULD pass through prefixed directives, types, and other schema elements - - SHOULD pass through directives which are not [associated with](#AssignFeatures) a {@link} feature - -Processors MAY accept configuration which overrides these default behaviors. - -Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. - -# Algorithms - -[Algorithms](algorithms.md) - -""" -schema -{ query: Query } - - -""" -Link a foreign schema by its URL. -""" -directive @link( - """ - The foreign schema's URL. - - Link URLs serve two main purposes: - - Providing a unique identifier for the foreign schema - - Directing human readers to documentation about the foreign schema - - Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. - - Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): - - ```html diagram -- Basic anatomy of a link URL - - https://spec.example.com/a/b/c/mySchema/v1.0 - - ``` - - The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). - - Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. - - ```html diagram -- Ignoring meaningless parts of a URL - - https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag - - ``` - - All of these are valid arguments to `url`, and their interpretations: - - | url | normalized url | name | version | - | ------------------------------------------------- | ------------------------------------------ | ------- | -------- | - | https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | - | https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | - | https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | - | https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | - | https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | - - If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. - """ - url: String!, - - """ - Change the [namespace prefix](#sec-Prefixing) assigned to this schema. - - The name must be a valid GraphQL identifier, and must not contain the namespace separator ({"__"}). - - By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. - - Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be assigned the provided prefix, regardless of the `name` present in the URL (or the lack of one). - - ```graphql example -- Using {@link}(url:, as:) to use a feature with a custom name - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://spec.example.com/example/v1.0", as: "eg") - { - query: Query - } - - type User { - # Specifying `as: "eg"` transforms @example into @eg - name: String @eg(data: ITEM) - } - - # Additional specified schema elements must have their prefixes set - # to the new name. - enum eg__Data { - ITEM - } - - # Name transformation must also be applied to definitions pulled in from - # specifications. - directive @eg(data: eg__Data) on FIELD_DEFINITION - - directive @link(url: String!, as: String) repeatable on SCHEMA - ``` - """ - as: String, - - """ - Import definitions into the local namespace. - """ - import: [Import], - - """ - An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. - - By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. - - This behavior is different for {@link}s with a specified purpose: - - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it - - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema - """ - for: Purpose) - repeatable on SCHEMA - -""" -TK describe an import -""" -scalar Import - -""" -The role of a feature referenced with {@link}. - -This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. - -Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. -""" -enum Purpose { - """ - `SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. - - Security-conscious consumers MUST NOT serve a field if: - - the schema definition has **any** unsupported SECURITY directives, - - the field's parent type definition has **any** unsupported SECURITY directives, - - the field's return type definition has **any** unsupported SECURITY directives, or - - the field definition has **any** unsupported SECURITY directives - - Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. - - Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. - - More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. - - Consumers MUST NOT serve a field if: - - the schema's definition has **any** unsupported EXECUTION directives, - - the field's parent type definition has **any** unsupported EXECUTION directives, - - the field's return type definition has **any** unsupported EXECUTION directives, or - - the field definition has **any** unsupported EXECUTION directives - - Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. - """ - EXECUTION -} diff --git a/link/v1.0/link-v1.0.md b/link/v1.0/link-v1.0.md new file mode 100644 index 0000000..04f5050 --- /dev/null +++ b/link/v1.0/link-v1.0.md @@ -0,0 +1,647 @@ +# link v1.0 + +```raw html + + + +
      StatusDraft
      Version1.0
      + + +``` + +Core schemas provide tools for linking definitions from different GraphQL schemas together into one. + +```graphql example -- linking a directive from another schema +extend schema + # you link @link by @linking link + @link(url: "https://specs.apollo.dev/link/v1.0") + # 👇🏽 schemas are identified by a url + @link(url: "https://internal.example.com/admin") + +type Query { + allUsers: [User] @admin__adminOnly # 👈🏽 remote identifier, namespaced +} +``` + +```graphql example -- importing a directive from another schema +extend schema + @link(url: "https://specs.apollo.dev/link/v1.0") + # specific definitions can be imported 👇🏽 + @link(url: "https://internal.example.com/admin", import: ["@adminOnly"]) + +type Query { + allUsers: [User] @adminOnly # 👈🏽 remote identifier, imported +} +``` + +This document introduces a set of conventions for linking and namespacing within GraphQL schemas. Core schemas are not a new kind of document and do not introduce any new syntax—they are just GraphQL schemas which can be interpreted according to the conventions outlined in this doc. + +# Global Graph References + +Within a core schema document, every top-level definition and reference (for example, every named type and directive) has a position in the global graph. This position is captured by a *global graph reference*. + +A global graph reference (or *gref*) is a (*[link url](#@link/url)*, *element*) pair. + +The URL may be {null}, to represent local definitions within a schema that is not bound to a URL. Otherwise it must be a [valid link url in canonical form](#@link/url). + +The *element* may be: +- Directive({name}) — a directive +- Type({name}) — a named type of any kind +- Schema({name}?) — a further link to yet another schema. {name} may be {null} to indicate the schema itself. + +Implementations may represent these internally in a variety of ways. They also have a canonical string form, which is used in [{Import}](#Import) + +- Directive({name}) is represented with "@{name}" +- Type({name}) is simply represented with "{name}" +- Schema({name}) is represented with "{name}::" + +Types and directives are separated in this way because that's how GraphQL does it—you can have a `User` type and a `@User` directive in the same schema (though GraphQL naming conventions would suggest againt it). + +## URL representation + +Since [link urls](#Url) explicitly cannot have fragments, we can represent global graph references as a URL with a fragment. For example, a reference to this document's {@link} directive can be rendered as `https://specs.apollo.dev/link/v1.0#@link`. A reference to this document's {Import} scalar can be rendered as `https://specs.apollo.dev/link/v1.0#Import`. + +This is particularly convenient for providing links to documentation. We'll use this form in examples throughout this document. + +# Attribution + +As mentioned [previously](#sec-Global-Graph-References), core schemas connect every definition, directive usage, and named type reference with a global graph reference. Taking the example from the introduction: + +```graphql example -- global graph references (shown in URL form) +extend schema +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://specs.apollo.dev/link/v1.0") + +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://internal.example.com/admin") + +# 👇🏽 🌍 #Query (note: this schema has no url, so this gref's url part is null) +type Query { + # 👇🏽 🌍 https://internal.example.com/admin#@adminOnly + allUsers: [User] @admin__adminOnly + # 🖕🏽 🌍 #User +} +``` + +This attribution *does not change* if we [`import`](#@link/import) some names, even renaming them: + +```graphql example -- global graph references with imports +extend schema +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://specs.apollo.dev/link/v1.0") + +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://internal.example.com/admin", import: [{ name: "@adminOnly", as: "@admin" }]) + +# 👇🏽 🌍 #Query (note: this schema has no url, so the url part is null) +type Query { + # 👇🏽 🌍 https://internal.example.com/admin#@adminOnly + allUsers: [User] @admin + # 🖕🏽 🌍 #User +} +``` + +This indirection is the whole point. A core-aware security layer scanning the document for fields marked with `https://internal.example.com/admin#@adminOnly` will find it, regardless of its local name within the document. + +Attribution functions the same for all references and definitions within the document. For example, if we include a definition of the `@admin` directive from the example above (as we must, if the schema is to be fully valid) it too will be attributed to the foreign schema: + +```graphql example -- global graph references for definitions +extend schema +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://specs.apollo.dev/link/v1.0") + +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://internal.example.com/admin", import: [{ name: "@adminOnly", as: "@admin" }]) + +# 👇🏽 🌍 #Query (note: this schema has no url, so the url part is null) +type Query { + # 👇🏽 🌍 https://internal.example.com/admin#@adminOnly + allUsers: [User] @admin + # 🖕🏽 🌍 #User +} + +# 👇🏽 🌍 https://internal.example.com/admin#@adminOnly +directive @admin on FIELD_DEFINITION +``` + +## Identifying the document's own URL + +The document's own URL can be specified with the [`@id` directive](#@id): + +```graphql example -- global graph references with {@id} +extend schema +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@id + @id(url: "https://api.example.com/myself") + +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@id"]) + +# 👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#@link + @link(url: "https://internal.example.com/admin", import: [{ name: "@adminOnly", as: "@admin" }]) + +# 👇🏽 🌍 https://api.example.com/myself#Query +type Query { + # 👇🏽 🌍 https://internal.example.com/admin#@adminOnly + allUsers: [User] @admin + # 🖕🏽 🌍 https://api.example.com/myself#User +} +``` + +Using `@id` is not, strictly speaking, necessary. A URL can be associated with a document in any number of ways (for example, a processor could associate the schema with the URL where it found it. However, using [{@id}](#@id) makes the document self-describing; core-aware processors will correctly attribute definitions and references within such documents, regardless of where they were found. + +# Scope + +Core schemas have a document-wide *scope*. A document's scope is a map of {Element} ==> {Binding}. The scope is constructed from a document's [@link](#@link) and [@id](#@id) directives and is used to [attribute](#sec-Attribution) definitions and references within the document. + +Elements are the same as in [global graph references](#sec-Global-Graph-References). When used as scope keys, they carry the following meanings: +- Schema({name}) — a schema {@link}ed from the document. {name} can be used as a [prefix](#sec-Prefixing) for definitions and references within the document, and {name} MUST either be a valid prefix or {null}, indicating the present schema. +- Directive({name}) — a directive [imported](#@link/import) into the document +- Type({name}) — a type [imported](#@link/import) into the document + +A {Binding} contains: +- {gref}: GRef — the [global graph reference](#sec-Global-Graph-References) which is the target of the binding +- {implicit}: Bool — indicating whether the binding was explicitly imported or created implicitly. Implicit bindings may be overwritten by explicit bindings and will not be formed if an explicit binding for the item alreaady exists + +Similar to a [gref](#sec-Global-Graph-References)'s elements, different types of scoped items can have the same name without conflict. For example, a scope can contain both a type and schema named "User", although this should generally be avoided if possible. + +The [global graph reference](#sec-Global-Graph-References) mapped to the target MUST match the item's type—a scope cannot map a schema to a directive, for instance. The [algorithms](#sec-Algorithms) provided in this document ensure this is always the case. + +## Entries added by @link + +A {@link} without any imports introduces two entries into the scope: +1. an explicit binding to the foreign schema, and +2. an implicit binding to a directive with the foreign schema's own name, if its URL [has a name](#@link/url). This somewhat-blessed directive is the schema's "root directive" + +```graphql example -- {@link} bringing a single schema into scope + @link(url: "https://example.com/foreignSchema") + # 1. Schema("foreignSchema") -> https://example.com/foreignSchema (explicit) + # 2. Directive("foreignSchema") -> https://example.com/foreignSchema#@foreignSchema (implicit) +``` + +{@link}ing a foreign schema whose URL does not have a name will create a schema binding if and only if [`as:`](#@link/as) is specified, and will never create a root directive reference: + +```graphql example -- {@link} bringing a single schema into scope + # 👇🏽 url does not have a name + @link(url: "https://api.example.com", as: "example") + # 1. Schema("example") -> https://example.com#example (explicit) +``` + +A {@link} with imports will add these entries to the scope, in addition to entries for each import: + +```graphql example -- {@link} importing items into the scope + @link(url: "https://example.com/foreignSchema", import: ["SomeType", "@someDirective"]) + # 1. Schema("foreignSchema") -> https://example.com/foreignSchema (explicit) + # 2. Directive("foreignSchema") -> https://example.com/foreignSchema#@foreignSchema (implicit) + # 3. Type("SomeType") -> https://example.com/foreignSchema#SomeType (explicit) + # 4. Directive("someDirective") -> https://example.com/foreignSchema#@someDirective (explicit) +``` + +Specifying [`as:`](#@link/as) changes the names of the scope items, but not their bound grefs: + +```graphql example -- {@link} conflicting schema names + @link(url: "https://example.com/foreignSchema", as: "other") + # 1. Schema("other") -> https://example.com/foreignSchema (explicit) + # 2. Directive("other") -> https://example.com/foreignSchema#@foreignSchema (implicit) +``` + +It is not an error to overwrite an implicit binding with an explicit one: + +```graphql example -- {@link} import overriding an implicit binding + @link(url: "https://example.com/foreignSchema") + # 1. Schema("foreignSchema") -> https://example.com/foreignSchema (explicit) + # 2. Directive("foreignSchema") -> https://example.com/foreignSchema#@foreignSchema (implicit) + + # (2) will be subsequently overwritten: + @link(url: "https://other.com/otherSchema, import: ["@foreignSchema"]) + # 3. Schema("otherSchema") -> https://other.com/otherSchema (explicit) + # 4. Directive("otherSchema") -> https://other.com/otherSchema#@otherSchema (implicit) + # 5. Directive("foreignSchema") -> https://other.com/otherSchema#@foreignSchema (explicit, overwrites (2)) +``` + +But it is an error to overwrite an explicit binding, or for two implicit bindings to overlap: + +```graphql counter-example -- {@link} conflicting schema names + @link(url: "https://example.com/foreignSchema") + # 1. Schema("foreignSchema") -> https://example.com/foreignSchema (explicit) + # 2. Directive("foreignSchema") -> https://example.com/foreignSchema#@foreignSchema (implicit) + + @link(url: "https://other.com/foreignSchema") + # ❌ Schema("foreignSchema") -> https://other.com/foreignSchema (explicit) + # (error, conflicts with with (1)) + # ❌ Directive("foreignSchema") -> https://other.com/otherSchema#otherSchema (implicit) + # (error, conflicts with (2)) +``` + +Document processors MAY reject schemas with such errors outright. + +Permissive processors (for example, a language server which wants to provide best-effort attribution even in the face of document errors) MAY choose to process such documents even in the face of conflicts. Such processors SHOULD include the first (in document order) binding in the scope, and reject subsequent bindings. Such processors SHOULD also provide error messages listing *all* {@link}s which are in conflict. + +## Entry added by @id + +[@id](#@id) adds just one entry into the scope, binding the current document to the given URL: + +```graphql example -- {@id} adds a self-binding into the scope + @id(url: "https://example.com/myself") + # 1. Schema() -> https://example.com/myself (explicit) +``` + +## Bootstrapping + +Documents can {@link} link itself. Indeed, if they MUST do so if they use [@link](#@link) at all and are intended to be [fully valid](#sec-Fully-Valid-Core-Schemas): + +```graphql example -- bootstrapping {@link} +extend schema + @link(url: "https://specs.apollo.dev/link/v1.0") +``` + +The bootstrapping {@link} MUST be the first {@link} in the document. Other directives may precede it, including directives which have been {@link}ed. + +```graphql example -- bootstrapping {@link} and using {@id} before doing so +extend schema + @id(url: "https://api.example.com") + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@id"]) +``` + +There is otherwise nothing special or restricted about these "bootstrapping links". Documents MAY rename {@link}—either with [`as:`](#@link/as) or [`import:`](#@link/import) or both: + +```graphql example -- bootstrapping {@link} with a different name +extend schema + @core(url: "https://specs.apollo.dev/link/v1.0", as: "core") +``` + +```graphql example -- importing {@link} with a different name +extend schema + @core(url: "https://specs.apollo.dev/link/v1.0", import: [{ name: "@link", as: "@core" }]) +``` + +```graphql example -- importing {@link} and other things simultaneously +extend schema + @id(url: "https://api.example.com") + @foo(url: "https://specs.apollo.dev/link/v1.0", import: [ + {name: "@link", as: "@core"}, + "@id" + ]) +``` + +# Fully Valid Core Schemas + +For a document to be a fully valid core schema: + +1. It MUST be a valid GraphQL schema. Amongst other things, this means that it MUST contain definitions for all types and directives it references, including those from foreign schemas +2. If it includes any {@link}s, it MUST include a [bootstrap link](#sec-Bootstrapping) which MUST precede all other {@link}s in the document +3. It MAY include an {@id}, particularly if other schemas are meant to link to it (e.g. if the document provide directives for use on other schemas) + +Good news: this means that every valid GraphQL schema which *does not* use {@link} is automatically a fully valid core schema. Otherwise-valid schemas which use {@link} simply have to include a [bootstrap](#sec-Bootstrapping) to become fully valid core schemas. + +Note: The bootstrap link is required in order to properly identify the [version](#sec-Versioning) of the link spec in use. + +# Definitions + +##! @link + +```graphql definition +directive @link(url: String!, as: String, import: [Import]) +``` + +Link a foreign schema by its URL. + +###! url: String! + +The foreign schema's URL. + +Link URLs serve two main purposes: + - Providing a unique identifier for the foreign schema + - Directing human readers to documentation about the foreign schema + +Link URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide schema documentation in some human-readable form—a human reader should be able to click the link and go to the correct version of the docs. This is not an absolute functional requirement—as far as the core schema machinery is concerned, the URL is simply a globally unique namespace identifier with a particular form. + +Link URLs MAY contain information about the spec's [name](#sec-Prefixing) and [version](#sec-Versioning): + +```html diagram -- Basic anatomy of a link URL + + https://spec.example.com/a/b/c/mySchema/v1.0 + +``` + +The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MAY contain the schema's name and a [version tag](#sec-Versioning), in that order. Both are optional. To be recognized as a version tag, the final path component MUST be a valid {VersionTag}. To be recognized as a name, the penultimate path component MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not start or end with an underscore (`_`) and which does not contain the namespace separator (`__`). + +Empty final path components (that is, trailing slashes) and any URL components which have not been assigned a meaning (such as the fragment and query) MUST be ignored. + +```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSchema/v1.0/?key=val&k2=v2#frag + +``` + +All of these are valid arguments to `url`, and their interpretations: + +| url | normalized url | name | version | +| ------------------------------------------------- | ------------------------------------------ | ------- | -------- | +| https://spec.example.com/a/b/mySchema/v1.0/ | https://spec.example.com/a/b/mySchema/v1.0 | mySchema | v1.0 | +| https://spec.example.com | https://spec.example.com | *(null)* | *(null)* | +| https://spec.example.com/mySchema/v0.1?q=v#frag | https://spec.example.com/mySchema/v0.1 | mySchema | v0.1 | +| https://spec.example.com/v1.0 | https://spec.example.com/v1.0 | *(null)* | v1.0 | +| https://spec.example.com/vX | https://spec.example.com/vX | vX | *(null)* | + +If `name` is present, that [namespace prefix](#sec-Prefixing) will automatically be linked to the URL. If a `name` is not present, then elements of the foreign schema must be [`imported`](#@link/import) in order to be referenced. + +###! as: String + +Change the [namespace prefix](#sec-Prefixing) assigned to the foreign schema. + +The name MUST be a valid GraphQL identifier, MUST NOT contain the namespace separator ({"__"}), and MUST NOT end with an underscore (which would create ambiguity between whether {"x___y"} is prefix `x_` for element `y` or prefix `x` for element `_y`). + +By default, {@link} will assign a prefix based on the `name` extracted from the URL. If no `name` is present, a prefix will not be assigned. + +Providing [`as:`](#@link/as) overrides the default behavior: the foreign schema will be bound to the provided name, regardless of the `name` present in the URL (or the lack of one). + +```graphql example -- Using {@link}(url:, as:) to link a schema with a custom name +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://spec.example.com/example/v1.0", as: "eg") +{ + query: Query +} + +type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) +} + +# Additional specified schema elements must have their prefixes set +# to the new name. +enum eg__Data { + ITEM +} + +# Name transformation must also be applied to definitions pulled in from +# specifications. +directive @eg(data: eg__Data) on FIELD_DEFINITION + +directive @link(url: String!, as: String) repeatable on SCHEMA +``` + +###! import: [Import] + +A list of names, possibly with aliases, to import from the foreign schema into the document. + +See the [Import](#Import) scalar for a description of the format. + +###! for: Purpose + +An optional [purpose](#Purpose) for this link. This hints to consumers as to whether they can safely ignore metadata described by a foreign schema. + +By default, {@link}s SHOULD fail open. This means that {@link}s to unknown schemas SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + +This behavior is different for {@link}s with a specified purpose: + - `SECURITY` links convey metadata necessary to compute the API schema and securely resolve fields within it + - `EXECUTION` links convey metadata necessary to correctly resolve fields within the schema + +##! @id + +```graphql definition +directive @id(url: String!) on SCHEMA +``` + +Identify the current document by its URL. The URL is interpreted identically to [`@link`'s `url:` argument](#@link/as). + +##! Import + +```graphql definition +scalar Import +``` + +An element, possibly aliased, to import into the document. + +`Import` can take the form of a string: + +```graphql example -- import a string name + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@link", "Purpose"]) +``` + +or an object with `name` and (optionally `as`): + +```graphql example -- import an aliased name + @link(url: "https://example.com/, import: [{ + name: "@example", + as: "@eg" + }, { name: "Purpose", as: "LinkPurpose" }]) +``` + +`name` and `as` MUST be of the same type: + +```graphql counter-example -- incorrectly importing a type as a directive + @link(url: "https://example.com/, import: [{ + name: "SomeType", + as: "@someDirective" + }]) +``` + +Imports cannot currently reference transitive schemas: + +```graphql counter-example -- incorrectly importing a transitive schema reference + @link(url: "https://example.com/, import: "otherSchema::") +``` + +Note: Future versions may support this. + +##! Purpose + +The role of a feature referenced with {@link}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. + +###! SECURITY + +`SECURITY` links provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + +Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + +Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + +Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY links during development. + +More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY links, even if those links are never used to annotate the schema. + +###! EXECUTION + +`EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + +Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + +Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + +# Appendix: Validations & Algorithms + +## Construct the document's scope + +Visit every {@link} and {@id} within the document to construct the document's scope. + +Note: This algorithm **Report**s errors. Depending on their needs, implementations MAY decide to fail immediately in the face of these errors, or may elect to continue processing the document + +ConstructScope(document, baseScope) : + 1. **Let** {scope} be {baseScope} if provided. Otherwise, it begins as an empty map of {Element} ==> {Binding} + 2. **For** each schema definition or schema extension {schemaDef} in {document}, + 1. **For** each directive {dir} on {schemaDef}, + 1. **If** {Locate(scope, dir)} is the gref `https://specs.apollo.dev/link/v1.0#@link`... + 2. ...or {LocateBound(scope, dir)} is {null} and {IsBootstrap(dir)} **Then** + 1. **For each** ({element}, {binding}) **from** {BindingsFromLink(dir)} + 1. **If** {binding} is implicit and {element} exists in {scope} and is explicit, **Then Continue** + 2. **If** {binding} is implicit and {element} exists in {scope} and is implicit... + 3. **Or If** {binding} is explicit and {element} exists in {scope} and is explicit **Then** + 1. **Report** ❌ NameConflict + 1. **Insert** {element} ==> {binding} **into** {scope} + 2. **For** each schema definition or schema extension {schemaDef} in {document}, + 1. **For** each directive {dir} on {schemaDef}, + 1. **If** {Locate(scope, dir)} is the gref `https://specs.apollo.dev/link/v1.0#@id`... + 1. **If** {directive} does not have a `url` argument or its `url` argument is an [invalid URL](#@link/url) + 1. **Fail** ❌ BadId + 2. **Let** {url} be the canonical form of {directive}'s `url` + 1. **Insert** Schema() ==> Binding(gref: GRef({url}, Schema()), implicit: {false}) + 3. **Return** {scope} + +## Get all bindings from a @link directive + +Emit all scope bindings produced by a @link directive. + +Note: This algorithm is specified as a generator. This is deemed to produce the clearest psuedocode, but implementations may choose other approaches, such as collecting bindings into a list. + +BindingsFromLink(directive) : + 1. **If** {directive} does not have a `url` argument or its `url` argument is an [invalid URL](#@link/url) + 1. **Fail** ❌ BadLinkUrl + 1. **Let** {url} be the canonical form of the {directive}'s `url` argument, with [all meaningless components stripped](#@link/url) + 2. **If** {url} does not have a name... + 2. ...and {directive} does not have an `as` argument... + 3. ...and {directive} does not have an `import` argument or `import` is an empty list **Then** + 1. **Fail** ❌ UselessLink + 2. **If** {url} has a name, **Then** + 1. **Let** {name} be the name extracted from {url} + 2. **Let** {localName} be {directive}'s `as` argument if present and valid, otherwise {name} + 2. **Emit** the schema binding (Schema({localName}), Binding(gref: GRef({url}, Schema()), implicit: {false})) + 2. **Emit** the root directive binding (Directive({localName}), Binding(gref: GRef({url}, Directive({name})), implicit: {true})) + 3. ...**Else** + 2. **Let** {localName} be {directive}'s `as` argument if present and valid + 2. **Emit** the schema binding (Schema({localName}), Binding(gref: GRef({url}, Schema()), implicit: {false})) + 4. **For each** {import} **from** {directive}'s `import` argument: + 1. **If** {import} is a string directive name starting with `@`, + 0. **Let** {name} be the name of the directive specified by {import} + 1. **Emit** (Directive({name}), Binding(gref: GRef({url}, Directive({name})), implicit: {false})) + 2. **If** {import} is an object, + 1. **If** {import} does not have a string-valued `name` field, + 1. **Report** ❌ BadImport + 2. **Continue** + 1. **Let** {name} be the `name` field from {import} + 2. **Let** {as} be the `as` field from {import}, if present, otherwise {name} + 1. **If** {name} is a string directive name starting with `@`, + 1. **If** {as} is not a directive name starting with `@`, + 1. **Report** ❌ BadImportTypeMismatch + 2. **Continue** + 2. ...**Else Emit** (Directive({as}), Binding(gref: GRef({url}, Directive({name})), implicit: {false})) + 2. ...**Else If** {name} is a valid GraphQL identifier, + 1. **If** {as} is not a valid GraphQL identifier, + 1. **Report** ❌ BadImportTypeMismatch + 2. **Continue** + 3. ...**Else Emit** (Type({as}), Binding(gref: GRef({url}, Type({name})), implicit: {false})) + + +## Detecting a bootstrap directive + +Returns {true} if a directive is a [bootstrap](#sec-Bootstrapping). Otherwise, returns {false}. + +IsBootstrap(directive) : + 1. **If** {directive} does not have a string-valued `url` argument, **Then Return** {false} + 2. **Let** {url} be the canonical form of the {directive}'s `url` argument, with [all meaningless components stripped](#@link/url) + 3. **If** {url} is `https://specs.apollo.dev/link/v1.0`, + 1. **Let** {testScope} be an empty map of {Element} ==> {Binding} + 2. **For each** ({element}, {binding}) **from** {BindingsFromLink(directive)} + 1. **Insert** {element} ==> {binding} **into** {testScope} + 3. **If** {Locate(testScope, directive)} is a binding with the gref `https://specs.apollo.dev/link/v1.0#@link`, **Then** + 1. **Return** {true} + 4. Otherwise, **Return** {false} + +## Locating definitions and references + +Locate a definition or reference within the document's scope, returning a gref. + +{defOrRef} must be one of: +- a definition node with a name +- an extension node with a name +- a directive + +Locate(scope, defOrRef) : + 1. **If** {LocateBound(scope, defOrRef)} is not {null} **Then Return** {LocateBound(scope, defOrRef)} + 3. **Let** {selfReference} be the item `Schema()` + 3. **Let** {myself} be the URL of the schema returned from {Lookup(scope, selfReference)}, or `Schema(null)` if none was found + 1. Let {name} be the name of {defOrRef} + 4. **If** {defOrRef} is a named type reference, extension, or definition, **Then** + 1. **Return** the gref (myself, Type({name})) + 5. Otherwise, **Return** the gref (myself, Directive({name})) + +LocateBound(scope, defOrRef) : + 1. **Let** ({schema}, {element}) be the pair returned from {GetPathFrom(defOrRef)} + 2. **If** {schema} is not {null} and exists in {scope}, **Then** + 1. Let {foundGraph} be the URL of the binding found by looking up {schema} in {scope} + 2. **Return** the gref ({foundGraph}, {element}) + 3. **If** {schema} is {null} and {element} exists in {scope}, **Then** + 1. **Let** {foundElement} be the gref of the binding found by looking up {element} in {scope}, + 2. **Return** {foundElement} + 4. Otherwise, **Return** {null} + +# Appendix: Versioning + +VersionTag : "v" Version + +Version : Major "." Minor + +Major : NumericIdentifier + +Minor : NumericIdentifier + +NumericIdentifier : "0" + | PositiveDigit Digit* + +Digit : "0" | PositiveDigit + +PositiveDigit : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +Specs are versioned with a **subset** of a [Semantic Version Number](https://semver.org/spec/v2.0.0.html) containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form `v`{Major}`.`{Minor}, where both integers >= 0. + +```text example -- Valid version tags +v2.2 +v1.0 +v1.1 +v0.1 +``` + +As specified by semver, spec authors SHOULD increment the: + +{++ + +- MAJOR version when you make incompatible API changes, +- MINOR version when you add functionality in a backwards compatible manner + +++} + +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of GraphQL schemas, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. + +As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. + +## Satisfaction + +Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: + +Satisfies(requested, available) : + 1. If {requested}.{Major} ≠ {available}.{Major}, return {false} + 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} + 3. Return {requested}.{Minor} <= {available}.{Minor} + diff --git a/link/versioning.md b/link/versioning.md index 3967380..6f76177 100644 --- a/link/versioning.md +++ b/link/versioning.md @@ -38,7 +38,7 @@ Patch and pre-release qualifiers are judged to be not particularly meaningful in As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. -## Satisfaction +# Satisfaction Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: @@ -47,7 +47,7 @@ Satisfies(requested, available) : 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} 3. Return {requested}.{Minor} <= {available}.{Minor} -## Referencing versions and activating implementations +# Referencing versions and activating implementations Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less-deprecated version with a large major version. diff --git a/netlify.toml b/netlify.toml index c5ba547..940c346 100644 --- a/netlify.toml +++ b/netlify.toml @@ -3,6 +3,6 @@ [build] # This site has no build step at the moment. We just publish the contents. - publish = "/.graphs/https/specs.apollo.dev" + publish = "/" # Redirects are handled in the ./_redirects file, which has a simple format diff --git a/package-lock.json b/package-lock.json index 8e57ce7..ede9909 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "specs.apollo.dev": "bin/library" }, "devDependencies": { - "@queerviolet/speck": "queerviolet/speck", + "@queerviolet/speck": "github:queerviolet/speck", "express": "^4.17.1", "http-server": "^0.12.3", "jsdom": "^16.4.0", @@ -24,7 +24,7 @@ }, "node_modules/@queerviolet/speck": { "version": "2.0.2", - "resolved": "git+ssh://git@github.com/queerviolet/speck.git#c34a337ff933613b590b01681d44608b4696044b", + "resolved": "git+ssh://git@github.com/queerviolet/speck.git#7ae75598200928362757b15752495a5cf84f66ef", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2524,7 +2524,7 @@ }, "dependencies": { "@queerviolet/speck": { - "version": "git+ssh://git@github.com/queerviolet/speck.git#c34a337ff933613b590b01681d44608b4696044b", + "version": "git+ssh://git@github.com/queerviolet/speck.git#7ae75598200928362757b15752495a5cf84f66ef", "dev": true, "from": "@queerviolet/speck@queerviolet/speck", "requires": { diff --git a/package.json b/package.json index f9a8c24..ae0f41f 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,11 @@ "author": "", "license": "ISC", "devDependencies": { + "@queerviolet/speck": "github:queerviolet/speck", "express": "^4.17.1", "http-server": "^0.12.3", "jsdom": "^16.4.0", - "puppeteer": "^5.5.0", - "@queerviolet/speck": "queerviolet/speck" + "puppeteer": "^5.5.0" }, "dependencies": { "commonmark": "^0.29.3" From 39967e9c092b813a65e780f000fb1927de726d48 Mon Sep 17 00:00:00 2001 From: Ashi Krishnan Date: Sat, 23 Apr 2022 05:34:04 -0400 Subject: [PATCH 07/13] link spec updates --- .gitignore | 2 - build | 1 + core/v0.1/.gitignore | 3 + core/v0.1/bad-non-unique-prefix-multi.graphql | 9 + core/v0.1/bad-non-unique-prefix.graphql | 9 + core/v0.1/basic.graphql | 14 + core/v0.1/core-v0.1.md | 485 +++++ core/v0.1/good-unique-prefix-multi.graphql | 9 + core/v0.1/index.html | 1705 +++++++++++++++ core/v0.1/netlify.toml | 6 + core/v0.1/package.json | 18 + core/v0.1/prefix-uniqueness.graphql | 25 + core/v0.1/prefixing.graphql | 46 + core/v0.2/.gitignore | 3 + core/v0.2/bad-non-unique-prefix-multi.graphql | 9 + core/v0.2/bad-non-unique-prefix.graphql | 9 + core/v0.2/basic.graphql | 14 + core/v0.2/core-v0.2.md | 577 ++++++ core/v0.2/good-unique-prefix-multi.graphql | 9 + core/v0.2/index.html | 1829 +++++++++++++++++ core/v0.2/netlify.toml | 6 + core/v0.2/package.json | 20 + core/v0.2/prefix-uniqueness.graphql | 25 + core/v0.2/prefixing.graphql | 46 + index.html | 1087 ++++++++++ index.md | 20 + join/v0.1/albums.graphql | 14 + join/v0.1/auth.graphql | 8 + join/v0.1/images.graphql | 15 + join/v0.1/index.html | 1674 +++++++++++++++ join/v0.1/join-v0.1.md | 469 +++++ join/v0.1/package.json | 18 + join/v0.1/photos.graphql | 62 + join/v0.1/spec.graphql | 14 + link/v1.0/index.html | 301 +-- link/v1.0/link-v1.0.md | 105 +- tag/v0.1 | 1 - tag/v0.1/index.html | 1155 +++++++++++ tag/v0.1/ownership-example.graphql | 33 + tag/v0.1/spec.graphql | 5 + tag/v0.1/tag-v0.1.md | 42 + 41 files changed, 9734 insertions(+), 168 deletions(-) create mode 100644 core/v0.1/.gitignore create mode 100644 core/v0.1/bad-non-unique-prefix-multi.graphql create mode 100644 core/v0.1/bad-non-unique-prefix.graphql create mode 100644 core/v0.1/basic.graphql create mode 100644 core/v0.1/core-v0.1.md create mode 100644 core/v0.1/good-unique-prefix-multi.graphql create mode 100644 core/v0.1/index.html create mode 100644 core/v0.1/netlify.toml create mode 100644 core/v0.1/package.json create mode 100644 core/v0.1/prefix-uniqueness.graphql create mode 100644 core/v0.1/prefixing.graphql create mode 100644 core/v0.2/.gitignore create mode 100644 core/v0.2/bad-non-unique-prefix-multi.graphql create mode 100644 core/v0.2/bad-non-unique-prefix.graphql create mode 100644 core/v0.2/basic.graphql create mode 100644 core/v0.2/core-v0.2.md create mode 100644 core/v0.2/good-unique-prefix-multi.graphql create mode 100644 core/v0.2/index.html create mode 100644 core/v0.2/netlify.toml create mode 100644 core/v0.2/package.json create mode 100644 core/v0.2/prefix-uniqueness.graphql create mode 100644 core/v0.2/prefixing.graphql create mode 100644 index.html create mode 100644 index.md create mode 100644 join/v0.1/albums.graphql create mode 100644 join/v0.1/auth.graphql create mode 100644 join/v0.1/images.graphql create mode 100644 join/v0.1/index.html create mode 100644 join/v0.1/join-v0.1.md create mode 100644 join/v0.1/package.json create mode 100644 join/v0.1/photos.graphql create mode 100644 join/v0.1/spec.graphql delete mode 160000 tag/v0.1 create mode 100644 tag/v0.1/index.html create mode 100644 tag/v0.1/ownership-example.graphql create mode 100644 tag/v0.1/spec.graphql create mode 100644 tag/v0.1/tag-v0.1.md diff --git a/.gitignore b/.gitignore index e85ede9..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ node_modules -core -join \ No newline at end of file diff --git a/build b/build index e57e384..e7be76f 100755 --- a/build +++ b/build @@ -8,3 +8,4 @@ spec-md inaccessible/v0.1/inaccessible-v0.1.md > inaccessible/v0.1/index.html spec-md link/v1.0/link-v1.0.md > link/v1.0/index.html spec-md join/v0.1/join-v0.1.md > join/v0.1/index.html spec-md federation/v2.0/federation-v2.0.md > federation/v2.0/index.html +spec-md index.md > index.html \ No newline at end of file diff --git a/core/v0.1/.gitignore b/core/v0.1/.gitignore new file mode 100644 index 0000000..b768a31 --- /dev/null +++ b/core/v0.1/.gitignore @@ -0,0 +1,3 @@ +node_modules +.dist +package-lock.json \ No newline at end of file diff --git a/core/v0.1/bad-non-unique-prefix-multi.graphql b/core/v0.1/bad-non-unique-prefix-multi.graphql new file mode 100644 index 0000000..221f3e4 --- /dev/null +++ b/core/v0.1/bad-non-unique-prefix-multi.graphql @@ -0,0 +1,9 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0") # name is A +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.1/bad-non-unique-prefix.graphql b/core/v0.1/bad-non-unique-prefix.graphql new file mode 100644 index 0000000..749db43 --- /dev/null +++ b/core/v0.1/bad-non-unique-prefix.graphql @@ -0,0 +1,9 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://www.specs.com/specA/1.1", as: "A") # name is A +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.1/basic.graphql b/core/v0.1/basic.graphql new file mode 100644 index 0000000..454191b --- /dev/null +++ b/core/v0.1/basic.graphql @@ -0,0 +1,14 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/example/v1.0") +{ + query: Query +} + +type Query { + field: Int @example +} + +directive @example on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.1/core-v0.1.md b/core/v0.1/core-v0.1.md new file mode 100644 index 0000000..e67f292 --- /dev/null +++ b/core/v0.1/core-v0.1.md @@ -0,0 +1,485 @@ +# Core Schemas + +

      flexible metadata for GraphQL schemas

      + +```raw html + + + +
      StatusRelease
      Version0.1
      + + +``` + +[GraphQL](https://spec.graphql.org/) provides directives as a means of attaching user-defined metadata to a GraphQL document. Directives are highly flexible, and can be used to suggest behavior and define features of a graph which are not otherwise evident in the schema. + +Alas, *GraphQL does not provide a mechanism to globally identify or version directives*. Given a particular directive—e.g. `@join`—processors are expected to know how to interpret the directive based only on its name, definition within the document, and additional configuration from outside the document. This means that programs interpreting these directives have two options: + + 1. rely on a hardcoded interpretation for directives with certain signatures, or + 2. accept additional configuration about how to interpret directives in the schema. + +The first solution is fragile, particularly as GraphQL has no built-in namespacing mechanisms, so the possibility of name collisions always looms. + +The second is unfortunate: GraphQL schemas are generally intended to be self-describing, and requiring additional configuration subtly undermines this guarantee: given just a schema, programs do not necessarily know how to interpret it, and certainly not how to serve it. It also creates the possibility for the schema and configuration to fall out of sync, leading to issues which can manifest late in a deployment pipeline. + +Introducing **core schemas**. + + + +A basic core schema: + +:::[example](basic.graphql) -- A basic core schema + +**Core schemas** provide a concise mechanism for schema documents to specify the metadata they provide. Metadata is grouped into **features**, which typically define directives and associated types (e.g. scalars and inputs which serve as directive inputs). Additionally, core schemas provide: + - [**Flexible namespacing rules.**](#sec-Prefixing) It is always possible to represent any GraphQL schema within a core schema document. Additionally, documents can [choose the names](#@core/as) they use for the features they reference, guaranteeing that namespace collisions can always be resolved. + - [**Versioning.**](#sec-Versioning) Feature specifications follow [semver-like semantic versioning principles](#sec-Versioning), which helps schema processors determine if they are able to correctly interpret a document's metadata. + +**Core schemas are not a new language.** All core schema documents are valid GraphQL schema documents. However, this specification introduces new requirements, so not all valid GraphQL schemas are valid core schemas. + +The broad intention behind core schemas is to provide a *single document* which provides all the necessary configuration for programs that process and serve the schema to GraphQL clients, primarily by following directives in order to determine how to resolve queries made against that schema. + +# Parts of a Core Schema + +When talking about a core schema, we can broadly break it into two pieces: +- an **API** consisting of schema elements (objects, interfaces, enums, directives, etc.) which SHOULD be served to clients, and +- **machinery** containing document metadata. This typically consists of directives and associated input types (such as enums and input objects), but may include any schema element. Machinery MUST NOT be served to clients. Specifically, machinery MUST NOT be included in introspection responses or used to validate or execute queries. + +This reflects how core schemas are used: a core schema contains a GraphQL interface (the *API*) along with metadata about how to implement that interface (the *machinery*). Exposing the machinery to clients is unnecessary, and may in some cases constitute a security issue (for example, the machinery for a public-facing graph router will likely reference internal services, possibly exposing network internals which should not be visible to the general public). + +A key feature of core schemas is that it is always possible to derive a core schema's API without any knowledge of the features used by the document (with the exception of the `core` feature itself). Specifically, named elements are not included in the API schema if they are named `something__likeThis` or are a directive named `@something`, and `something` is the prefix of a feature declared with {@core}. + +A formal description is provided by the [IsInAPI](#sec-Is-In-API-) algorithm. + +# Actors + +```mermaid diagram -- Actors who may be interested in the core schemas +graph TB + classDef bg fill:none,color:#22262E; + author("👩🏽‍💻 🤖  Author"):::bg-->schema(["☉ Core Schema"]):::bg + schema-->proc1("🤖  Processor"):::bg + proc1-->output1(["☉ Core Schema[0]"]):::bg + output1-->proc2("🤖  Processor"):::bg + proc2-->output2(["☉ Core Schema[1]"]):::bg + output2-->etc("..."):::bg + etc-->final(["☉ Core Schema [final]"]):::bg + final-->core("🤖 Data Core"):::bg + schema-->reader("👩🏽‍💻 Reader"):::bg + output1-->reader + output2-->reader + final-->reader +``` + +- **Authors (either human or machine)** write an initial core schema as specified in this document, including versioned {@core} requests for all directives they use +- **Machine processors** can process core schemas and output new core schemas. The versioning of directives and associated schema elements provided by the {@core} allows processors to operate on directives they understand and pass through directives they do not. +- **Human readers** can examine the core schema at various stages of processing. At any stage, they can examine the {@core} directives and follow URLs to the specification, receiving an explanation of the requirements of the specification and what new directives, types, and other schema objects are available within the document. +- **Data cores** can then pick up the processed core schema and provide some data-layer service with it. Typically this means serving the schema's API as a GraphQL endpoint, using metadata defined by machinery to inform how it processes operations it receives. However, data cores may perform other tasks described in the core schema, such as routing to backend services, caching commonly-accessed fields and queries, and so on. The term "data core" is intended to capture this multiplicity of possible activities. + +# Basic Requirements + +Core schemas: + 1. MUST be valid GraphQL schema documents, + 2. MUST contain exactly one `SchemaDefinition`, and + 3. MUST use the {@core} directive on their schema definition to declare any features they reference by using {@core} to reference a [well-formed feature URL](#@core/feature). + +The first {@core} directive on the schema MUST reference the core spec itself, i.e. this document. + +:::[example](basic.graphql) -- Basic core schema using {@core} and `@example` + +## Unspecified directives are passed through by default + +Existing schemas likely contain definitions for directives which are not versioned, have no specification document, and are intended mainly to be passed through. This is the default behavior for core schema processors: + +```graphql example -- Unspecified directives are passed through +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") +{ + query: Query +} + +type SomeType { + field: Int @another +} + +# `@another` is unspecified. Core processors will not extract metadata from +# it, but its definition and all usages within the schema will be exposed +# in the API. +directive @another on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +## Renaming core itself + +It is possible to rename the core feature itself with the same [`as:`](#@core/as) mechanism used for all features: + +```graphql example -- Renaming {@core} to {@coreSchema} +schema + @coreSchema(feature: "https://specs.apollo.dev/core/v0.1", as: "coreSchema") + @coreSchema(feature: "https://example.com/example/v1.0") +{ + query: Query +} + +type SomeType { + field: Int @example +} + +directive @coreSchema(feature: String!, as: String) + repeatable on SCHEMA +directive @example on FIELD_DEFINITION +``` + +# Directive Definitions + +All core schemas use the [{@core}](#@core) directive to declare their use of the `core` feature itself as well as any other core features they use. + +In order to use these directives in your schema, GraphQL requires you to include their definitions in your schema. + +Processors MUST validate that you have defined the directives with the same arguments, locations, and `repeatable` flag as given below. Specifically, the [bootstrapping](#sec-Bootstrapping) algorithm validates that the `@core` directive has a definition matching the definition given below. (The bootstrapping algorithm does not require processors to validate other aspects of the directive declaration such as description strings or argument ordering. The main purpose of this validation is to ensure that directive arguments have the type and default values expected by the specification.) + +The following declares the directive defined by this specification. You SHOULD define the directives in your core schema by including the following text in your schema document. + +```graphql +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +When writing a specification for your own core feature, you SHOULD include a section like this one with sample definitions to copy into schemas, and you SHOULD require processors to validate that directive definitions in documents match your sample definitions. + +# Directives + +##! @core + +Declare a core feature present in this schema. + +```graphql definition +directive @core( + feature: String!, + as: String) + repeatable on SCHEMA +``` + +Documents MUST include a definition for the {@core} directive which includes all of the arguments defined above with the same types and default values. + +###! feature: String! + +A feature URL specifying the directive and associated schema elements. When viewed, the URL SHOULD provide the content of the appropriate version of the specification in some human-readable form. In short, a human reader should be able to click the link and go to the docs for the version in use. There are specific requirements on the format of the URL, but it is not required that the *content* be machine-readable in any particular way. + +Feature URLs contain information about the spec's [prefix](#sec-Prefixing) and [version](#sec-Versioning). + +Feature URLs serve two main purposes: + - Directing human readers to documentation about the feature + - Providing tools with information about the specs in use, along with enough information to select and invoke an implementation + +Feature URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide the specification of the selected version of the feature in some human-readable form; a human reader should be able to click the link and go to the correct version of the docs. + +Although they are not prohibited from doing so, it's assumed that processors will not load the content of feature URLs. Published specifications are not required to be machine-readable, and [this spec](.) places no requirements on the structure or syntax of the content to be found there. + +There are, however, requirements on the structure of the URL itself: + +```html diagram -- Basic anatomy of a feature URL + + https://spec.example.com/a/b/c/exampleFeature/v1.0 + +``` + +The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MUST contain the feature's name and a [version tag](#sec-Versioning). The content of the URL up to and including the name—but excluding the `/` after the name and the version tag—is the feature's *identity*. Trailing slashes at the end of the URL (ie, after the version tag) should be ignored. For the above example, +
      +
      `identity: "https://spec.example.com/a/b/c/exampleFeature"`
      +
      A global identifier for the feature. Processors can treat this as an opaque string identifying the feature (but not the version of the feature) for purposes of selecting an appropriate implementation. The identity never has a trailing `/`.
      +
      `name: "exampleFeature"`
      +
      The feature's name, for purposes of [prefixing](#sec-Prefixing) schema elements it defines.
      +
      `version: "v1.0"`
      +
      The tag for the [version](#sec-Versioning) of the feature used to author the document. Processors MUST select an implementation of the feature which can [satisfy](#sec-Satisfaction) the specified version.
      +
      + +The version tag MUST be a valid {VersionTag}. The name MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + +#### Ignore meaningless URL components + +When extracting the URL's `name` and `version`, processors MUST ignore any url components which are not assigned a meaning. This spec assigns meaning to the final two segments of the [path](https://tools.ietf.org/html/rfc3986#section-3.3). Other URL components—particularly query strings and fragments, if present—MUST be ignored for the purposes of extracting the `name` and `version`. + +```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSpec/v1.0/?key=val&k2=v2#frag + +``` + +#### Why is versioning in the URL, not a directive argument? + +The version is in the URL because when a human reader visits the URL, we would like them to be taken to the documentation for the *version of the feature used by this document*. Many text editors will turn URLs into hyperlinks, and it's highly desirable that clicking the link takes the user to the correct version of the docs. Putting the version information in a separate argument to the {@core} directive would prevent this. + +###! as: String + +Change the [names](#sec-Prefixing) of directives and schema elements from this specification. The specified string MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) and MUST NOT contain the namespace separator (two underscores, {"__"}) or end with an underscore. + +When [`as:`](#@core/as) is provided, processors looking for [prefixed schema elements](#sec-Elements-which-must-be-prefixed) MUST look for elements whose names are the specified name with the prefix replaced with the name provided to the `as:` argument. + +```graphql example -- Using {@core}(feature:, as:) to use a feature with a custom name +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://spec.example.com/example/v1.0", as: "eg") +{ + query: Query +} + +type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) +} + +# Additional specified schema elements must have their prefixes set +# to the new name. +# +# The spec at https://spec.example.com/example/v1.0 calls this enum +# `example__Data`, but because of the `as:` argument above, processors +# will use this `eg__Data` enum instead. +enum eg__Data { + ITEM +} + +# Name transformation must also be applied to definitions pulled in from +# specifications. +directive @eg(data: eg__Data) on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +# Prefixing + +With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix: + 1. MUST match the name of the feature as derived from the feature's specification URL, + 2. MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name), and + 3. MUST NOT contain the core namespace separator, which is two underscores ({"__"}), and + 4. MUST NOT end with an underscore (which would create ambiguity between whether {"x___y"} is prefix `x_` for element `y` or prefix `x` for element `_y`). + +Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid [GraphQL name](https://spec.graphql.org/draft/#Name). For instance, the `core` specification (which you are currently reading) introduces an element named [{@core}](#@core), and the `join` specification introduces an element named {@join__field} (among others). + +Note that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like `@feature__24hours`. + +A feature's *root directive* is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the `core` specification introduces a {@core} directive. This directive has the same name as the feature ("`core`"), and so requires no prefix. + +```graphql example -- Using the @core directive without changing the prefix +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://spec.example.com/example/v1.0") { + query: Query +} + +type User { + name: String @example(data: ITEM) +} + +# An enum used to provide structured data to the example spec. +# It is prefixed with the name of the spec. +enum example__Data { + ITEM +} + +directive @example(data: example__Data) on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature's name as a prefix. + +## Elements which must be prefixed + +Feature specs MUST prefix the following schema elements: + - the names of any object types, interfaces, unions, enums, or input object types defined by the feature + - the names of any directives introduced in the spec, with the exception of the *root directive*, which must have the same name as the feature + +:::[example](prefixing.graphql) -- Prefixing + +# Versioning + +VersionTag : "v" Version + +Version : Major "." Minor + +Major : NumericIdentifier + +Minor : NumericIdentifier + +NumericIdentifier : "0" + | PositiveDigit Digit* + +Digit : "0" | PositiveDigit + +PositiveDigit : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +Specs are versioned with a **subset** of a [Semantic Version Number](https://semver.org/spec/v2.0.0.html) containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form `v`{Major}`.`{Minor}, where both integers >= 0. + +```text example -- Valid version tags +v2.2 +v1.0 +v1.1 +v0.1 +``` + +As specified by semver, spec authors SHOULD increment the: + +{++ + +- MAJOR version when you make incompatible API changes, +- MINOR version when you add functionality in a backwards compatible manner + +++} + +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. + +As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. + +## Satisfaction + +Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: + +Satisfies(requested, available) : + 1. If {requested}.{Major} ≠ {available}.{Major}, return {false} + 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} + 3. Return {requested}.{Minor} <= {available}.{Minor} + +## Referencing versions and activating implementations + +Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less-deprecated version with a large major version. + +If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can [satisfy](#sec-Satisfaction) the version required by the document. + + +# Processing Schemas + +```mermaid diagram +graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +``` + +A common use case is that of a processor which consumes a valid input schema and generates an output schema. + +The general guidance for processor behavior is: don't react to what you don't understand. + +Specifically, processors: + - SHOULD pass through {@core} directives which reference unknown feature URLs + - SHOULD pass through prefixed directives, types, and other schema elements + - SHOULD pass through directives which are not [associated with](#AssignFeatures) a {@core} feature + +Processors MAY accept configuration which overrides these default behaviors. + +Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. + +# Validations & Algorithms + +This section lays out algorithms for processing core schemas. + +Algorithms described in this section may produce *validation failures* if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified. + +## Bootstrapping + +Determine the name of the core specification within the document. + +It is possible to [rename the core feature](#sec-Renaming-core-itself) within a document. This process determines the actual name for the core feature if one is present. + +- **Fails** the *Has Schema* validation if there are no SchemaDefinitions in the document +- **Fails** the *Has Core Feature* validation if the `core` feature itself is not referenced with a {@core} directive within the document +- **Fails** the *Bootstrap Core Feature Listed First* validation if the reference to the `core` feature is not the first {@core} directive on the document's SchemaDefinition +- **Fails** the *Core Directive Incorrect Definition* validation if the {@core} directive definition does not *match* the directive as defined by this specification. + +For the purposes of this algorithm, a directive's definition in a schema *matches* a definition provided in this specification if: +- Its arguments have the specified names, types, and default values (or lack thereof) +- It is defined as `repeatable` if and only if the specification's definition defines it as `repeatable` +- The set of locations it belongs to is the same set of locations in the specification's definition. + +The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from *matching*: +- The name of the directive (due to [prefixing](#sec-Prefixing)) +- The order of arguments +- The order of locations +- The directive's description string +- Argument description strings +- Directives applied to argument definitions + +Bootstrap(document) : +1. Let {schema} be the only SchemaDefinition in {document}. (Note that legal GraphQL documents [must include at most one SchemaDefinition](http://spec.graphql.org/draft/#sec-Root-Operation-Types).) + 1. ...if no SchemaDefinitions are present in {document}, the ***Has Schema* validation fails**. +1. For each directive {d} on {schema}, + 1. If {d} has a [`feature:`](#@core/feature) argument which [parses as a feature URL](#@core/feature), *and* whose identity is {"https://specs.apollo.dev/core/"} *and* whose version is {"v0.1"}, *and either* {d} has an [`as:`](#@core/as) argument whose value is equal to {d}'s name *or* {d} does not have an [`as:`](#@core/as) argument and {d}'s name is `core`: + - If any directive on {schema} listed before {d} has the same name as {d}, the ***Bootstrap Core Feature Listed First* validation fails**. + - If the definition of the directive {d} does not *match* the [definition of {@core} in this specification](#@core), the ***Core Directive Incorrect Definition* validation fails**. + - Otherwise, **Return** {d}'s name. +- If no matching directive was found, the ***Has Core Feature* validation fails**. + +## Feature Collection + +Collect a map of ({featureName}: `String`) -> `Directive`, where `Directive` is a {@core} Directive which introduces the feature named {featureName} into the document. + +- **Fails** the *Name Uniqueness* validation if feature names are not unique within the document. +- **Fails** *Invalid Feature URL* validation for any invalid feature URLs. + +CollectFeatures(document) : + - Let {coreName} be the name of the core feature found via {Bootstrap(document)} + - Let {features} be a map of {featureName}: `String` -> `Directive`, initially empty. + - For each directive {d} named {coreName} on the SchemaDefinition within {document}, + - Let {specifiedFeatureName} and {version} be the result of parsing {d}'s `feature:` argument according to the [specified rules for feature URLs](#@core/feature) + - If the `feature:` is not present or fails to parse: + - The ***Invalid Feature URL* validation fails** for {d}, + - Let {featureName} be the {d}'s [`as:`](#@core/as) argument or, if the argument is not present, {specifiedFeatureName} + - If {featureName} exists within {features}, the ***Name Uniqueness* validation fails**. + - Insert {featureName} => {d} into {features} + - **Return** {features} + + +Prefixes, whether implicit or explicit, must be unique within a document. Valid: + +:::[example](prefixing.graphql#schema[0]) -- Unique prefixes + +It is also valid to reference multiple versions of the same spec under different prefixes: + +:::[example](prefix-uniqueness.graphql#schema[0]) -- Explicit prefixes allow multiple versions of the same spec to coexist within a Document + +Without the explicit [`as:`](#@core/as), the above would be invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[1]) -- Non-unique prefixes with multiple versions of the same spec + +Different specs with the same prefix are also invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[2]) -- Different specs with non-unique prefixes + +## Assign Features + +Create a map of {element}: *Any Named Element* -> {feature}: `Directive` | {null}, associating every named schema element within the document with a feature directive, or {null} if it is not associated with a feature. + +AssignFeatures(document) : + - Let {features} be the result of collecting features via {CollectFeatures(document)} + - Let {assignments} be a map of ({element}: *Any Named Element*) -> {feature}: `Directive` | {null}, initally empty + - For each named schema element {e} within the {document} + - Let {name} be the name of the {e} + - If {e} is a Directive and {name} is a key within {features}, + - Insert {e} => {features}`[`{name}`]` into {assignments} + - **Continue** to next {e} + - If {name} begins with {"__"}, + - Insert {e} => {null} into {assignments} + - **Continue** to next {e} + - If {name} contains the substring {"__"}, + - Partition {name} into `[`{prefix}, {base}`]` at the first {"__"} (that is, find the shortest {prefix} and longest {base} such that {name} = {prefix} + {"__"} + {base}) + - If {prefix} exists within {features}, insert {e} => {features}`[`{prefix}`]` into {assignments} + - Else, insert {e} => {null} into {assignments} + - **Continue** to next {e} + - Insert {e} => {null} into {assignments} + - **Return** {assignments} + +## Is In API? + +Determine if any schema element is included in the [API](#sec-Parts-of-a-Core-Schema) described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a [name](https://spec.graphql.org/draft/#Name). + +IsInAPI(element) : + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - If {assignments}`[`{element}`]` is {null}, **Return** {true} + - Else, **Return** {false} + +Note: Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification. + diff --git a/core/v0.1/good-unique-prefix-multi.graphql b/core/v0.1/good-unique-prefix-multi.graphql new file mode 100644 index 0000000..8eafe08 --- /dev/null +++ b/core/v0.1/good-unique-prefix-multi.graphql @@ -0,0 +1,9 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0", as: "A2") # name is A2 +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.1/index.html b/core/v0.1/index.html new file mode 100644 index 0000000..df41bb3 --- /dev/null +++ b/core/v0.1/index.html @@ -0,0 +1,1705 @@ + + + + +Core Schemas + + + + + +
      +
      +

      Core Schemas

      +
      +

      flexible metadata for GraphQL schemas

      + + + +
      StatusRelease
      Version0.1
      + + +

      GraphQL provides directives as a means of attaching user‐defined metadata to a GraphQL document. Directives are highly flexible, and can be used to suggest behavior and define features of a graph which are not otherwise evident in the schema.

      +

      Alas, GraphQL does not provide a mechanism to globally identify or version directives. Given a particular directive—e.g. @join—processors are expected to know how to interpret the directive based only on its name, definition within the document, and additional configuration from outside the document. This means that programs interpreting these directives have two options:

      +
        +
      1. rely on a hardcoded interpretation for directives with certain signatures, or
      2. +
      3. accept additional configuration about how to interpret directives in the schema.
      4. +
      +

      The first solution is fragile, particularly as GraphQL has no built‐in namespacing mechanisms, so the possibility of name collisions always looms.

      +

      The second is unfortunate: GraphQL schemas are generally intended to be self‐describing, and requiring additional configuration subtly undermines this guarantee: given just a schema, programs do not necessarily know how to interpret it, and certainly not how to serve it. It also creates the possibility for the schema and configuration to fall out of sync, leading to issues which can manifest late in a deployment pipeline.

      +

      Introducing core schemas.

      +

      A basic core schema:

      +
      Example № 1 A basic core schema
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/example/v1.0")
      +{
      +  query: Query
      +}
      +
      +type Query {
      +  field: Int @example
      +}
      +
      +directive @example on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +

      Core schemas provide a concise mechanism for schema documents to specify the metadata they provide. Metadata is grouped into features, which typically define directives and associated types (e.g. scalars and inputs which serve as directive inputs). Additionally, core schemas provide:

      + +

      Core schemas are not a new language. All core schema documents are valid GraphQL schema documents. However, this specification introduces new requirements, so not all valid GraphQL schemas are valid core schemas.

      +

      The broad intention behind core schemas is to provide a single document which provides all the necessary configuration for programs that process and serve the schema to GraphQL clients, primarily by following directives in order to determine how to resolve queries made against that schema.

      +
      + +
      +
      +

      1Parts of a Core Schema

      +

      When talking about a core schema, we can broadly break it into two pieces:

      +
        +
      • an API consisting of schema elements (objects, interfaces, enums, directives, etc.) which SHOULD be served to clients, and
      • +
      • machinery containing document metadata. This typically consists of directives and associated input types (such as enums and input objects), but may include any schema element. Machinery MUST NOT be served to clients. Specifically, machinery MUST NOT be included in introspection responses or used to validate or execute queries.
      • +
      +

      This reflects how core schemas are used: a core schema contains a GraphQL interface (the API) along with metadata about how to implement that interface (the machinery). Exposing the machinery to clients is unnecessary, and may in some cases constitute a security issue (for example, the machinery for a public‐facing graph router will likely reference internal services, possibly exposing network internals which should not be visible to the general public).

      +

      A key feature of core schemas is that it is always possible to derive a core schema’s API without any knowledge of the features used by the document (with the exception of the core feature itself). Specifically, named elements are not included in the API schema if they are named something__likeThis or are a directive named @something, and something is the prefix of a feature declared with @core.

      +

      A formal description is provided by the IsInAPI algorithm.

      +
      +
      +

      2Actors

      +
      Actors who may be interested in the core schemas
      graph TB + classDef bg fill:none,color:#22262E; + author("👩🏽‍💻 🤖  Author"):::bg-->schema(["☉ Core Schema"]):::bg + schema-->proc1("🤖  Processor"):::bg + proc1-->output1(["☉ Core Schema[0]"]):::bg + output1-->proc2("🤖  Processor"):::bg + proc2-->output2(["☉ Core Schema[1]"]):::bg + output2-->etc("..."):::bg + etc-->final(["☉ Core Schema [final]"]):::bg + final-->core("🤖 Data Core"):::bg + schema-->reader("👩🏽‍💻 Reader"):::bg + output1-->reader + output2-->reader + final-->reader +
        +
      • Authors (either human or machine) write an initial core schema as specified in this document, including versioned @core requests for all directives they use
      • +
      • Machine processors can process core schemas and output new core schemas. The versioning of directives and associated schema elements provided by the @core allows processors to operate on directives they understand and pass through directives they do not.
      • +
      • Human readers can examine the core schema at various stages of processing. At any stage, they can examine the @core directives and follow URLs to the specification, receiving an explanation of the requirements of the specification and what new directives, types, and other schema objects are available within the document.
      • +
      • Data cores can then pick up the processed core schema and provide some data‐layer service with it. Typically this means serving the schema’s API as a GraphQL endpoint, using metadata defined by machinery to inform how it processes operations it receives. However, data cores may perform other tasks described in the core schema, such as routing to backend services, caching commonly‐accessed fields and queries, and so on. The term “data core” is intended to capture this multiplicity of possible activities.
      • +
      +
      +
      +

      3Basic Requirements

      +

      Core schemas:

      +
        +
      1. MUST be valid GraphQL schema documents,
      2. +
      3. MUST contain exactly one SchemaDefinition, and
      4. +
      5. MUST use the @core directive on their schema definition to declare any features they reference by using @core to reference a well‐formed feature URL.
      6. +
      +

      The first @core directive on the schema MUST reference the core spec itself, i.e. this document.

      +
      Example № 2 Basic core schema using @core and @example
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/example/v1.0")
      +{
      +  query: Query
      +}
      +
      +type Query {
      +  field: Int @example
      +}
      +
      +directive @example on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +

      3.1Unspecified directives are passed through by default

      +

      Existing schemas likely contain definitions for directives which are not versioned, have no specification document, and are intended mainly to be passed through. This is the default behavior for core schema processors:

      +
      Example № 3 Unspecified directives are passed through
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +{
      +  query: Query
      +}
      +
      +type SomeType {
      +  field: Int @another
      +}
      +
      +# `@another` is unspecified. Core processors will not extract metadata from
      +# it, but its definition and all usages within the schema will be exposed
      +# in the API.
      +directive @another on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +
      +

      3.2Renaming core itself

      +

      It is possible to rename the core feature itself with the same as: mechanism used for all features:

      +
      Example № 4 Renaming @core to @coreSchema
      schema
      +  @coreSchema(feature: "https://specs.apollo.dev/core/v0.1", as: "coreSchema")
      +  @coreSchema(feature: "https://example.com/example/v1.0")
      +{
      +  query: Query
      +}
      +
      +type SomeType {
      +  field: Int @example
      +}
      +
      +directive @coreSchema(feature: String!, as: String)
      +  repeatable on SCHEMA
      +directive @example on FIELD_DEFINITION
      +
      +
      +
      +
      +

      4Directive Definitions

      +

      All core schemas use the @core directive to declare their use of the core feature itself as well as any other core features they use.

      +

      In order to use these directives in your schema, GraphQL requires you to include their definitions in your schema.

      +

      Processors MUST validate that you have defined the directives with the same arguments, locations, and repeatable flag as given below. Specifically, the bootstrapping algorithm validates that the @core directive has a definition matching the definition given below. (The bootstrapping algorithm does not require processors to validate other aspects of the directive declaration such as description strings or argument ordering. The main purpose of this validation is to ensure that directive arguments have the type and default values expected by the specification.)

      +

      The following declares the directive defined by this specification. You SHOULD define the directives in your core schema by including the following text in your schema document.

      +
      directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +

      When writing a specification for your own core feature, you SHOULD include a section like this one with sample definitions to copy into schemas, and you SHOULD require processors to validate that directive definitions in documents match your sample definitions.

      +
      +
      +

      5Directives

      +
      +

      5.1@core

      +

      Declare a core feature present in this schema.

      +
      directive @core(
      +  feature: String!,
      +  as: String)
      +  repeatable on SCHEMA
      +
      +

      Documents MUST include a definition for the @core directive which includes all of the arguments defined above with the same types and default values.

      +
      +

      5.1.1feature: String!

      +

      A feature URL specifying the directive and associated schema elements. When viewed, the URL SHOULD provide the content of the appropriate version of the specification in some human‐readable form. In short, a human reader should be able to click the link and go to the docs for the version in use. There are specific requirements on the format of the URL, but it is not required that the content be machine‐readable in any particular way.

      +

      Feature URLs contain information about the spec’s prefix and version.

      +

      Feature URLs serve two main purposes:

      +
        +
      • Directing human readers to documentation about the feature
      • +
      • Providing tools with information about the specs in use, along with enough information to select and invoke an implementation
      • +
      +

      Feature URLs SHOULD be RFC 3986 URLs. When viewed, the URL SHOULD provide the specification of the selected version of the feature in some human‐readable form; a human reader should be able to click the link and go to the correct version of the docs.

      +

      Although they are not prohibited from doing so, it’s assumed that processors will not load the content of feature URLs. Published specifications are not required to be machine‐readable, and this spec places no requirements on the structure or syntax of the content to be found there.

      +

      There are, however, requirements on the structure of the URL itself:

      +
      Basic anatomy of a feature URL
      + https://spec.example.com/a/b/c/exampleFeature/v1.0 + +

      The final two segments of the URL’s path MUST contain the feature’s name and a version tag. The content of the URL up to and including the name—but excluding the / after the name and the version tag—is the feature’s identity. Trailing slashes at the end of the URL (ie, after the version tag) should be ignored. For the above example,

      identity: "https://spec.example.com/a/b/c/exampleFeature"
      A global identifier for the feature. Processors can treat this as an opaque string identifying the feature (but not the version of the feature) for purposes of selecting an appropriate implementation. The identity never has a trailing /.
      name: "exampleFeature"
      The feature’s name, for purposes of prefixing schema elements it defines.
      version: "v1.0"
      The tag for the version of the feature used to author the document. Processors MUST select an implementation of the feature which can satisfy the specified version.

      +

      The version tag MUST be a valid VersionTag. The name MUST be a valid GraphQL name which does not include the namespace separator ("__").

      +
      +

      5.1.1.1Ignore meaningless URL components

      +

      When extracting the URL’s name and version, processors MUST ignore any url components which are not assigned a meaning. This spec assigns meaning to the final two segments of the path. Other URL components—particularly query strings and fragments, if present—MUST be ignored for the purposes of extracting the name and version.

      +
      Ignoring meaningless parts of a URL
      + https://example.com/exampleSpec/v1.0/?key=val&k2=v2#frag + +
      +
      +

      5.1.1.2Why is versioning in the URL, not a directive argument?

      +

      The version is in the URL because when a human reader visits the URL, we would like them to be taken to the documentation for the version of the feature used by this document. Many text editors will turn URLs into hyperlinks, and it’s highly desirable that clicking the link takes the user to the correct version of the docs. Putting the version information in a separate argument to the @core directive would prevent this.

      +
      +
      +
      +

      5.1.2as: String

      +

      Change the names of directives and schema elements from this specification. The specified string MUST be a valid GraphQL name and MUST NOT contain the namespace separator (two underscores, "__") or end with an underscore.

      +

      When as: is provided, processors looking for prefixed schema elements MUST look for elements whose names are the specified name with the prefix replaced with the name provided to the as: argument.

      +
      Example № 5 Using @core(feature:, as:) to use a feature with a custom name
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://spec.example.com/example/v1.0", as: "eg")
      +{
      +  query: Query
      +}
      +
      +type User {
      +  # Specifying `as: "eg"` transforms @example into @eg
      +  name: String @eg(data: ITEM)
      +}
      +
      +# Additional specified schema elements must have their prefixes set
      +# to the new name.
      +#
      +# The spec at https://spec.example.com/example/v1.0 calls this enum
      +# `example__Data`, but because of the `as:` argument above, processors
      +# will use this `eg__Data` enum instead.
      +enum eg__Data {
      +  ITEM
      +}
      +
      +# Name transformation must also be applied to definitions pulled in from
      +# specifications.
      +directive @eg(data: eg__Data) on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +
      +
      +
      +

      6Prefixing

      +

      With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix:

      +
        +
      1. MUST match the name of the feature as derived from the feature’s specification URL,
      2. +
      3. MUST be a valid GraphQL name, and
      4. +
      5. MUST NOT contain the core namespace separator, which is two underscores ("__"), and
      6. +
      7. MUST NOT end with an underscore (which would create ambiguity between whether "x___y" is prefix x_ for element y or prefix x for element _y).
      8. +
      +

      Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid GraphQL name. For instance, the core specification (which you are currently reading) introduces an element named @core, and the join specification introduces an element named @join__field (among others).

      +
      +Note +that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like @feature__24hours.
      +

      A feature’s root directive is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the core specification introduces a @core directive. This directive has the same name as the feature (”core”), and so requires no prefix.

      +
      Example № 6 Using the @core directive without changing the prefix
      schema
      + @core(feature: "https://specs.apollo.dev/core/v0.1")
      + @core(feature: "https://spec.example.com/example/v1.0") {
      +  query: Query
      +}
      +
      +type User {
      +  name: String @example(data: ITEM)
      +}
      +
      +# An enum used to provide structured data to the example spec.
      +# It is prefixed with the name of the spec.
      +enum example__Data {
      +  ITEM
      +}
      +
      +directive @example(data: example__Data) on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +

      The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature’s name as a prefix.

      +
      +

      6.1Elements which must be prefixed

      +

      Feature specs MUST prefix the following schema elements:

      +
        +
      • the names of any object types, interfaces, unions, enums, or input object types defined by the feature
      • +
      • the names of any directives introduced in the spec, with the exception of the root directive, which must have the same name as the feature
      • +
      +
      Example № 7 Prefixing
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://spec.example.com/featureA/v1.0")
      +  @core(feature: "https://spec.example.com/featureB/v2.0", as: "B") {
      +  query: Query
      +}
      +
      +"""
      +featureA__SomeType is a type defined by feature A.
      +"""
      +type featureA__SomeType {
      +  """
      +  nativeField is a field defined by featureA on a type also defined
      +  by featureA (namely featureA__SomeType)
      +  """
      +  nativeField: Int @featureA__fieldDirective
      +}
      +
      +"""
      +featureA__SomeInput is an input specified by feature A
      +"""
      +input featureA__SomeInput {
      +  """
      +  nativeInputField is defined by featureA
      +  """
      +  nativeInputField: Int
      +}
      +
      +"""
      +featureA__Items is specified by feature A
      +"""
      +enum featureA__Items { ONE, TWO, THREE @B }
      +
      +"""
      +@B is the root directive defined by featureB
      +
      +Root directives are named after their feature
      +"""
      +directive @B on ENUM_VALUE
      +
      +"""
      +@featureA__fieldDirective is a non-root (prefixed) directive defined by featureA
      +"""
      +directive @featureA__fieldDirective on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +
      +
      +

      7Versioning

      + + + + + + +
      +PositiveDigit
      1|2|3|4|5|6|7|8|9
      +
      +

      Specs are versioned with a subset of a Semantic Version Number containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form vMajor.Minor, where both integers ≥ 0.

      +
      Example № 8 Valid version tags
      v2.2
      +v1.0
      +v1.1
      +v0.1
      +
      +

      As specified by semver, spec authors SHOULD increment the:

      +
        +
      • MAJOR version when you make incompatible API changes,
      • +
      • MINOR version when you add functionality in a backwards compatible manner
      • +
      +
      +

      Patch and pre‐release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch‐level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution.

      +

      As with semver, the 0.x version series is special: there is no expectation of compatibility between versions 0.x and 0.y. For example, a processor must not activate implementation 0.4 to satisfy a requested version of 0.2.

      +
      +

      7.1Satisfaction

      +

      Given a version requested by a document and an available version of an implementation, the following algorithm will determine if the available version can satisfy the requested version:

      +
      +Satisfies(requested, available)
        +
      1. If requested.Majoravailable.Major, return false
      2. +
      3. If requested.Major = 0, return requested.Minor = available.Minor
      4. +
      5. Return requested.Minoravailable.Minor
      6. +
      +
      +
      +
      +

      7.2Referencing versions and activating implementations

      +

      Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less‐deprecated version with a large major version.

      +

      If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can satisfy the version required by the document.

      +
      +
      +
      +

      8Processing Schemas

      +
      graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +

      A common use case is that of a processor which consumes a valid input schema and generates an output schema.

      +

      The general guidance for processor behavior is: don’t react to what you don’t understand.

      +

      Specifically, processors:

      +
        +
      • SHOULD pass through @core directives which reference unknown feature URLs
      • +
      • SHOULD pass through prefixed directives, types, and other schema elements
      • +
      • SHOULD pass through directives which are not associated with a @core feature
      • +
      +

      Processors MAY accept configuration which overrides these default behaviors.

      +

      Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case‐by‐case basis.

      +
      +
      +

      9Validations & Algorithms

      +

      This section lays out algorithms for processing core schemas.

      +

      Algorithms described in this section may produce validation failures if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified.

      +
      +

      9.1Bootstrapping

      +

      Determine the name of the core specification within the document.

      +

      It is possible to rename the core feature within a document. This process determines the actual name for the core feature if one is present.

      +
        +
      • Fails the Has Schema validation if there are no SchemaDefinitions in the document
      • +
      • Fails the Has Core Feature validation if the core feature itself is not referenced with a @core directive within the document
      • +
      • Fails the Bootstrap Core Feature Listed First validation if the reference to the core feature is not the first @core directive on the document’s SchemaDefinition
      • +
      • Fails the Core Directive Incorrect Definition validation if the @core directive definition does not match the directive as defined by this specification.
      • +
      +

      For the purposes of this algorithm, a directive’s definition in a schema matches a definition provided in this specification if:

      +
        +
      • Its arguments have the specified names, types, and default values (or lack thereof)
      • +
      • It is defined as repeatable if and only if the specification’s definition defines it as repeatable
      • +
      • The set of locations it belongs to is the same set of locations in the specification’s definition.
      • +
      +

      The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from matching:

      +
        +
      • The name of the directive (due to prefixing)
      • +
      • The order of arguments
      • +
      • The order of locations
      • +
      • The directive’s description string
      • +
      • Argument description strings
      • +
      • Directives applied to argument definitions
      • +
      +
      +Bootstrap(document)
        +
      1. Let schema be the only SchemaDefinition in document. (Note that legal GraphQL documents must include at most one SchemaDefinition.)
          +
        1. ...if no SchemaDefinitions are present in document, the Has Schema validation fails.
        2. +
        +
      2. +
      3. For each directive d on schema,
          +
        1. If d has a feature: argument which parses as a feature URL, and whose identity is "https://specs.apollo.dev/core/" and whose version is "v0.1", and either d has an as: argument whose value is equal to d‘s name or d does not have an as: argument and d‘s name is core:
            +
          1. If any directive on schema listed before d has the same name as d, the Bootstrap Core Feature Listed First validation fails.
          2. +
          3. If the definition of the directive d does not match the definition of @core in this specification, the Core Directive Incorrect Definition validation fails.
          4. +
          5. Otherwise, Return d‘s name.
          6. +
          +
        2. +
        +
      4. +
      5. If no matching directive was found, the Has Core Feature validation fails.
      6. +
      +
      +
      +
      +

      9.2Feature Collection

      +

      Collect a map of (featureName: String) → Directive, where Directive is a @core Directive which introduces the feature named featureName into the document.

      +
        +
      • Fails the Name Uniqueness validation if feature names are not unique within the document.
      • +
      • Fails Invalid Feature URL validation for any invalid feature URLs.
      • +
      +
      +CollectFeatures(document)
        +
      1. Let coreName be the name of the core feature found via Bootstrap(document)
      2. +
      3. Let features be a map of featureName: StringDirective, initially empty.
      4. +
      5. For each directive d named coreName on the SchemaDefinition within document,
          +
        1. Let specifiedFeatureName and version be the result of parsing d‘s feature: argument according to the specified rules for feature URLs
        2. +
        3. If the feature: is not present or fails to parse:
            +
          1. The Invalid Feature URL validation fails for d,
          2. +
          +
        4. +
        5. Let featureName be the d‘s as: argument or, if the argument is not present, specifiedFeatureName
        6. +
        7. If featureName exists within features, the Name Uniqueness validation fails.
        8. +
        9. Insert featureNamed into features
        10. +
        +
      6. +
      7. Return features
      8. +
      +
      +

      Prefixes, whether implicit or explicit, must be unique within a document. Valid:

      +
      Example № 9 Unique prefixes
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://spec.example.com/featureA/v1.0")
      +  @core(feature: "https://spec.example.com/featureB/v2.0", as: "B") {
      +  query: Query
      +}
      +

      It is also valid to reference multiple versions of the same spec under different prefixes:

      +
      Example № 10 Explicit prefixes allow multiple versions of the same spec to coexist within a Document
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/A/1.0")               # name is A
      +  @core(feature: "https://specs.example.com/A/2.0", as: "A2")     # name is A2
      +{
      +  query: Query
      +}
      +

      Without the explicit as:, the above would be invalid:

      +
      Counter Example № 11 Non‐unique prefixes with multiple versions of the same spec
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/A/1.0") # name is A
      +  @core(feature: "https://specs.example.com/A/2.0") # name is A
      +{
      +  query: Query
      +}
      +

      Different specs with the same prefix are also invalid:

      +
      Counter Example № 12 Different specs with non‐unique prefixes
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/A/1.0")              # name is A
      +  @core(feature: "https://www.specs.com/specA/1.1", as: "A")     # name is A
      +{
      +  query: Query
      +}
      +
      +
      +

      9.3Assign Features

      +

      Create a map of element: Any Named Elementfeature: Directive | null, associating every named schema element within the document with a feature directive, or null if it is not associated with a feature.

      +
      +AssignFeatures(document)
        +
      1. Let features be the result of collecting features via CollectFeatures(document)
      2. +
      3. Let assignments be a map of (element: Any Named Element) → feature: Directive | null, initally empty
      4. +
      5. For each named schema element e within the document
          +
        1. Let name be the name of the e
        2. +
        3. If e is a Directive and name is a key within features,
            +
          1. Insert efeatures[name] into assignments
          2. +
          3. Continue to next e
          4. +
          +
        4. +
        5. If name begins with "__",
            +
          1. Insert enull into assignments
          2. +
          3. Continue to next e
          4. +
          +
        6. +
        7. If name contains the substring "__",
            +
          1. Partition name into [prefix, base] at the first "__" (that is, find the shortest prefix and longest base such that name = prefix + "__" + base)
          2. +
          3. If prefix exists within features, insert efeatures[prefix] into assignments
              +
            1. Else, insert enull into assignments
            2. +
            +
          4. +
          5. Continue to next e
          6. +
          +
        8. +
        9. Insert enull into assignments
        10. +
        +
      6. +
      7. Return assignments
      8. +
      +
      +
      +
      +

      9.4Is In API?

      +

      Determine if any schema element is included in the API described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a name.

      +
      +IsInAPI(element)
        +
      1. Let assignments be the result of assigning features to elements via AssignFeatures(document)
      2. +
      3. If assignments[element] is null, Return true
      4. +
      5. Else, Return false
      6. +
      +
      +
      +Note +Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification.
      +
      +
      +

      §Index

      1. AssignFeatures
      2. Bootstrap
      3. CollectFeatures
      4. Digit
      5. IsInAPI
      6. Major
      7. Minor
      8. NumericIdentifier
      9. PositiveDigit
      10. Satisfies
      11. Version
      12. VersionTag
      + + +
      + + +
      + + + diff --git a/core/v0.1/netlify.toml b/core/v0.1/netlify.toml new file mode 100644 index 0000000..f1a19b0 --- /dev/null +++ b/core/v0.1/netlify.toml @@ -0,0 +1,6 @@ +# Netlify Admin: https://app.netlify.com/sites/apollo-specs-core/ +# Docs: https://docs.netlify.com/configure-builds/file-based-configuration/ + +[build] + command = "npm run build" + publish = ".dist/" diff --git a/core/v0.1/package.json b/core/v0.1/package.json new file mode 100644 index 0000000..03a1ce4 --- /dev/null +++ b/core/v0.1/package.json @@ -0,0 +1,18 @@ +{ + "name": "@apollo/core", + "version": "1.0.0", + "description": "for GraphQL schemas with extensible metadata", + "repository": "https://github.com/apollo-specs/core", + "main": "index.js", + "scripts": { + "build": "rsync -avz --exclude .dist . .dist && spec-md core.spec.md > .dist/index.html" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@queerviolet/speck": "git://github.com/queerviolet/speck.git#main", + "chokidar-cli": "^2.1.0", + "watch": "^1.0.2" + } +} diff --git a/core/v0.1/prefix-uniqueness.graphql b/core/v0.1/prefix-uniqueness.graphql new file mode 100644 index 0000000..a26a309 --- /dev/null +++ b/core/v0.1/prefix-uniqueness.graphql @@ -0,0 +1,25 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0", as: "A2") # name is A2 +{ + query: Query +} + +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0") # name is A +{ + query: Query +} + +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://www.specs.com/specA/1.1", as: "A") # name is A +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.1/prefixing.graphql b/core/v0.1/prefixing.graphql new file mode 100644 index 0000000..12c5da7 --- /dev/null +++ b/core/v0.1/prefixing.graphql @@ -0,0 +1,46 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://spec.example.com/featureA/v1.0") + @core(feature: "https://spec.example.com/featureB/v2.0", as: "B") { + query: Query +} + +""" +featureA__SomeType is a type defined by feature A. +""" +type featureA__SomeType { + """ + nativeField is a field defined by featureA on a type also defined + by featureA (namely featureA__SomeType) + """ + nativeField: Int @featureA__fieldDirective +} + +""" +featureA__SomeInput is an input specified by feature A +""" +input featureA__SomeInput { + """ + nativeInputField is defined by featureA + """ + nativeInputField: Int +} + +""" +featureA__Items is specified by feature A +""" +enum featureA__Items { ONE, TWO, THREE @B } + +""" +@B is the root directive defined by featureB + +Root directives are named after their feature +""" +directive @B on ENUM_VALUE + +""" +@featureA__fieldDirective is a non-root (prefixed) directive defined by featureA +""" +directive @featureA__fieldDirective on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.2/.gitignore b/core/v0.2/.gitignore new file mode 100644 index 0000000..b768a31 --- /dev/null +++ b/core/v0.2/.gitignore @@ -0,0 +1,3 @@ +node_modules +.dist +package-lock.json \ No newline at end of file diff --git a/core/v0.2/bad-non-unique-prefix-multi.graphql b/core/v0.2/bad-non-unique-prefix-multi.graphql new file mode 100644 index 0000000..221f3e4 --- /dev/null +++ b/core/v0.2/bad-non-unique-prefix-multi.graphql @@ -0,0 +1,9 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0") # name is A +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.2/bad-non-unique-prefix.graphql b/core/v0.2/bad-non-unique-prefix.graphql new file mode 100644 index 0000000..749db43 --- /dev/null +++ b/core/v0.2/bad-non-unique-prefix.graphql @@ -0,0 +1,9 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://www.specs.com/specA/1.1", as: "A") # name is A +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.2/basic.graphql b/core/v0.2/basic.graphql new file mode 100644 index 0000000..454191b --- /dev/null +++ b/core/v0.2/basic.graphql @@ -0,0 +1,14 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/example/v1.0") +{ + query: Query +} + +type Query { + field: Int @example +} + +directive @example on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.2/core-v0.2.md b/core/v0.2/core-v0.2.md new file mode 100644 index 0000000..ff1aa04 --- /dev/null +++ b/core/v0.2/core-v0.2.md @@ -0,0 +1,577 @@ +# Core Schemas + +

      flexible metadata for GraphQL schemas

      + +```raw html + + + +
      StatusRelease
      Version0.2
      + + +``` + +[GraphQL](https://spec.graphql.org/) provides directives as a means of attaching user-defined metadata to a GraphQL document. Directives are highly flexible, and can be used to suggest behavior and define features of a graph which are not otherwise evident in the schema. + +Alas, *GraphQL does not provide a mechanism to globally identify or version directives*. Given a particular directive—e.g. `@join`—processors are expected to know how to interpret the directive based only on its name, definition within the document, and additional configuration from outside the document. This means that programs interpreting these directives have two options: + + 1. rely on a hardcoded interpretation for directives with certain signatures, or + 2. accept additional configuration about how to interpret directives in the schema. + +The first solution is fragile, particularly as GraphQL has no built-in namespacing mechanisms, so the possibility of name collisions always looms. + +The second is unfortunate: GraphQL schemas are generally intended to be self-describing, and requiring additional configuration subtly undermines this guarantee: given just a schema, programs do not necessarily know how to interpret it, and certainly not how to serve it. It also creates the possibility for the schema and configuration to fall out of sync, leading to issues which can manifest late in a deployment pipeline. + +Introducing **core schemas**. + +```html diagram + + + + + core schema + +``` + +A basic core schema: + +:::[example](basic.graphql) -- A basic core schema + +**Core schemas** provide a concise mechanism for schema documents to specify the metadata they provide. Metadata is grouped into **features**, which typically define directives and associated types (e.g. scalars and inputs which serve as directive inputs). Additionally, core schemas provide: + - [**Flexible namespacing rules.**](#sec-Prefixing) It is always possible to represent any GraphQL schema within a core schema document. Additionally, documents can [choose the names](#@core/as) they use for the features they reference, guaranteeing that namespace collisions can always be resolved. + - [**Versioning.**](#sec-Versioning) Feature specifications follow [semver-like semantic versioning principles](#sec-Versioning), which helps schema processors determine if they are able to correctly interpret a document's metadata. + +**Core schemas are not a new language.** All core schema documents are valid GraphQL schema documents. However, this specification introduces new requirements, so not all valid GraphQL schemas are valid core schemas. + +The broad intention behind core schemas is to provide a *single document* which provides all the necessary configuration for programs that process and serve the schema to GraphQL clients, primarily by following directives in order to determine how to resolve queries made against that schema. + +# Parts of a Core Schema + +When talking about a core schema, we can broadly break it into two pieces: +- an **API** consisting of schema elements (objects, interfaces, enums, directives, etc.) which SHOULD be served to clients, and +- **machinery** containing document metadata. This typically consists of directives and associated input types (such as enums and input objects), but may include any schema element. Machinery MUST NOT be served to clients. Specifically, machinery MUST NOT be included in introspection responses or used to validate or execute queries. + +This reflects how core schemas are used: a core schema contains a GraphQL interface (the *API*) along with metadata about how to implement that interface (the *machinery*). Exposing the machinery to clients is unnecessary, and may in some cases constitute a security issue (for example, the machinery for a public-facing graph router will likely reference internal services, possibly exposing network internals which should not be visible to the general public). + +A key feature of core schemas is that it is always possible to derive a core schema's API without any knowledge of the features used by the document (with the exception of the `core` feature itself). Specifically, named elements are not included in the API schema if they are named `something__likeThis` or are a directive named `@something`, and `something` is the prefix of a feature declared with {@core}. + +A formal description is provided by the [IsInAPI](#sec-Is-In-API-) algorithm. + +# Actors + +```mermaid diagram -- Actors who may be interested in the core schemas +graph TB + classDef bg fill:none,color:#22262E; + author("👩🏽‍💻 🤖  Author"):::bg-->schema(["☉ Core Schema"]):::bg + schema-->proc1("🤖  Processor"):::bg + proc1-->output1(["☉ Core Schema[0]"]):::bg + output1-->proc2("🤖  Processor"):::bg + proc2-->output2(["☉ Core Schema[1]"]):::bg + output2-->etc("..."):::bg + etc-->final(["☉ Core Schema [final]"]):::bg + final-->core("🤖 Data Core"):::bg + schema-->reader("👩🏽‍💻 Reader"):::bg + output1-->reader + output2-->reader + final-->reader +``` + +- **Authors (either human or machine)** write an initial core schema as specified in this document, including versioned {@core} requests for all directives they use +- **Machine processors** can process core schemas and output new core schemas. The versioning of directives and associated schema elements provided by the {@core} allows processors to operate on directives they understand and pass through directives they do not. +- **Human readers** can examine the core schema at various stages of processing. At any stage, they can examine the {@core} directives and follow URLs to the specification, receiving an explanation of the requirements of the specification and what new directives, types, and other schema objects are available within the document. +- **Data cores** can then pick up the processed core schema and provide some data-layer service with it. Typically this means serving the schema's API as a GraphQL endpoint, using metadata defined by machinery to inform how it processes operations it receives. However, data cores may perform other tasks described in the core schema, such as routing to backend services, caching commonly-accessed fields and queries, and so on. The term "data core" is intended to capture this multiplicity of possible activities. + +# Basic Requirements + +Core schemas: + 1. MUST be valid GraphQL schema documents, + 2. MUST contain exactly one `SchemaDefinition`, and + 3. MUST use the {@core} directive on their schema definition to declare any features they reference by using {@core} to reference a [well-formed feature URL](#@core/feature). + +The first {@core} directive on the schema MUST reference the core spec itself, i.e. this document. + +:::[example](basic.graphql) -- Basic core schema using {@core} and `@example` + +## Unspecified directives are passed through by default + +Existing schemas likely contain definitions for directives which are not versioned, have no specification document, and are intended mainly to be passed through. This is the default behavior for core schema processors: + +```graphql example -- Unspecified directives are passed through +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") +{ + query: Query +} + +type SomeType { + field: Int @another +} + +# `@another` is unspecified. Core processors will not extract metadata from +# it, but its definition and all usages within the schema will be exposed +# in the API. +directive @another on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +## Renaming core itself + +It is possible to rename the core feature itself with the same [`as:`](#@core/as) mechanism used for all features: + +```graphql example -- Renaming {@core} to {@coreSchema} +schema + @coreSchema(feature: "https://specs.apollo.dev/core/v0.1", as: "coreSchema") + @coreSchema(feature: "https://example.com/example/v1.0") +{ + query: Query +} + +type SomeType { + field: Int @example +} + +directive @coreSchema(feature: String!, as: String) + repeatable on SCHEMA +directive @example on FIELD_DEFINITION +``` + +# Directive Definitions + +All core schemas use the [{@core}](#@core) directive to declare their use of the `core` feature itself as well as any other core features they use. + +In order to use these directives in your schema, GraphQL requires you to include their definitions in your schema. + +Processors MUST validate that you have defined the directives with the same arguments, locations, and `repeatable` flag as given below. Specifically, the [bootstrapping](#sec-Bootstrapping) algorithm validates that the `@core` directive has a definition matching the definition given below. (The bootstrapping algorithm does not require processors to validate other aspects of the directive declaration such as description strings or argument ordering. The main purpose of this validation is to ensure that directive arguments have the type and default values expected by the specification.) + +The following declares the directive defined by this specification. You SHOULD define the directives in your core schema by including the following text in your schema document. + +```graphql +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +When writing a specification for your own core feature, you SHOULD include a section like this one with sample definitions to copy into schemas, and you SHOULD require processors to validate that directive definitions in documents match your sample definitions. + +# Directives + +##! @core + +Declare a core feature present in this schema. + +```graphql definition +directive @core( + feature: String!, + as: String, + for: core__Purpose) + repeatable on SCHEMA +``` + +Documents MUST include a definition for the {@core} directive which includes all of the arguments defined above with the same types and default values. + +###! feature: String! + +A feature URL specifying the directive and associated schema elements. When viewed, the URL SHOULD provide the content of the appropriate version of the specification in some human-readable form. In short, a human reader should be able to click the link and go to the docs for the version in use. There are specific requirements on the format of the URL, but it is not required that the *content* be machine-readable in any particular way. + +Feature URLs contain information about the spec's [prefix](#sec-Prefixing) and [version](#sec-Versioning). + +Feature URLs serve two main purposes: + - Directing human readers to documentation about the feature + - Providing tools with information about the specs in use, along with enough information to select and invoke an implementation + +Feature URLs SHOULD be [RFC 3986 URLs](https://tools.ietf.org/html/rfc3986). When viewed, the URL SHOULD provide the specification of the selected version of the feature in some human-readable form; a human reader should be able to click the link and go to the correct version of the docs. + +Although they are not prohibited from doing so, it's assumed that processors will not load the content of feature URLs. Published specifications are not required to be machine-readable, and [this spec](.) places no requirements on the structure or syntax of the content to be found there. + +There are, however, requirements on the structure of the URL itself: + +```html diagram -- Basic anatomy of a feature URL + + https://spec.example.com/a/b/c/exampleFeature/v1.0 + +``` + +The final two segments of the URL's [path](https://tools.ietf.org/html/rfc3986#section-3.3) MUST contain the feature's name and a [version tag](#sec-Versioning). The content of the URL up to and including the name—but excluding the `/` after the name and the version tag—is the feature's *identity*. Trailing slashes at the end of the URL (ie, after the version tag) should be ignored. For the above example, +
      +
      `identity: "https://spec.example.com/a/b/c/exampleFeature"`
      +
      A global identifier for the feature. Processors can treat this as an opaque string identifying the feature (but not the version of the feature) for purposes of selecting an appropriate implementation. The identity never has a trailing `/`.
      +
      `name: "exampleFeature"`
      +
      The feature's name, for purposes of [prefixing](#sec-Prefixing) schema elements it defines.
      +
      `version: "v1.0"`
      +
      The tag for the [version](#sec-Versioning) of the feature used to author the document. Processors MUST select an implementation of the feature which can [satisfy](#sec-Satisfaction) the specified version.
      +
      + +The version tag MUST be a valid {VersionTag}. The name MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) which does not include the namespace separator ({"__"}). + +#### Ignore meaningless URL components + +When extracting the URL's `name` and `version`, processors MUST ignore any url components which are not assigned a meaning. This spec assigns meaning to the final two segments of the [path](https://tools.ietf.org/html/rfc3986#section-3.3). Other URL components—particularly query strings and fragments, if present—MUST be ignored for the purposes of extracting the `name` and `version`. + +```html diagram -- Ignoring meaningless parts of a URL + + https://example.com/exampleSpec/v1.0/?key=val&k2=v2#frag + +``` + +#### Why is versioning in the URL, not a directive argument? + +The version is in the URL because when a human reader visits the URL, we would like them to be taken to the documentation for the *version of the feature used by this document*. Many text editors will turn URLs into hyperlinks, and it's highly desirable that clicking the link takes the user to the correct version of the docs. Putting the version information in a separate argument to the {@core} directive would prevent this. + +###! as: String + +Change the [names](#sec-Prefixing) of directives and schema elements from this specification. The specified string MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name) and MUST NOT contain the namespace separator (two underscores, {"__"}) or end with an underscore. + +When [`as:`](#@core/as) is provided, processors looking for [prefixed schema elements](#sec-Elements-which-must-be-prefixed) MUST look for elements whose names are the specified name with the prefix replaced with the name provided to the `as:` argument. + +```graphql example -- Using {@core}(feature:, as:) to use a feature with a custom name +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://spec.example.com/example/v1.0", as: "eg") +{ + query: Query +} + +type User { + # Specifying `as: "eg"` transforms @example into @eg + name: String @eg(data: ITEM) +} + +# Additional specified schema elements must have their prefixes set +# to the new name. +# +# The spec at https://spec.example.com/example/v1.0 calls this enum +# `example__Data`, but because of the `as:` argument above, processors +# will use this `eg__Data` enum instead. +enum eg__Data { + ITEM +} + +# Name transformation must also be applied to definitions pulled in from +# specifications. +directive @eg(data: eg__Data) on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +###! for: core__Purpose + +An optional [purpose](#core__Purpose) for this feature. This hints to consumers as to whether they can safely ignore metadata from a given feature. + +By default, core features SHOULD fail open. This means that an unknown feature SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally. + +This behavior is different for features with a specified purpose: + - [`SECURITY`](#core__Purpose/SECURITY) features convey metadata necessary to securely resolve fields within the schema + - [`EXECUTION`](#core__Purpose/EXECUTION) features convey metadata necessary to correctly resolve fields within the schema + +# Enums + +##! core__Purpose + +```graphql definition +enum core__Purpose { + SECURITY + EXECUTION +} +``` + +The role of a feature referenced with {@core}. + +This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail-open behavior of core schema consumers is undesirable. + +Note we'll refer to directives from features which are `for: SECURITY` or `for: EXECUTION` as "`SECURITY` directives" and "`EXECUTION` directives", respectively. + +###! SECURITY + +`SECURITY` features provide metadata necessary to securely resolve fields. For instance, a hypothetical {auth} feature may provide an {@auth} directive to flag fields which require authorization. If a data core does not support the {auth} feature and serves those fields anyway, these fields will be accessible without authorization, compromising security. + +Security-conscious consumers MUST NOT serve a field if: + - the schema definition has **any** unsupported SECURITY directives, + - the field's parent type definition has **any** unsupported SECURITY directives, + - the field's return type definition has **any** unsupported SECURITY directives, or + - the field definition has **any** unsupported SECURITY directives + +Such fields are *not securely resolvable*. Security-conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it. + +Less security-conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY features during development. + +More security-conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY features, even if those features are never used to annotate the schema. + +###! EXECUTION + +`EXECUTION` features provide metadata necessary to correctly resolve fields. For instance, a hypothetical {ts} feature may provide a `@ts__resolvers` annotation which references a TypeScript module of field resolvers. A consumer which does not support the {ts} feature will be unable to correctly resolve such fields. + +Consumers MUST NOT serve a field if: + - the schema's definition has **any** unsupported EXECUTION directives, + - the field's parent type definition has **any** unsupported EXECUTION directives, + - the field's return type definition has **any** unsupported EXECUTION directives, or + - the field definition has **any** unsupported EXECUTION directives + +Such fields are *unresolvable*. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema. + +# Prefixing + +With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix: + 1. MUST match the name of the feature as derived from the feature's specification URL, + 2. MUST be a valid [GraphQL name](https://spec.graphql.org/draft/#Name), and + 3. MUST NOT contain the core namespace separator, which is two underscores ({"__"}), and + 4. MUST NOT end with an underscore (which would create ambiguity between whether {"x___y"} is prefix `x_` for element `y` or prefix `x` for element `_y`). + +Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid [GraphQL name](https://spec.graphql.org/draft/#Name). For instance, the `core` specification (which you are currently reading) introduces an element named [{@core}](#@core), and the `join` specification introduces an element named {@join__field} (among others). + +Note that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like `@feature__24hours`. + +A feature's *root directive* is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the `core` specification introduces a {@core} directive. This directive has the same name as the feature ("`core`"), and so requires no prefix. + +```graphql example -- Using the @core directive without changing the prefix +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://spec.example.com/example/v1.0") { + query: Query +} + +type User { + name: String @example(data: ITEM) +} + +# An enum used to provide structured data to the example spec. +# It is prefixed with the name of the spec. +enum example__Data { + ITEM +} + +directive @example(data: example__Data) on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA +``` + +The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature's name as a prefix. + +## Elements which must be prefixed + +Feature specs MUST prefix the following schema elements: + - the names of any object types, interfaces, unions, enums, or input object types defined by the feature + - the names of any directives introduced in the spec, with the exception of the *root directive*, which must have the same name as the feature + +:::[example](prefixing.graphql) -- Prefixing + +# Versioning + +VersionTag : "v" Version + +Version : Major "." Minor + +Major : NumericIdentifier + +Minor : NumericIdentifier + +NumericIdentifier : "0" + | PositiveDigit Digit* + +Digit : "0" | PositiveDigit + +PositiveDigit : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +Specs are versioned with a **subset** of a [Semantic Version Number](https://semver.org/spec/v2.0.0.html) containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form `v`{Major}`.`{Minor}, where both integers >= 0. + +```text example -- Valid version tags +v2.2 +v1.0 +v1.1 +v0.1 +``` + +As specified by semver, spec authors SHOULD increment the: + +{++ + +- MAJOR version when you make incompatible API changes, +- MINOR version when you add functionality in a backwards compatible manner + +++} + +Patch and pre-release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch-level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution. + +As with [semver](https://semver.org/spec/v2.0.0.html), the `0.x` version series is special: there is no expectation of compatibility between versions `0.x` and `0.y`. For example, a processor must not activate implementation `0.4` to satisfy a requested version of `0.2`. + +## Satisfaction + +Given a version {requested} by a document and an {available} version of an implementation, the following algorithm will determine if the {available} version can satisfy the {requested} version: + +Satisfies(requested, available) : + 1. If {requested}.{Major} ≠ {available}.{Major}, return {false} + 2. If {requested}.{Major} = 0, return {requested}.{Minor} = {available}.{Minor} + 3. Return {requested}.{Minor} <= {available}.{Minor} + +## Referencing versions and activating implementations + +Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less-deprecated version with a large major version. + +If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can [satisfy](#sec-Satisfaction) the version required by the document. + + +# Processing Schemas + +```mermaid diagram +graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +``` + +A common use case is that of a processor which consumes a valid input schema and generates an output schema. + +The general guidance for processor behavior is: don't react to what you don't understand. + +Specifically, processors: + - SHOULD pass through {@core} directives which reference unknown feature URLs + - SHOULD pass through prefixed directives, types, and other schema elements + - SHOULD pass through directives which are not [associated with](#AssignFeatures) a {@core} feature + +Processors MAY accept configuration which overrides these default behaviors. + +Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case-by-case basis. + +# Validations & Algorithms + +This section lays out algorithms for processing core schemas. + +Algorithms described in this section may produce *validation failures* if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified. + +## Bootstrapping + +Determine the name of the core specification within the document. + +It is possible to [rename the core feature](#sec-Renaming-core-itself) within a document. This process determines the actual name for the core feature if one is present. + +- **Fails** the *Has Schema* validation if there are no SchemaDefinitions in the document +- **Fails** the *Has Core Feature* validation if the `core` feature itself is not referenced with a {@core} directive within the document +- **Fails** the *Bootstrap Core Feature Listed First* validation if the reference to the `core` feature is not the first {@core} directive on the document's SchemaDefinition +- **Fails** the *Core Directive Incorrect Definition* validation if the {@core} directive definition does not *match* the directive as defined by this specification. + +For the purposes of this algorithm, a directive's definition in a schema *matches* a definition provided in this specification if: +- Its arguments have the specified names, types, and default values (or lack thereof) +- It is defined as `repeatable` if and only if the specification's definition defines it as `repeatable` +- The set of locations it belongs to is the same set of locations in the specification's definition. + +The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from *matching*: +- The name of the directive (due to [prefixing](#sec-Prefixing)) +- The order of arguments +- The order of locations +- The directive's description string +- Argument description strings +- Directives applied to argument definitions + +Bootstrap(document) : +1. Let {schema} be the only SchemaDefinition in {document}. (Note that legal GraphQL documents [must include at most one SchemaDefinition](http://spec.graphql.org/draft/#sec-Root-Operation-Types).) + 1. ...if no SchemaDefinitions are present in {document}, the ***Has Schema* validation fails**. +1. For each directive {d} on {schema}, + 1. If {d} has a [`feature:`](#@core/feature) argument which [parses as a feature URL](#@core/feature), *and* whose identity is {"https://specs.apollo.dev/core/"} *and* whose version is {"v0.1"}, *and either* {d} has an [`as:`](#@core/as) argument whose value is equal to {d}'s name *or* {d} does not have an [`as:`](#@core/as) argument and {d}'s name is `core`: + - If any directive on {schema} listed before {d} has the same name as {d}, the ***Bootstrap Core Feature Listed First* validation fails**. + - If the definition of the directive {d} does not *match* the [definition of {@core} in this specification](#@core), the ***Core Directive Incorrect Definition* validation fails**. + - Otherwise, **Return** {d}'s name. +- If no matching directive was found, the ***Has Core Feature* validation fails**. + +## Feature Collection + +Collect a map of ({featureName}: `String`) -> `Directive`, where `Directive` is a {@core} Directive which introduces the feature named {featureName} into the document. + +- **Fails** the *Name Uniqueness* validation if feature names are not unique within the document. +- **Fails** *Invalid Feature URL* validation for any invalid feature URLs. + +CollectFeatures(document) : + - Let {coreName} be the name of the core feature found via {Bootstrap(document)} + - Let {features} be a map of {featureName}: `String` -> `Directive`, initially empty. + - For each directive {d} named {coreName} on the SchemaDefinition within {document}, + - Let {specifiedFeatureName} and {version} be the result of parsing {d}'s `feature:` argument according to the [specified rules for feature URLs](#@core/feature) + - If the `feature:` is not present or fails to parse: + - The ***Invalid Feature URL* validation fails** for {d}, + - Let {featureName} be the {d}'s [`as:`](#@core/as) argument or, if the argument is not present, {specifiedFeatureName} + - If {featureName} exists within {features}, the ***Name Uniqueness* validation fails**. + - Insert {featureName} => {d} into {features} + - **Return** {features} + + +Prefixes, whether implicit or explicit, must be unique within a document. Valid: + +:::[example](prefixing.graphql#schema[0]) -- Unique prefixes + +It is also valid to reference multiple versions of the same spec under different prefixes: + +:::[example](prefix-uniqueness.graphql#schema[0]) -- Explicit prefixes allow multiple versions of the same spec to coexist within a Document + +Without the explicit [`as:`](#@core/as), the above would be invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[1]) -- Non-unique prefixes with multiple versions of the same spec + +Different specs with the same prefix are also invalid: + +:::[counter-example](prefix-uniqueness.graphql#schema[2]) -- Different specs with non-unique prefixes + +## Assign Features + +Create a map of {element}: *Any Named Element* -> {feature}: `Directive` | {null}, associating every named schema element within the document with a feature directive, or {null} if it is not associated with a feature. + +AssignFeatures(document) : + - Let {features} be the result of collecting features via {CollectFeatures(document)} + - Let {assignments} be a map of ({element}: *Any Named Element*) -> {feature}: `Directive` | {null}, initally empty + - For each named schema element {e} within the {document} + - Let {name} be the name of the {e} + - If {e} is a Directive and {name} is a key within {features}, + - Insert {e} => {features}`[`{name}`]` into {assignments} + - **Continue** to next {e} + - If {name} begins with {"__"}, + - Insert {e} => {null} into {assignments} + - **Continue** to next {e} + - If {name} contains the substring {"__"}, + - Partition {name} into `[`{prefix}, {base}`]` at the first {"__"} (that is, find the shortest {prefix} and longest {base} such that {name} = {prefix} + {"__"} + {base}) + - If {prefix} exists within {features}, insert {e} => {features}`[`{prefix}`]` into {assignments} + - Else, insert {e} => {null} into {assignments} + - **Continue** to next {e} + - Insert {e} => {null} into {assignments} + - **Return** {assignments} + +## Is In API? + +Determine if any schema element is included in the [API](#sec-Parts-of-a-Core-Schema) described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a [name](https://spec.graphql.org/draft/#Name). + +IsInAPI(element) : + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - If {assignments}`[`{element}`]` is {null}, **Return** {true} + - Else, **Return** {false} + +Note: Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification. + +## Is Affected By Feature? + +Determine if a schema element is *affected* by a given feature. + +IsAffected(element, feature): + - Let {assignments} be the result of assigning features to elements via {AssignFeatures(document)} + - For each directive {d} on {element}, If {assignments}`[`{d}`]` is {feature}, **Return** {true} + - If {element} is a FieldDefinition, + - Let {parent} be the parent ObjectDefinition or InterfaceDefinition for {element} + - If {IsAffected(parent, feature)}, **Return** {true} + - For each argument type {a} declared on {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for argument {a} + - If {IsAffected(t, feature)}, **Return** {true} + - Let {return} be the ObjectDefinition, InterfaceDefinition, or UnionDefinition for {element}'s return type + - If {IsAffected(return, feature)}, **Return** {true} + - If {element} is an InputDefinition, + - For each InputFieldDefinition {field} within {element}, + - Let {t} be the InputDefinition, EnumDefinition, or ScalarDefinition for the type of {field} + - If {IsAffected(t, feature)}, **Return** {true} + - If {element} is an EnumDefinition, + - For each EnumValueDefinition {value} in {element}, + - If {IsAffected(value, feature)}, **Return** {true} + + + diff --git a/core/v0.2/good-unique-prefix-multi.graphql b/core/v0.2/good-unique-prefix-multi.graphql new file mode 100644 index 0000000..8eafe08 --- /dev/null +++ b/core/v0.2/good-unique-prefix-multi.graphql @@ -0,0 +1,9 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0", as: "A2") # name is A2 +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.2/index.html b/core/v0.2/index.html new file mode 100644 index 0000000..b981165 --- /dev/null +++ b/core/v0.2/index.html @@ -0,0 +1,1829 @@ + + + + +Core Schemas + + + + + +
      +
      +

      Core Schemas

      +
      +

      flexible metadata for GraphQL schemas

      + + + +
      StatusRelease
      Version0.2
      + + +

      GraphQL provides directives as a means of attaching user‐defined metadata to a GraphQL document. Directives are highly flexible, and can be used to suggest behavior and define features of a graph which are not otherwise evident in the schema.

      +

      Alas, GraphQL does not provide a mechanism to globally identify or version directives. Given a particular directive—e.g. @join—processors are expected to know how to interpret the directive based only on its name, definition within the document, and additional configuration from outside the document. This means that programs interpreting these directives have two options:

      +
        +
      1. rely on a hardcoded interpretation for directives with certain signatures, or
      2. +
      3. accept additional configuration about how to interpret directives in the schema.
      4. +
      +

      The first solution is fragile, particularly as GraphQL has no built‐in namespacing mechanisms, so the possibility of name collisions always looms.

      +

      The second is unfortunate: GraphQL schemas are generally intended to be self‐describing, and requiring additional configuration subtly undermines this guarantee: given just a schema, programs do not necessarily know how to interpret it, and certainly not how to serve it. It also creates the possibility for the schema and configuration to fall out of sync, leading to issues which can manifest late in a deployment pipeline.

      +

      Introducing core schemas.

      +
      + + + + core schema + +

      A basic core schema:

      +
      Example № 1 A basic core schema
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/example/v1.0")
      +{
      +  query: Query
      +}
      +
      +type Query {
      +  field: Int @example
      +}
      +
      +directive @example on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +

      Core schemas provide a concise mechanism for schema documents to specify the metadata they provide. Metadata is grouped into features, which typically define directives and associated types (e.g. scalars and inputs which serve as directive inputs). Additionally, core schemas provide:

      + +

      Core schemas are not a new language. All core schema documents are valid GraphQL schema documents. However, this specification introduces new requirements, so not all valid GraphQL schemas are valid core schemas.

      +

      The broad intention behind core schemas is to provide a single document which provides all the necessary configuration for programs that process and serve the schema to GraphQL clients, primarily by following directives in order to determine how to resolve queries made against that schema.

      +
      + +
      +
      +

      1Parts of a Core Schema

      +

      When talking about a core schema, we can broadly break it into two pieces:

      +
        +
      • an API consisting of schema elements (objects, interfaces, enums, directives, etc.) which SHOULD be served to clients, and
      • +
      • machinery containing document metadata. This typically consists of directives and associated input types (such as enums and input objects), but may include any schema element. Machinery MUST NOT be served to clients. Specifically, machinery MUST NOT be included in introspection responses or used to validate or execute queries.
      • +
      +

      This reflects how core schemas are used: a core schema contains a GraphQL interface (the API) along with metadata about how to implement that interface (the machinery). Exposing the machinery to clients is unnecessary, and may in some cases constitute a security issue (for example, the machinery for a public‐facing graph router will likely reference internal services, possibly exposing network internals which should not be visible to the general public).

      +

      A key feature of core schemas is that it is always possible to derive a core schema’s API without any knowledge of the features used by the document (with the exception of the core feature itself). Specifically, named elements are not included in the API schema if they are named something__likeThis or are a directive named @something, and something is the prefix of a feature declared with @core.

      +

      A formal description is provided by the IsInAPI algorithm.

      +
      +
      +

      2Actors

      +
      Actors who may be interested in the core schemas
      graph TB + classDef bg fill:none,color:#22262E; + author("👩🏽‍💻 🤖  Author"):::bg-->schema(["☉ Core Schema"]):::bg + schema-->proc1("🤖  Processor"):::bg + proc1-->output1(["☉ Core Schema[0]"]):::bg + output1-->proc2("🤖  Processor"):::bg + proc2-->output2(["☉ Core Schema[1]"]):::bg + output2-->etc("..."):::bg + etc-->final(["☉ Core Schema [final]"]):::bg + final-->core("🤖 Data Core"):::bg + schema-->reader("👩🏽‍💻 Reader"):::bg + output1-->reader + output2-->reader + final-->reader +
        +
      • Authors (either human or machine) write an initial core schema as specified in this document, including versioned @core requests for all directives they use
      • +
      • Machine processors can process core schemas and output new core schemas. The versioning of directives and associated schema elements provided by the @core allows processors to operate on directives they understand and pass through directives they do not.
      • +
      • Human readers can examine the core schema at various stages of processing. At any stage, they can examine the @core directives and follow URLs to the specification, receiving an explanation of the requirements of the specification and what new directives, types, and other schema objects are available within the document.
      • +
      • Data cores can then pick up the processed core schema and provide some data‐layer service with it. Typically this means serving the schema’s API as a GraphQL endpoint, using metadata defined by machinery to inform how it processes operations it receives. However, data cores may perform other tasks described in the core schema, such as routing to backend services, caching commonly‐accessed fields and queries, and so on. The term “data core” is intended to capture this multiplicity of possible activities.
      • +
      +
      +
      +

      3Basic Requirements

      +

      Core schemas:

      +
        +
      1. MUST be valid GraphQL schema documents,
      2. +
      3. MUST contain exactly one SchemaDefinition, and
      4. +
      5. MUST use the @core directive on their schema definition to declare any features they reference by using @core to reference a well‐formed feature URL.
      6. +
      +

      The first @core directive on the schema MUST reference the core spec itself, i.e. this document.

      +
      Example № 2 Basic core schema using @core and @example
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/example/v1.0")
      +{
      +  query: Query
      +}
      +
      +type Query {
      +  field: Int @example
      +}
      +
      +directive @example on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +

      3.1Unspecified directives are passed through by default

      +

      Existing schemas likely contain definitions for directives which are not versioned, have no specification document, and are intended mainly to be passed through. This is the default behavior for core schema processors:

      +
      Example № 3 Unspecified directives are passed through
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +{
      +  query: Query
      +}
      +
      +type SomeType {
      +  field: Int @another
      +}
      +
      +# `@another` is unspecified. Core processors will not extract metadata from
      +# it, but its definition and all usages within the schema will be exposed
      +# in the API.
      +directive @another on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +
      +

      3.2Renaming core itself

      +

      It is possible to rename the core feature itself with the same as: mechanism used for all features:

      +
      Example № 4 Renaming @core to @coreSchema
      schema
      +  @coreSchema(feature: "https://specs.apollo.dev/core/v0.1", as: "coreSchema")
      +  @coreSchema(feature: "https://example.com/example/v1.0")
      +{
      +  query: Query
      +}
      +
      +type SomeType {
      +  field: Int @example
      +}
      +
      +directive @coreSchema(feature: String!, as: String)
      +  repeatable on SCHEMA
      +directive @example on FIELD_DEFINITION
      +
      +
      +
      +
      +

      4Directive Definitions

      +

      All core schemas use the @core directive to declare their use of the core feature itself as well as any other core features they use.

      +

      In order to use these directives in your schema, GraphQL requires you to include their definitions in your schema.

      +

      Processors MUST validate that you have defined the directives with the same arguments, locations, and repeatable flag as given below. Specifically, the bootstrapping algorithm validates that the @core directive has a definition matching the definition given below. (The bootstrapping algorithm does not require processors to validate other aspects of the directive declaration such as description strings or argument ordering. The main purpose of this validation is to ensure that directive arguments have the type and default values expected by the specification.)

      +

      The following declares the directive defined by this specification. You SHOULD define the directives in your core schema by including the following text in your schema document.

      +
      directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +

      When writing a specification for your own core feature, you SHOULD include a section like this one with sample definitions to copy into schemas, and you SHOULD require processors to validate that directive definitions in documents match your sample definitions.

      +
      +
      +

      5Directives

      +
      +

      5.1@core

      +

      Declare a core feature present in this schema.

      +
      directive @core(
      +  feature: String!,
      +  as: String,
      +  for: core__Purpose)
      +  repeatable on SCHEMA
      +
      +

      Documents MUST include a definition for the @core directive which includes all of the arguments defined above with the same types and default values.

      +
      +

      5.1.1feature: String!

      +

      A feature URL specifying the directive and associated schema elements. When viewed, the URL SHOULD provide the content of the appropriate version of the specification in some human‐readable form. In short, a human reader should be able to click the link and go to the docs for the version in use. There are specific requirements on the format of the URL, but it is not required that the content be machine‐readable in any particular way.

      +

      Feature URLs contain information about the spec’s prefix and version.

      +

      Feature URLs serve two main purposes:

      +
        +
      • Directing human readers to documentation about the feature
      • +
      • Providing tools with information about the specs in use, along with enough information to select and invoke an implementation
      • +
      +

      Feature URLs SHOULD be RFC 3986 URLs. When viewed, the URL SHOULD provide the specification of the selected version of the feature in some human‐readable form; a human reader should be able to click the link and go to the correct version of the docs.

      +

      Although they are not prohibited from doing so, it’s assumed that processors will not load the content of feature URLs. Published specifications are not required to be machine‐readable, and this spec places no requirements on the structure or syntax of the content to be found there.

      +

      There are, however, requirements on the structure of the URL itself:

      +
      Basic anatomy of a feature URL
      + https://spec.example.com/a/b/c/exampleFeature/v1.0 + +

      The final two segments of the URL’s path MUST contain the feature’s name and a version tag. The content of the URL up to and including the name—but excluding the / after the name and the version tag—is the feature’s identity. Trailing slashes at the end of the URL (ie, after the version tag) should be ignored. For the above example,

      identity: "https://spec.example.com/a/b/c/exampleFeature"
      A global identifier for the feature. Processors can treat this as an opaque string identifying the feature (but not the version of the feature) for purposes of selecting an appropriate implementation. The identity never has a trailing /.
      name: "exampleFeature"
      The feature’s name, for purposes of prefixing schema elements it defines.
      version: "v1.0"
      The tag for the version of the feature used to author the document. Processors MUST select an implementation of the feature which can satisfy the specified version.

      +

      The version tag MUST be a valid VersionTag. The name MUST be a valid GraphQL name which does not include the namespace separator ("__").

      +
      +

      5.1.1.1Ignore meaningless URL components

      +

      When extracting the URL’s name and version, processors MUST ignore any url components which are not assigned a meaning. This spec assigns meaning to the final two segments of the path. Other URL components—particularly query strings and fragments, if present—MUST be ignored for the purposes of extracting the name and version.

      +
      Ignoring meaningless parts of a URL
      + https://example.com/exampleSpec/v1.0/?key=val&k2=v2#frag + +
      +
      +

      5.1.1.2Why is versioning in the URL, not a directive argument?

      +

      The version is in the URL because when a human reader visits the URL, we would like them to be taken to the documentation for the version of the feature used by this document. Many text editors will turn URLs into hyperlinks, and it’s highly desirable that clicking the link takes the user to the correct version of the docs. Putting the version information in a separate argument to the @core directive would prevent this.

      +
      +
      +
      +

      5.1.2as: String

      +

      Change the names of directives and schema elements from this specification. The specified string MUST be a valid GraphQL name and MUST NOT contain the namespace separator (two underscores, "__") or end with an underscore.

      +

      When as: is provided, processors looking for prefixed schema elements MUST look for elements whose names are the specified name with the prefix replaced with the name provided to the as: argument.

      +
      Example № 5 Using @core(feature:, as:) to use a feature with a custom name
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://spec.example.com/example/v1.0", as: "eg")
      +{
      +  query: Query
      +}
      +
      +type User {
      +  # Specifying `as: "eg"` transforms @example into @eg
      +  name: String @eg(data: ITEM)
      +}
      +
      +# Additional specified schema elements must have their prefixes set
      +# to the new name.
      +#
      +# The spec at https://spec.example.com/example/v1.0 calls this enum
      +# `example__Data`, but because of the `as:` argument above, processors
      +# will use this `eg__Data` enum instead.
      +enum eg__Data {
      +  ITEM
      +}
      +
      +# Name transformation must also be applied to definitions pulled in from
      +# specifications.
      +directive @eg(data: eg__Data) on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +
      +

      5.1.3for: core__Purpose

      +

      An optional purpose for this feature. This hints to consumers as to whether they can safely ignore metadata from a given feature.

      +

      By default, core features SHOULD fail open. This means that an unknown feature SHOULD NOT prevent a schema from being served or processed. Instead, consumers SHOULD ignore unknown feature metadata and serve or process the rest of the schema normally.

      +

      This behavior is different for features with a specified purpose:

      +
        +
      • SECURITY features convey metadata necessary to securely resolve fields within the schema
      • +
      • EXECUTION features convey metadata necessary to correctly resolve fields within the schema
      • +
      +
      +
      +
      +
      +

      6Enums

      +
      +

      6.1core__Purpose

      +
      enum core__Purpose {
      +  SECURITY
      +  EXECUTION
      +}
      +
      +

      The role of a feature referenced with @core.

      +

      This is not intended to be an exhaustive list of all the purposes a feature might serve. Rather, it is intended to capture cases where the default fail‐open behavior of core schema consumers is undesirable.

      +
      +Note +we’ll refer to directives from features which are for: SECURITY or for: EXECUTION as “SECURITY directives” and “EXECUTION directives”, respectively.
      +
      +

      6.1.1SECURITY

      +

      SECURITY features provide metadata necessary to securely resolve fields. For instance, a hypothetical auth feature may provide an @auth directive to flag fields which require authorization. If a data core does not support the auth feature and serves those fields anyway, these fields will be accessible without authorization, compromising security.

      +

      Security‐conscious consumers MUST NOT serve a field if:

      +
        +
      • the schema definition has any unsupported SECURITY directives,
      • +
      • the field’s parent type definition has any unsupported SECURITY directives,
      • +
      • the field’s return type definition has any unsupported SECURITY directives, or
      • +
      • the field definition has any unsupported SECURITY directives
      • +
      +

      Such fields are not securely resolvable. Security‐conscious consumers MAY serve schemas with fields which are not securely resolvable. However, they MUST remove such fields from the schema before serving it.

      +

      Less security‐conscious consumers MAY choose to relax these requirements. For instance, servers may provide a development mode in which unknown SECURITY directives are ignored, perhaps with a warning. Such software may also provide a way to explicitly disable some or all SECURITY features during development.

      +

      More security‐conscious consumers MAY choose to enhance these requirements. For instance, production servers MAY adopt a policy of entirely rejecting any schema which contains ANY unsupported SECURITY features, even if those features are never used to annotate the schema.

      +
      +
      +

      6.1.2EXECUTION

      +

      EXECUTION features provide metadata necessary to correctly resolve fields. For instance, a hypothetical ts feature may provide a @ts__resolvers annotation which references a TypeScript module of field resolvers. A consumer which does not support the ts feature will be unable to correctly resolve such fields.

      +

      Consumers MUST NOT serve a field if:

      +
        +
      • the schema’s definition has any unsupported EXECUTION directives,
      • +
      • the field’s parent type definition has any unsupported EXECUTION directives,
      • +
      • the field’s return type definition has any unsupported EXECUTION directives, or
      • +
      • the field definition has any unsupported EXECUTION directives
      • +
      +

      Such fields are unresolvable. Consumers MAY attempt to serve schemas with unresolvable fields. Depending on the needs of the consumer, unresolvable fields MAY be removed from the schema prior to serving, or they MAY produce runtime errors if a query attempts to resolve them. Consumers MAY implement stricter policies, wholly refusing to serve schemas with unresolvable fields, or even refusing to serve schemas with any unsupported EXECUTION features, even if those features are never used in the schema.

      +
      +
      +
      +
      +

      7Prefixing

      +

      With the exception of a single root directive, core feature specifications MUST prefix all schema elements they introduce. The prefix:

      +
        +
      1. MUST match the name of the feature as derived from the feature’s specification URL,
      2. +
      3. MUST be a valid GraphQL name, and
      4. +
      5. MUST NOT contain the core namespace separator, which is two underscores ("__"), and
      6. +
      7. MUST NOT end with an underscore (which would create ambiguity between whether "x___y" is prefix x_ for element y or prefix x for element _y).
      8. +
      +

      Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid GraphQL name. For instance, the core specification (which you are currently reading) introduces an element named @core, and the join specification introduces an element named @join__field (among others).

      +
      +Note +that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like @feature__24hours.
      +

      A feature’s root directive is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the core specification introduces a @core directive. This directive has the same name as the feature (”core”), and so requires no prefix.

      +
      Example № 6 Using the @core directive without changing the prefix
      schema
      + @core(feature: "https://specs.apollo.dev/core/v0.1")
      + @core(feature: "https://spec.example.com/example/v1.0") {
      +  query: Query
      +}
      +
      +type User {
      +  name: String @example(data: ITEM)
      +}
      +
      +# An enum used to provide structured data to the example spec.
      +# It is prefixed with the name of the spec.
      +enum example__Data {
      +  ITEM
      +}
      +
      +directive @example(data: example__Data) on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +

      The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature’s name as a prefix.

      +
      +

      7.1Elements which must be prefixed

      +

      Feature specs MUST prefix the following schema elements:

      +
        +
      • the names of any object types, interfaces, unions, enums, or input object types defined by the feature
      • +
      • the names of any directives introduced in the spec, with the exception of the root directive, which must have the same name as the feature
      • +
      +
      Example № 7 Prefixing
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://spec.example.com/featureA/v1.0")
      +  @core(feature: "https://spec.example.com/featureB/v2.0", as: "B") {
      +  query: Query
      +}
      +
      +"""
      +featureA__SomeType is a type defined by feature A.
      +"""
      +type featureA__SomeType {
      +  """
      +  nativeField is a field defined by featureA on a type also defined
      +  by featureA (namely featureA__SomeType)
      +  """
      +  nativeField: Int @featureA__fieldDirective
      +}
      +
      +"""
      +featureA__SomeInput is an input specified by feature A
      +"""
      +input featureA__SomeInput {
      +  """
      +  nativeInputField is defined by featureA
      +  """
      +  nativeInputField: Int
      +}
      +
      +"""
      +featureA__Items is specified by feature A
      +"""
      +enum featureA__Items { ONE, TWO, THREE @B }
      +
      +"""
      +@B is the root directive defined by featureB
      +
      +Root directives are named after their feature
      +"""
      +directive @B on ENUM_VALUE
      +
      +"""
      +@featureA__fieldDirective is a non-root (prefixed) directive defined by featureA
      +"""
      +directive @featureA__fieldDirective on FIELD_DEFINITION
      +
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      +
      +
      +
      +

      8Versioning

      + + + + + + +
      +PositiveDigit
      1|2|3|4|5|6|7|8|9
      +
      +

      Specs are versioned with a subset of a Semantic Version Number containing only the major and minor parts. Thus, specifications SHOULD provide a version of the form vMajor.Minor, where both integers ≥ 0.

      +
      Example № 8 Valid version tags
      v2.2
      +v1.0
      +v1.1
      +v0.1
      +
      +

      As specified by semver, spec authors SHOULD increment the:

      +
        +
      • MAJOR version when you make incompatible API changes,
      • +
      • MINOR version when you add functionality in a backwards compatible manner
      • +
      +
      +

      Patch and pre‐release qualifiers are judged to be not particularly meaningful in the context of core features, which are (by definition) interfaces rather than implementations. The patch component of a semver denotes a bug fix which is backwards compatible—that is, a change to the implementation which does not affect the interface. Patch‐level changes in the version of a spec denote wording clarifications which do not require implementation changes. As such, it is not important to track them for the purposes of version resolution.

      +

      As with semver, the 0.x version series is special: there is no expectation of compatibility between versions 0.x and 0.y. For example, a processor must not activate implementation 0.4 to satisfy a requested version of 0.2.

      +
      +

      8.1Satisfaction

      +

      Given a version requested by a document and an available version of an implementation, the following algorithm will determine if the available version can satisfy the requested version:

      +
      +Satisfies(requested, available)
        +
      1. If requested.Majoravailable.Major, return false
      2. +
      3. If requested.Major = 0, return requested.Minor = available.Minor
      4. +
      5. Return requested.Minoravailable.Minor
      6. +
      +
      +
      +
      +

      8.2Referencing versions and activating implementations

      +

      Schema documents MUST reference a feature version which supports all the schema elements and behaviors required by the document. As a practical matter, authors will generally prefer to reference a version they have reason to believe is supported by the most processors; depending on context, this might be an old stable version with a low major version, or a new less‐deprecated version with a large major version.

      +

      If a processor chooses to activate support for a feature, the processor MUST activate an implementation which can satisfy the version required by the document.

      +
      +
      +
      +

      9Processing Schemas

      +
      graph LR + schema(["📄 Input Schema"]):::file-->proc("🤖  Processor") + proc-->output(["📄 Output Schema"]):::file + classDef file fill:none,color:#22262E; + style proc fill:none,stroke:fuchsia,color:fuchsia; +

      A common use case is that of a processor which consumes a valid input schema and generates an output schema.

      +

      The general guidance for processor behavior is: don’t react to what you don’t understand.

      +

      Specifically, processors:

      +
        +
      • SHOULD pass through @core directives which reference unknown feature URLs
      • +
      • SHOULD pass through prefixed directives, types, and other schema elements
      • +
      • SHOULD pass through directives which are not associated with a @core feature
      • +
      +

      Processors MAY accept configuration which overrides these default behaviors.

      +

      Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case‐by‐case basis.

      +
      +
      +

      10Validations & Algorithms

      +

      This section lays out algorithms for processing core schemas.

      +

      Algorithms described in this section may produce validation failures if a document does not conform to the requirements core schema document. Validation failures SHOULD halt processing. Some consumers, such as authoring tools, MAY attempt to continue processing in the presence of validation failures, but their behavior in such cases is unspecified.

      +
      +

      10.1Bootstrapping

      +

      Determine the name of the core specification within the document.

      +

      It is possible to rename the core feature within a document. This process determines the actual name for the core feature if one is present.

      +
        +
      • Fails the Has Schema validation if there are no SchemaDefinitions in the document
      • +
      • Fails the Has Core Feature validation if the core feature itself is not referenced with a @core directive within the document
      • +
      • Fails the Bootstrap Core Feature Listed First validation if the reference to the core feature is not the first @core directive on the document’s SchemaDefinition
      • +
      • Fails the Core Directive Incorrect Definition validation if the @core directive definition does not match the directive as defined by this specification.
      • +
      +

      For the purposes of this algorithm, a directive’s definition in a schema matches a definition provided in this specification if:

      +
        +
      • Its arguments have the specified names, types, and default values (or lack thereof)
      • +
      • It is defined as repeatable if and only if the specification’s definition defines it as repeatable
      • +
      • The set of locations it belongs to is the same set of locations in the specification’s definition.
      • +
      +

      The following aspects may differ between the definition in the schema and the definition in the specification without preventing the definitions from matching:

      +
        +
      • The name of the directive (due to prefixing)
      • +
      • The order of arguments
      • +
      • The order of locations
      • +
      • The directive’s description string
      • +
      • Argument description strings
      • +
      • Directives applied to argument definitions
      • +
      +
      +Bootstrap(document)
        +
      1. Let schema be the only SchemaDefinition in document. (Note that legal GraphQL documents must include at most one SchemaDefinition.)
          +
        1. ...if no SchemaDefinitions are present in document, the Has Schema validation fails.
        2. +
        +
      2. +
      3. For each directive d on schema,
          +
        1. If d has a feature: argument which parses as a feature URL, and whose identity is "https://specs.apollo.dev/core/" and whose version is "v0.1", and either d has an as: argument whose value is equal to d‘s name or d does not have an as: argument and d‘s name is core:
            +
          1. If any directive on schema listed before d has the same name as d, the Bootstrap Core Feature Listed First validation fails.
          2. +
          3. If the definition of the directive d does not match the definition of @core in this specification, the Core Directive Incorrect Definition validation fails.
          4. +
          5. Otherwise, Return d‘s name.
          6. +
          +
        2. +
        +
      4. +
      5. If no matching directive was found, the Has Core Feature validation fails.
      6. +
      +
      +
      +
      +

      10.2Feature Collection

      +

      Collect a map of (featureName: String) → Directive, where Directive is a @core Directive which introduces the feature named featureName into the document.

      +
        +
      • Fails the Name Uniqueness validation if feature names are not unique within the document.
      • +
      • Fails Invalid Feature URL validation for any invalid feature URLs.
      • +
      +
      +CollectFeatures(document)
        +
      1. Let coreName be the name of the core feature found via Bootstrap(document)
      2. +
      3. Let features be a map of featureName: StringDirective, initially empty.
      4. +
      5. For each directive d named coreName on the SchemaDefinition within document,
          +
        1. Let specifiedFeatureName and version be the result of parsing d‘s feature: argument according to the specified rules for feature URLs
        2. +
        3. If the feature: is not present or fails to parse:
            +
          1. The Invalid Feature URL validation fails for d,
          2. +
          +
        4. +
        5. Let featureName be the d‘s as: argument or, if the argument is not present, specifiedFeatureName
        6. +
        7. If featureName exists within features, the Name Uniqueness validation fails.
        8. +
        9. Insert featureNamed into features
        10. +
        +
      6. +
      7. Return features
      8. +
      +
      +

      Prefixes, whether implicit or explicit, must be unique within a document. Valid:

      +
      Example № 9 Unique prefixes
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://spec.example.com/featureA/v1.0")
      +  @core(feature: "https://spec.example.com/featureB/v2.0", as: "B") {
      +  query: Query
      +}
      +

      It is also valid to reference multiple versions of the same spec under different prefixes:

      +
      Example № 10 Explicit prefixes allow multiple versions of the same spec to coexist within a Document
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/A/1.0")               # name is A
      +  @core(feature: "https://specs.example.com/A/2.0", as: "A2")     # name is A2
      +{
      +  query: Query
      +}
      +

      Without the explicit as:, the above would be invalid:

      +
      Counter Example № 11 Non‐unique prefixes with multiple versions of the same spec
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/A/1.0") # name is A
      +  @core(feature: "https://specs.example.com/A/2.0") # name is A
      +{
      +  query: Query
      +}
      +

      Different specs with the same prefix are also invalid:

      +
      Counter Example № 12 Different specs with non‐unique prefixes
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v0.1")
      +  @core(feature: "https://specs.example.com/A/1.0")              # name is A
      +  @core(feature: "https://www.specs.com/specA/1.1", as: "A")     # name is A
      +{
      +  query: Query
      +}
      +
      +
      +

      10.3Assign Features

      +

      Create a map of element: Any Named Elementfeature: Directive | null, associating every named schema element within the document with a feature directive, or null if it is not associated with a feature.

      +
      +AssignFeatures(document)
        +
      1. Let features be the result of collecting features via CollectFeatures(document)
      2. +
      3. Let assignments be a map of (element: Any Named Element) → feature: Directive | null, initally empty
      4. +
      5. For each named schema element e within the document
          +
        1. Let name be the name of the e
        2. +
        3. If e is a Directive and name is a key within features,
            +
          1. Insert efeatures[name] into assignments
          2. +
          3. Continue to next e
          4. +
          +
        4. +
        5. If name begins with "__",
            +
          1. Insert enull into assignments
          2. +
          3. Continue to next e
          4. +
          +
        6. +
        7. If name contains the substring "__",
            +
          1. Partition name into [prefix, base] at the first "__" (that is, find the shortest prefix and longest base such that name = prefix + "__" + base)
          2. +
          3. If prefix exists within features, insert efeatures[prefix] into assignments
              +
            1. Else, insert enull into assignments
            2. +
            +
          4. +
          5. Continue to next e
          6. +
          +
        8. +
        9. Insert enull into assignments
        10. +
        +
      6. +
      7. Return assignments
      8. +
      +
      +
      +
      +

      10.4Is In API?

      +

      Determine if any schema element is included in the API described by the core schema. A schema element is any part of a GraphQL document using type system definitions that has a name.

      +
      +IsInAPI(element)
        +
      1. Let assignments be the result of assigning features to elements via AssignFeatures(document)
      2. +
      3. If assignments[element] is null, Return true
      4. +
      5. Else, Return false
      6. +
      +
      +
      +Note +Later versions of this specification may add other ways to affect the behavior of this algorithm, but those mechanisms will only be enabled if you reference those hypothetical versions of this specification.
      +
      +
      +

      10.5Is Affected By Feature?

      +

      Determine if a schema element is affected by a given feature.

      +
      +IsAffected(element, feature)
        +
      1. Let assignments be the result of assigning features to elements via AssignFeatures(document)
      2. +
      3. For each directive d on element, If assignments[d] is feature, Return true
      4. +
      5. If element is a FieldDefinition,
          +
        1. Let parent be the parent ObjectDefinition or InterfaceDefinition for element
        2. +
        3. If IsAffected(parent, feature), Return true
        4. +
        5. For each argument type a declared on element,
            +
          1. Let t be the InputDefinition, EnumDefinition, or ScalarDefinition for argument a
          2. +
          3. If IsAffected(t, feature), Return true
          4. +
          +
        6. +
        7. Let return be the ObjectDefinition, InterfaceDefinition, or UnionDefinition for element‘s return type
        8. +
        9. If IsAffected(return, feature), Return true
        10. +
        +
      6. +
      7. If element is an InputDefinition,
          +
        1. For each InputFieldDefinition field within element,
            +
          1. Let t be the InputDefinition, EnumDefinition, or ScalarDefinition for the type of field
          2. +
          3. If IsAffected(t, feature), Return true
          4. +
          +
        2. +
        +
      8. +
      9. If element is an EnumDefinition,
          +
        1. For each EnumValueDefinition value in element,
            +
          1. If IsAffected(value, feature), Return true
          2. +
          +
        2. +
        +
      10. +
      +
      +
      +
      +

      §Index

      1. AssignFeatures
      2. Bootstrap
      3. CollectFeatures
      4. Digit
      5. IsAffected
      6. IsInAPI
      7. Major
      8. Minor
      9. NumericIdentifier
      10. PositiveDigit
      11. Satisfies
      12. Version
      13. VersionTag
      + + +
      + + +
      + + + diff --git a/core/v0.2/netlify.toml b/core/v0.2/netlify.toml new file mode 100644 index 0000000..f1a19b0 --- /dev/null +++ b/core/v0.2/netlify.toml @@ -0,0 +1,6 @@ +# Netlify Admin: https://app.netlify.com/sites/apollo-specs-core/ +# Docs: https://docs.netlify.com/configure-builds/file-based-configuration/ + +[build] + command = "npm run build" + publish = ".dist/" diff --git a/core/v0.2/package.json b/core/v0.2/package.json new file mode 100644 index 0000000..63529c1 --- /dev/null +++ b/core/v0.2/package.json @@ -0,0 +1,20 @@ +{ + "name": "specs-site", + "private": true, + "version": "1.0.0", + "description": "for GraphQL schemas with extensible metadata", + "repository": "https://github.com/apollo-specs/core", + "main": "index.js", + "scripts": { + "build": "rsync -avz --exclude .dist . .dist && spec-md core.spec.md > .dist/index.html", + "dev": "npm run build && chokidar '**/*' -i '.dist' -c 'npm run build'" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@queerviolet/speck": "git://github.com/queerviolet/speck.git#main", + "chokidar-cli": "^2.1.0", + "watch": "^1.0.2" + } +} diff --git a/core/v0.2/prefix-uniqueness.graphql b/core/v0.2/prefix-uniqueness.graphql new file mode 100644 index 0000000..a26a309 --- /dev/null +++ b/core/v0.2/prefix-uniqueness.graphql @@ -0,0 +1,25 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0", as: "A2") # name is A2 +{ + query: Query +} + +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://specs.example.com/A/2.0") # name is A +{ + query: Query +} + +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://specs.example.com/A/1.0") # name is A + @core(feature: "https://www.specs.com/specA/1.1", as: "A") # name is A +{ + query: Query +} + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/core/v0.2/prefixing.graphql b/core/v0.2/prefixing.graphql new file mode 100644 index 0000000..12c5da7 --- /dev/null +++ b/core/v0.2/prefixing.graphql @@ -0,0 +1,46 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v0.1") + @core(feature: "https://spec.example.com/featureA/v1.0") + @core(feature: "https://spec.example.com/featureB/v2.0", as: "B") { + query: Query +} + +""" +featureA__SomeType is a type defined by feature A. +""" +type featureA__SomeType { + """ + nativeField is a field defined by featureA on a type also defined + by featureA (namely featureA__SomeType) + """ + nativeField: Int @featureA__fieldDirective +} + +""" +featureA__SomeInput is an input specified by feature A +""" +input featureA__SomeInput { + """ + nativeInputField is defined by featureA + """ + nativeInputField: Int +} + +""" +featureA__Items is specified by feature A +""" +enum featureA__Items { ONE, TWO, THREE @B } + +""" +@B is the root directive defined by featureB + +Root directives are named after their feature +""" +directive @B on ENUM_VALUE + +""" +@featureA__fieldDirective is a non-root (prefixed) directive defined by featureA +""" +directive @featureA__fieldDirective on FIELD_DEFINITION + +directive @core(feature: String!, as: String) repeatable on SCHEMA diff --git a/index.html b/index.html new file mode 100644 index 0000000..d9f81c0 --- /dev/null +++ b/index.html @@ -0,0 +1,1087 @@ + + + + +Apollo Library of Technical Specifications + + + + + +
      +
      +

      Apollo Library of Technical Specifications

      +
      +
      +
      + +
      +
      + + +
      + +
      +
      +

      1Current Specs

      +
        +
      • link v1.0 provides the foundation of core schemas—GraphQL schemas for the global graph. Core schemas use specified conventions to link definitions from other schemas. All the other specs in this library rely on core schema conventions.
      • +
      • join v0.1 describes supergraphs which join types from one or more subgraphs
      • +
      • tag v0.1 attaches a single piece of string metadata to various GraphQL definitions (it is mainly used for contracts)
      • +
      • inaccessible v0.1 masks fields and types from a graph’s public API
      • +
      +
      +
      + + +
      + + +
      + + + diff --git a/index.md b/index.md new file mode 100644 index 0000000..cf832e0 --- /dev/null +++ b/index.md @@ -0,0 +1,20 @@ +# Apollo Library of Technical Specifications + +```raw html +
      +
      + +
      +
      + + +``` + +# Current Specs + +- [link v1.0](link/v1.0) provides the foundation of core schemas—GraphQL schemas for the global graph. Core schemas use [specified conventions](link/v1.0) to link definitions from other schemas. All the other specs in this library rely on core schema conventions. +- [join v0.1](join/v0.1) describes supergraphs which join types from one or more subgraphs +- [tag v0.1](tag/v0.1) attaches a single piece of string metadata to various GraphQL definitions (it is mainly used for contracts) +- [inaccessible v0.1](inaccessible/v0.1) masks fields and types from a graph's public API + + diff --git a/join/v0.1/albums.graphql b/join/v0.1/albums.graphql new file mode 100644 index 0000000..e9ff385 --- /dev/null +++ b/join/v0.1/albums.graphql @@ -0,0 +1,14 @@ +type Album @key(fields: "id") { + id: ID! + user: User + photos: [Image!] +} + +extend type Image { + albums: [Album!] +} + +extend type User { + albums: [Album!] + favorite: Album +} \ No newline at end of file diff --git a/join/v0.1/auth.graphql b/join/v0.1/auth.graphql new file mode 100644 index 0000000..7a85d3a --- /dev/null +++ b/join/v0.1/auth.graphql @@ -0,0 +1,8 @@ +type User @key(fields: "id") { + id: ID! + name: String +} + +type Query { + me: User +} \ No newline at end of file diff --git a/join/v0.1/images.graphql b/join/v0.1/images.graphql new file mode 100644 index 0000000..741ec0f --- /dev/null +++ b/join/v0.1/images.graphql @@ -0,0 +1,15 @@ +type Image @key(fields: "url") { + url: Url + type: MimeType +} + +type Query { + images: [Image] +} + +extend type User { + favorite: Image +} + +scalar Url +scalar MimeType \ No newline at end of file diff --git a/join/v0.1/index.html b/join/v0.1/index.html new file mode 100644 index 0000000..478720c --- /dev/null +++ b/join/v0.1/index.html @@ -0,0 +1,1674 @@ + + + + +Join + + + + + +
      +
      +

      Join

      +
      +

      for defining supergraphs which join multiple subgraphs

      + + + +
      StatusRelease
      Version0.1
      + + +
      Schema joining multiple subgraphs
      graph LR + classDef bg fill:none,color:#22262E; + s1(auth.graphql):::bg-->core(composed schema: photos.graphql) + s2(images.graphql):::bg-->core + s3(albums.graphql):::bg-->core + style core fill:none,stroke:fuchsia,color:fuchsia; +

      This document defines a core schema named join for describing core schemas which join multiple subgraph schemas into a single supergraph schema.

      +

      This specification provides machinery to:

      +
        +
      • define subgraphs with the join__Graph enum and the @join__graph directive
      • +
      • assign fields to subgraphs with the @join__field directive
      • +
      • declare additional data required and provided by subgraph field resolvers with the requires and provides arguments to @join__field
      • +
      • assign keys and ownership to types with the @join__type and @join__owner directives
      • +
      +
      + +
      +
      +

      1How to read this document

      +

      This document uses RFC 2119 guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL.

      +
      +

      1.1What this document isn't

      +

      This document specifies only the structure and semantics of supergraphs. It’s expected that a supergraph will generally be the output of a compilation process which composes subgraphs. The mechanics of that process are not specified normatively here. Conforming implementations may choose any approach they like, so long as the result conforms to the requirements of this document.

      +
      +
      +
      +

      2Example: Photo Library

      +

      This section is non‐normative.

      +

      We’ll refer to this example of a photo library throughout the document:

      +
      Example № 1 Photos library composed schema
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v1.0")
      +  @core(feature: "https://specs.apollo.dev/join/v1.0") {
      +  query: Query
      +}
      +
      +directive @core(feature: String!) repeatable on SCHEMA
      +
      +directive @join__owner(graph: join__Graph!) on OBJECT
      +
      +directive @join__type(
      +  graph: join__Graph!
      +  key: String!
      +) repeatable on OBJECT | INTERFACE
      +
      +directive @join__field(
      +  graph: join__Graph
      +  requires: String
      +  provides: String
      +) on FIELD_DEFINITION
      +
      +directive @join__graph(name: String!, url: String!) on ENUM_VALUE
      +
      +enum join__Graph {
      +  AUTH @join__graph(name: "auth", url: "https://auth.api.com")
      +  ALBUMS @join__graph(name: "albums", url: "https://albums.api.com")
      +  IMAGES @join__graph(name: "images", url: "https://images.api.com")
      +}
      +
      +type Query {
      +  me: User @join__field(graph: AUTH)
      +  images: [Image] @join__field(graph: IMAGES)
      +}
      +
      +type User
      +    @join__owner(graph: AUTH)
      +    @join__type(graph: AUTH, key: "id")
      +    @join__type(graph: ALBUMS, key: "id") {
      +  id: ID! @join__field(graph: AUTH)
      +  name: String @join__field(graph: AUTH)
      +  albums: [Album!] @join__field(graph: ALBUMS)
      +}
      +
      +type Album
      +    @join__owner(graph: ALBUMS)
      +    @join__type(graph: ALBUMS, key: "id") {
      +  id: ID!
      +  user: User
      +  photos: [Image!]
      +}
      +
      +type Image
      +    @join__owner(graph: IMAGES)
      +    @join__type(graph: ALBUMS, key: "url")
      +    @join__type(graph: IMAGES, key: "url") {
      +  url: Url @join__field(graph: IMAGES)
      +  type: MimeType @join__field(graph: IMAGES)
      +  albums: [Album!] @join__field(graph: ALBUMS)
      +}
      +
      +scalar Url
      +scalar MimeType
      +
      +

      The meaning of the @join__* directives is explored in the Directives section.

      +

      The example represents one way to compose three input schemas, based on federated composition. These schemas are provided for purposes of illustration only. This spec places no normative requirements on composer input. It does not require that subgraphs use federated composition directives, and it does not place any requirements on how the composer builds a supergraph, except to say that the resulting schema must be a valid supergraph document.

      +

      The auth subgraph provides the User type and Query.me.

      +
      Example № 2 Auth schema
      type User @key(fields: "id") {
      +  id: ID!
      +  name: String
      +}
      +
      +type Query {
      +  me: User
      +}
      +

      The images subgraph provides the Image type and URL scalar.

      +
      Example № 3 Images schema
      type Image @key(fields: "url") {
      +  url: Url
      +  type: MimeType
      +}
      +
      +type Query {
      +  images: [Image]
      +}
      +
      +extend type User {
      +  favorite: Image
      +}
      +
      +scalar Url
      +scalar MimeType
      +

      The albums subgraph provides the Album type and extends User and Image with album information.

      +
      Example № 4 Albums schema
      type Album @key(fields: "id") {
      +  id: ID!
      +  user: User
      +  photos: [Image!]
      +}
      +
      +extend type Image {
      +  albums: [Album!]
      +}
      +
      +extend type User {
      +  albums: [Album!]
      +  favorite: Album
      +}
      +
      +
      +

      3Actors

      +
      Actors and roles within an example composition pipeline
      flowchart TB + classDef bg fill:#EBE6FF; + subgraph A [subgraph A] + schemaA([schema A]):::bg + style schemaA color:#000 + endpointA([endpoint A]):::bg + style endpointA color:#000 + end + style A fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + subgraph B [subgraph B] + schemaB([schema B]):::bg + style schemaB color:#000 + endpointB([endpoint B]):::bg + style endpointB color:#000 + end + style B fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + subgraph C [subgraph C] + schemaC([schema C]):::bg + style schemaC color:#000 + endpointC([endpoint C]):::bg + style endpointC color:#000 + end + style C fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + subgraph producer["Producer ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"] + Composer + style Composer color:#000 + end + style producer fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + supergraph([Supergraph]):::bg + style supergraph color:#000 + subgraph consumer["Consumer ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"] + Router + style Router color:#000 + end + style consumer fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + A-->Composer:::bg + B-->Composer:::bg + C-->Composer:::bg + Composer-->supergraphSchema([Supergraph Schema]):::bg + style supergraphSchema color:#000 + supergraphSchema-->Router:::bg + Router-->published([Published Schema]):::bg + style published color:#000 + published-->Clients:::bg + style Clients color:#000 + Clients-->Router:::bg +

      Producers generate supergraphs. This spec places requirements on supergraph producers.

      +

      Consumers consume supergraphs. This spec places requirements on supergraph consumers.

      +

      Composers (or compilers) are producers which compose subgraphs into a supergraph. This document places no particular requirements on the composition algorithm, except that it must produce a valid supergraph.

      +

      Routers are consumers which serve a composed schema as a GraphQL endpoint. This definition is non‐normative.

      +
        +
      • Graph routers differ from standard GraphQL endpoints in that they are not expected to resolve fields or communicate with (non‐GraphQL) backend services on their own. Instead, graph routers receive GraphQL requests and service them by performing additional GraphQL requests. This spec provides guidance for implementing routers, but does not require particular implementations of query separation or dispatch, nor does it attempt to normatively separate routers from other supergraph consumers.
      • +
      • Routers expose an API schema to clients that is created by transforming the supergraph schema (for example, the join__Graph enum and the directives described in this spec are removed from the API schema). The API schema is used to validate client operations and may be exposed to clients via introspection.
      • +
      +

      Endpoints are running servers which can resolve GraphQL queries against a schema. In this version of the spec, endpoints must be URLs, typically http/https URLs.

      +

      Subgraphs are GraphQL schemas which are composed to form a supergraph. Subgraph names and metadata are declared within the special join__Graph enum.

      +

      This spec does not place any requirements on subgraph schemas. Generally, they may be of any shape. In particular, subgraph schemas do not need to be supergraphs themselves or to follow this spec in any way; neither is it an error for them to do so. Composers MAY place additional requirements on subgraph schemas to aid in composition; composers SHOULD document any such requirements.

      +
      +
      +

      4Overview

      +

      This section is non‐normative. It describes the motivation behind the directives defined by this specification.

      +

      A supergraph schema describes a GraphQL schema that can be served by a router. The router does not contain logic to resolve any of the schema’s fields; instead, the supergraph schema contains directives starting with @join__ that tell the router which subgraph endpoint can resolve each field, as well as other information needed in order to construct subgraph operations.

      +

      The directives described in this specification are designed for a particular query planning algorithm, and so there are some restrictions on how they can be combined that originate from the requirements of this algorithm. For example, this specification describes a concept of type ownership which exists not because we believe it describes the ideal method of structuring your subgraphs, but because this query planning algorithm depends on type ownership. We hope that future versions of this specification can relax some of these restrictions.

      +

      Each supergraph schema contains a list of its included subgraphs. The join__Graph enum represents this list with an enum value for each subgraph. Each enum value is annotated with a @join__graph directive telling the router what endpoint can be used to reach the subgraph, and giving the subgraph a human‐readable name that can be used for purposes such as query plan visualization and server logs.

      +

      To resolve a field, the router needs to know to which subgraphs it can delegate the field’s resolution. One explicit way to indicate this in a supergraph schema is by annotating the field with a @join__field directive specifying which subgraph should be used to resolve that field. (There are other ways of indicating which subgraphs can resolve a field which will be described later.)

      +

      In order for the router to send an operation that resolves a given field on a parent object to a subgraph, the operation needs to first resolve the parent object itself. There are several ways to accomplish this, described below. The examples below include abbreviated versions of the supergraph schemas which do not include the schema definition, directive definitions, or the join__Graph definition. This specification does not require the subgraph operations to be the same as those described in these examples; this is just intended to broadly describe the meanings of the directives.

      +
      +

      4.1Root fields

      +

      If a field appears at the root of the overall operation (query or mutation), then it can be placed at the root of the subgraph operation.

      +
      Example № 5 Root fields
      # Supergraph schema
      +type Query {
      +  fieldA: String @join__field(graph: A)
      +  fieldAlsoFromA: String @join__field(graph: A)
      +  fieldB: String @join__field(graph: B)
      +}
      +
      +# Operation
      +{ fieldA fieldAlsoFromA fieldB }
      +# Generated subgraph operations
      +## On A:
      +{ fieldA fieldAlsoFromA }
      +## On B:
      +{ fieldB }
      +
      +
      +
      +

      4.2Fields on the same subgraph as the parent operation

      +

      If a field’s parent field will be resolved by an operation on the same subgraph, then it can be resolved as part of the same operation, by putting it in a nested selection set on the parent field’s subgraph operation. Note that this example contains @join__owner and @join__type directives on an object type; these will be described later.

      +
      Example № 6 Fields on the same subgraph as the parent operation
      # Supergraph schema
      +type Query {
      +  fieldA: X @join__field(graph: A)
      +}
      +
      +type X @join__owner(graph: A) @join__type(graph: A, key: "nestedFieldA") {
      +  nestedFieldA: String @join__field(graph: A)
      +}
      +
      +# Operation
      +{ fieldA { nestedFieldA } }
      +# Generated subgraph operations
      +## On A:
      +{ fieldA { nestedFieldA }}
      +
      +
      +
      +

      4.3Fields provided by the parent field

      +

      Sometimes, a subgraph G may be capable of resolving a field that is ordinarily resolved in a different subgraph if the field’s parent object was resolved in G. Consider an example where the Product.priceCents: Int! field is usually resolved by the Products subgraph, which knows the priceCents for every Product in your system. In the Marketing subgraph, there is a Query.todaysPromotion: Product! field. While the Marketing subgraph cannot determine the priceCents of every product in your system, it does know the priceCents of the promoted products, and so the Marketing subgraph can resolve operations like { todaysPromotion { priceCents } }.

      +

      When this is the case, you can include a provides argument in the @join__field listing these “pre‐calculated” fields. The router can now resolve these fields in the “providing” subgraph instead of in the subgraph that would usually be used to resolve those fields.

      +
      Example № 7 Provided fields
      # Supergraph schema
      +type Query {
      +  todaysPromotion: Product! @join__field(graph: MARKETING, provides: "priceCents")
      +  randomProduct: Product! @join__field(graph: PRODUCTS)
      +}
      +
      +type Product @join__owner(graph: PRODUCTS) @join__type(graph: PRODUCTS, key: "id") {
      +  id: ID! @join__field(graph: PRODUCTS)
      +  priceCents: Int! @join__field(graph: PRODUCTS)
      +}
      +
      +# Operation showing that `priceCents` is typically resolved on PRODUCTS
      +{ randomProduct { priceCents } }
      +# Generated subgraph operations
      +## On PRODUCTS
      +{ randomProduct { priceCents } }
      +
      +# Operation showing that `provides` allows `priceCents` to be resolved on MARKETING
      +{ todaysPromotion { priceCents } }
      +# Generated subgraph operations
      +## On MARKETING
      +{ todaysPromotion { priceCents } }
      +
      +
      +
      +

      4.4Fields on value types

      +

      Some types have the property that all of their fields can be resolved by any subgraph that can resolve a field returning that type. These types are called value types. (Imagine a type type T { x: Int, y: String } where every resolver for a field of type T actually produces an object like {x: 1, y: "z"}, and the resolvers for the two fields on T just unpack the values already in the object.) In a supergraph schema, a type is a value type if it does not have a @join__owner directive on it.

      +
      Example № 8 Value types
      # Supergraph schema
      +type Query {
      +  fieldA: X @join__field(graph: A)
      +  fieldB: X @join__field(graph: B)
      +}
      +
      +type X {
      +  anywhere: String
      +}
      +
      +# Operation
      +{ fieldA { anywhere } }
      +# Generated subgraph operations
      +## On A
      +{ fieldA { anywhere } }
      +
      +# Operation
      +{ fieldB { anywhere } }
      +# Generated subgraph operations
      +## On B
      +{ fieldB { anywhere } }
      +
      +
      +
      +

      4.5Owned fields on owned types

      +

      We’ve finally reached the most interesting case: a field that must be resolved by an operation on a different subgraph from the subgraph on which its parent field was resolved. In order to do this, we need a way to tell the subgraph to resolve that parent object. We do this by defining a special root field in the subgraph’s schema: Query._entities(representations: [_Any!]!): [_Entity]!. This field takes a list of “representations” and returns a list of the same length of the corresponding objects resulting from looking up the representations in an application‐dependent way.

      +

      What is a representation? A representation is expressed as the scalar type _Any, and can be any JSON object with a top‐level __typename key with a string value. Often, a representation will be something like {__typename: "User", id: "abcdef"}: the type name plus one or more fields that you can use to look up the object in a database.

      +

      There are several ways that the router can calculate a representation to pass to a subgraph. In this specification, all non‐value types have a specific subgraph referred to as its “owner”, specified via a @join__owner directive on the type. Object types that are not value types are referred to as “entities”; the type _Entity referenced above is a union defined in each subgraph’s schema consisting of the entity types defined by that subgraph. (Only subgraphs which define entities need to define the Query._entities field.) Entity types must also have at least one @join__type directive specifying the owning subgraph along with a key. For each additional subgraph which can resolve fields returning that type, there should be exactly one @join__type directive specifying that subgraph along with a key, which should be identical to one of the keys specified with the owning subgraph.

      +

      A key is a set of fields on the type (potentially including sub‐selections and inline fragments), specified as a string. If a type T is annotated with @join__type(subgraph: G, key: "a b { c }"), then it must be possible to resolve the full field set provided as a key on subgraph G. Additionally, if you take an object with the structure returned by resolving that field set and add a field __typename: "T", then you should be able to pass the resulting value as a representation to the Query._entities field on subgraph G.

      +

      In order to resolve a field on an entity on the subgraph that owns its parent type, where that subgraph is different from the subgraph that resolved its parent object, the router first resolves a key for that object on the previous subgraph, and then uses that representation on the owning subgraph.

      +

      For convenience, you may omit @join__field(graph: A) directives on fields whose parent type is owned by A.

      +
      Example № 9 Owned fields on owned types
      # Supergraph schema
      +type Query {
      +  fieldB: X @join__field(graph: B)
      +}
      +
      +type X
      +  @join__owner(graph: A)
      +  # As the owner, A is allowed to have more than one key.
      +  @join__type(graph: A, key: "x")
      +  @join__type(graph: A, key: "y z")
      +  # As non-owners, B and C can only have one key each and
      +  # they must match a key from A.
      +  @join__type(graph: B, key: "x")
      +  @join__type(graph: C, key: "y z")
      +{
      +  # Because A owns X, we can omit @join__field(graph: A)
      +  # from these three fields.
      +  x: String
      +  y: String
      +  z: String
      +}
      +
      +# Operation
      +{ fieldB { y } }
      +# Generated subgraph operations
      +## On B. `y` is not available, so we need to fetch B's key for X.
      +{ fieldB { x } }
      +## On A
      +## $r = [{__typename: "X", x: "some-x-value"}]
      +query ($r: [_Any!]!) { _entities(representations: $r]) { y } }
      +
      +
      +
      +

      4.6Extension fields on owned types

      +

      The previous section described how to jump from one subgraph to another in order to resolve a field on the subgraph that owns the field’s parent type. The situation is a bit more complicated when you want to resolve a field on a subgraph that doesn’t own the field’s parent type — what we call an extension field. That’s because we no longer have the guarantee that the subgraph you’re coming from and the subgraph you’re going to share a key in common. In this case, we may need to pass through the owning type.

      +
      Example № 10 Extension fields on owned types
      # Supergraph schema
      +type Query {
      +  fieldB: X @join__field(graph: B)
      +}
      +
      +type X
      +  @join__owner(graph: A)
      +  # As the owner, A is allowed to have more than one key.
      +  @join__type(graph: A, key: "x")
      +  @join__type(graph: A, key: "y z")
      +  # As non-owners, B and C can only have one key each and
      +  # they must match a key from A.
      +  @join__type(graph: B, key: "x")
      +  @join__type(graph: C, key: "y z")
      +{
      +  x: String
      +  y: String
      +  z: String
      +  c: String @join__field(graph: C)
      +}
      +
      +# Operation
      +{ fieldB { c } }
      +# Generated subgraph operations
      +## On B. `c` is not available on B, so we need to eventually get over to C.
      +## In order to do that, we need `y` and `z`... which aren't available on B
      +## either! So we need to take two steps. First we use B's key.
      +{ fieldB { x } }
      +## On A. We use B's key to resolve our `X`, and we extract C's key.
      +## $r = [{__typename: "X", x: "some-x-value"}]
      +query ($r: [_Any!]!) { _entities(representations: $r]) { y z } }
      +## On C. We can finally look up the field we need.
      +## $r = [{__typename: "X", y: "some-y-value", z: "some-z-value"}]
      +query ($r: [_Any!]!) { _entities(representations: $r]) { c } }
      +
      +

      We only need to do this two‐jump process because the fields needed for C’s key are not available in B; otherwise a single jump would have worked, like in the owned‐field case.

      +

      Sometimes a particular extension field needs its parent object’s representation to contain more information than its parent type’s key requests. In this case, you can include a requires argument in the field’s @join__field listing those required fields (potentially including sub‐selections). All required fields must be resolvable in the owning subgraph (this restriction is why requires is only allowed on extension fields).

      +
      Example № 11 Required fields
      # Supergraph schema
      +type Query {
      +  fieldA: X @join__field(graph: A)
      +}
      +
      +type X
      +  @join__owner(graph: A)
      +  @join__type(graph: A, key: "x")
      +  @join__type(graph: B, key: "x")
      +{
      +  x: String
      +  y: String
      +  z: String @join__field(graph: B, requires: "y")
      +}
      +
      +# Operation
      +{ fieldA { z } }
      +# Generated subgraph operations
      +## On A. `x` is included because it is B's key for `X`; `y`
      +## is included because of the `requires`.
      +{ fieldA { x y } }
      +## On B..
      +## $r = [{__typename: "X", x: "some-x-value", y: "some-y-value"}]
      +query ($r: [_Any!]!) { _entities(representations: $r]) { z } }
      +
      +
      +
      +
      +

      5Basic Requirements

      +

      Schemas using the join core feature MUST be valid core schema documents with @core directives referencing the core specification and this specification.

      +
      Example № 12 @core directives for supergraphs
      schema
      +  @core(feature: "https://specs.apollo.dev/core/v1.0")
      +  @core(feature: "https://specs.apollo.dev/join/v1.0") {
      +  query: Query
      +}
      +

      As described in the core schema specification, your schema may use a prefix other than join for all of the directive and enum names defined by this specification by including an as argument to the @core directive which references this specification. All references to directive and enum names in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema.

      +

      In order to use the directives described by this specification, GraphQL requires you to include their definitions in your schema.

      +

      Processors MUST validate that you have defined the directives with the same arguments, locations, and repeatable flag as given below.

      +
      directive @join__owner(graph: join__Graph!) on OBJECT
      +
      +directive @join__type(
      +  graph: join__Graph!,
      +  key: String!,
      +) repeatable on OBJECT | INTERFACE
      +
      +directive @join__field(
      +  graph: join__Graph,
      +  requires: String,
      +  provides: String,
      +) on FIELD_DEFINITION
      +
      +directive @join__graph(name: String!, url: String!) on ENUM_VALUE
      +
      +

      Processors MUST validate that the schema contains an enum named join__Graph; see its section below for other required properties of this enum.

      +

      As described in the core specification, all of the directives and enums defined by this schema should be removed from the supergraph’s API schema. For example, the join__Graph enum should not be visible via introspection.

      +
      +
      +

      6Enums

      +
      +

      6.1join__Graph

      +

      Enumerate subgraphs.

      +
      enum join__Graph
      +
      +

      Documents MUST define a join__Graph enum. Each enum value describes a subgraph. Each enum value MUST have a @join__graph directive applied to it.

      +
      Example № 13 Using join__Graph to define subgraphs and their endpoints
      enum join__Graph {
      +  AUTH @join__graph(name: "auth", url: "https://auth.api.com")
      +  ALBUMS @join__graph(name: "albums", url: "https://albums.api.com")
      +  IMAGES @join__graph(name: "images", url: "https://images.api.com")
      +}
      +

      The join__Graph enum is used as input to the @join__owner, @join__field, and @join__type directives.

      +
      +
      +
      +

      7Directives

      +
      +

      7.1@join__graph

      +

      Declare subgraph metadata on join__Graph enum values.

      +
      directive @join__graph(name: String!, url: String!) on ENUM_VALUE
      +
      +
      Example № 14 Using @join__graph to declare subgraph metadata on the join__Graph enum values.
      enum join__Graph {
      +  AUTH @join__graph(name: "auth", url: "https://auth.api.com")
      +  ALBUMS @join__graph(name: "albums", url: "https://albums.api.com")
      +  IMAGES @join__graph(name: "images", url: "https://images.api.com")
      +}
      +

      The @join__graph directive MUST be applied to each enum value on join__Graph, and nowhere else. Each application of @join__graph MUST have a distinct value for the name argument; this name is an arbitrary non‐empty string that can be used as a human‐readable identifier which may be used for purposes such as query plan visualization and server logs. The url argument is an endpoint that can resolve GraphQL queries for the subgraph.

      +
      +
      +

      7.2@join__type

      +

      Declares an entity key for a type on a subgraph.

      +
      directive @join__type(
      +  graph: join__Graph!
      +  key: String!
      +) repeatable on OBJECT | INTERFACE
      +
      +

      When this directive is placed on a type T, it means that subgraph graph MUST be able to:

      +
        +
      • Resolve selections on objects of the given type that contain the field set in key
      • +
      • Use Query._entities to resolve representations of objects containing __typename: "T" and the fields from the field set in key
      • +
      +
      Example № 15 Using @join__type to specify subgraph keys
      type Image
      +    @join__owner(graph: IMAGES)
      +    @join__type(graph: ALBUMS, key: "url")
      +    @join__type(graph: IMAGES, key: "url") {
      +  url: Url @join__field(graph: IMAGES)
      +  type: MimeType @join__field(graph: IMAGES)
      +  albums: [Album!] @join__field(graph: ALBUMS)
      +}
      +

      Every type with a @join__type MUST also have a @join__owner directive. Any type with a @join__owner directive MUST have at least one @join__type directive with the same graph as the @join__owner directive (the “owning graph”), and MUST have at most one @join__type directive for each graph value other than the owning graph. Any value that appears as a key in a @join__type directive with a graph value other than the owning graph must also appear as a key in a @join__type directive with graph equal to the owning graph.

      +
      +
      +

      7.3@join__field

      +

      Specify the graph that can resolve the field.

      +
      directive @join__field(
      +  graph: join__Graph
      +  requires: String
      +  provides: String
      +) on FIELD_DEFINITION
      +
      +

      The field’s parent type MUST be annotated with a @join__type with the same value of graph as this directive, unless the parent type is a root operation type.

      +

      If a field is not annotated with @join__field (or if the graph argument is not provided or null) and its parent type is annotated with @join__owner(graph: G), then a processor MUST treat the field as if it is annotated with @join__field(graph: G). If a field is not annotated with @join__field (or if the graph argument is not provided or null) and its parent type is not annotated with @join__owner (ie, the parent type is a value type) then it MUST be resolvable in any subgraph that can resolve values of its parent type.

      +
      Example № 16 Using @join__field to join fields to subgraphs
      type User
      +    @join__owner(graph: AUTH)
      +    @join__type(graph: AUTH, key: "id")
      +    @join__type(graph: ALBUMS, key: "id") {
      +  id: ID! @join__field(graph: AUTH)
      +  name: String @join__field(graph: AUTH)
      +  albums: [Album!] @join__field(graph: ALBUMS)
      +}
      +
      +type Album
      +    @join__owner(graph: ALBUMS)
      +    @join__type(graph: ALBUMS, key: "id") {
      +  id: ID!
      +  user: User
      +  photos: [Image!]
      +}
      +
      +type Image
      +    @join__owner(graph: IMAGES)
      +    @join__type(graph: ALBUMS, key: "url")
      +    @join__type(graph: IMAGES, key: "url") {
      +  url: Url @join__field(graph: IMAGES)
      +  type: MimeType @join__field(graph: IMAGES)
      +  albums: [Album!] @join__field(graph: ALBUMS)
      +}
      +

      Every field on a root operation type MUST be annotated with @join__field.

      +
      Example № 17 @join__field on root fields
      type Query {
      +  me: User @join__field(graph: AUTH)
      +  images: [Image] @join__field(graph: IMAGES)
      +}
      +

      The requires argument MUST only be specified on fields whose parent type has a @join__owner directive specifying a different graph than this @join__field directive does. All fields (including nested fields) mentioned in this field set must be resolvable in the parent type’s owning subgraph. When constructing a representation for a parent object of this field, a router will include the fields selected in this requires argument in addition to the appropriate key for the parent type.

      +

      The provides argument specifies fields that can be resolved in operations run on subgraph graph as a nested selection under this field, even if they ordinarily can only be resolved on other subgraphs.

      +
      +
      +

      7.4@join__owner

      +

      Specify the graph which owns the object type.

      +
      directive @join__owner(graph: join__Graph!) on OBJECT
      +
      +

      The descriptions of @join__type and @join__field describes requirements on how @join__owner relates to @join__type and the requires argument to @join__field.

      +
      +Note +Type ownership is currently slated for removal in a future version of this spec. It is RECOMMENDED that router implementations consider approaches which function in the absence of these restrictions. The overview explains how the current router’s query planning algorithm depends on concept of type ownership.
      +
      +
      +
      + + +
      + + +
      + + + diff --git a/join/v0.1/join-v0.1.md b/join/v0.1/join-v0.1.md new file mode 100644 index 0000000..6b1c7b8 --- /dev/null +++ b/join/v0.1/join-v0.1.md @@ -0,0 +1,469 @@ +# Join + +

      for defining *supergraphs* which join multiple *subgraphs*

      + +```raw html + + + +
      StatusRelease
      Version0.1
      + + +``` + +```mermaid diagram -- Schema joining multiple subgraphs +graph LR + classDef bg fill:none,color:#22262E; + s1(auth.graphql):::bg-->core(composed schema: photos.graphql) + s2(images.graphql):::bg-->core + s3(albums.graphql):::bg-->core + style core fill:none,stroke:fuchsia,color:fuchsia; +``` + +This document defines a [core schema](https://specs.apollo.dev/core/v0.1) named `join` for describing [core schemas](https://specs.apollo.dev/core/v0.1) which **join** multiple **subgraph** schemas into a single **supergraph** schema. + +This specification provides machinery to: +- define [subgraphs](#def-subgraph) with the {join__Graph} enum and the {@join__graph} directive +- assign fields to subgraphs with the {@join__field} directive +- declare additional data required and provided by subgraph field resolvers with the `requires` and `provides` arguments to {@join__field} +- assign [keys and ownership](#sec-Owned-fields-on-owned-types) to types with the {@join__type} and {@join__owner} directives + +# How to read this document + +This document uses [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) guidance regarding normative terms: MUST / MUST NOT / REQUIRED / SHALL / SHALL NOT / SHOULD / SHOULD NOT / RECOMMENDED / MAY / OPTIONAL. + +## What this document isn't + +This document specifies only the structure and semantics of supergraphs. It's expected that a supergraph will generally be the output of a compilation process which composes subgraphs. The mechanics of that process are not specified normatively here. Conforming implementations may choose any approach they like, so long as the result conforms to the requirements of this document. + +# Example: Photo Library + +*This section is non-normative.* + +We'll refer to this example of a photo library throughout the document: + +:::[example](./photos.graphql) -- Photos library composed schema + +The meaning of the `@join__*` directives is explored in the [Directives](#sec-Directives) section. + +The example represents **one way** to compose three input schemas, based on [federated composition](https://www.apollographql.com/docs/federation/federation-spec/). These schemas are provided for purposes of illustration only. This spec places no normative requirements on composer input. It does not require that subgraphs use federated composition directives, and it does not place any requirements on *how* the composer builds a supergraph, except to say that the resulting schema must be a valid supergraph document. + +The [auth](./auth.graphql) subgraph provides the `User` type and `Query.me`. + +:::[example](auth.graphql) -- Auth schema + +The [images](./images.graphql) subgraph provides the `Image` type and `URL` scalar. + +:::[example](./images.graphql) -- Images schema + +The [albums](./albums.graphql) subgraph provides the `Album` type and extends `User` and `Image` with album information. + +:::[example](./albums.graphql) -- Albums schema + + +# Actors + +```mermaid diagram -- Actors and roles within an example composition pipeline +flowchart TB + classDef bg fill:#EBE6FF; + subgraph A [subgraph A] + schemaA([schema A]):::bg + style schemaA color:#000 + endpointA([endpoint A]):::bg + style endpointA color:#000 + end + style A fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + subgraph B [subgraph B] + schemaB([schema B]):::bg + style schemaB color:#000 + endpointB([endpoint B]):::bg + style endpointB color:#000 + end + style B fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + subgraph C [subgraph C] + schemaC([schema C]):::bg + style schemaC color:#000 + endpointC([endpoint C]):::bg + style endpointC color:#000 + end + style C fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + subgraph producer["Producer ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"] + Composer + style Composer color:#000 + end + style producer fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + supergraph([Supergraph]):::bg + style supergraph color:#000 + subgraph consumer["Consumer ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"] + Router + style Router color:#000 + end + style consumer fill:#FCFDFF,stroke:#CAD0D8,color:#777F8E; + A-->Composer:::bg + B-->Composer:::bg + C-->Composer:::bg + Composer-->supergraphSchema([Supergraph Schema]):::bg + style supergraphSchema color:#000 + supergraphSchema-->Router:::bg + Router-->published([Published Schema]):::bg + style published color:#000 + published-->Clients:::bg + style Clients color:#000 + Clients-->Router:::bg +``` + +**Producers** generate supergraphs. This spec places requirements on supergraph producers. + +**Consumers** consume supergraphs. This spec places requirements on supergraph consumers. + +**Composers** (or **compilers**) are producers which compose subgraphs into a supergraph. This document places no particular requirements on the composition algorithm, except that it must produce a valid supergraph. + +**Routers** are consumers which serve a composed schema as a GraphQL endpoint. *This definition is non-normative.* + - Graph routers differ from standard GraphQL endpoints in that they are not expected to resolve fields or communicate with (non-GraphQL) backend services on their own. Instead, graph routers receive GraphQL requests and service them by performing additional GraphQL requests. This spec provides guidance for implementing routers, but does not require particular implementations of query separation or dispatch, nor does it attempt to normatively separate routers from other supergraph consumers. + - Routers expose an [API schema](https://specs.apollo.dev/core/v0.1/#sec-Parts-of-a-Core-Schema) to clients that is created by transforming the supergraph schema (for example, the {join__Graph} enum and the directives described in this spec are removed from the API schema). The API schema is used to validate client operations and may be exposed to clients via introspection. + +**Endpoints** are running servers which can resolve GraphQL queries against a schema. In this version of the spec, endpoints must be URLs, typically http/https URLs. + +**Subgraphs** are GraphQL schemas which are composed to form a supergraph. Subgraph names and metadata are declared within the special {join__Graph} enum. + +This spec does not place any requirements on subgraph schemas. Generally, they may be of any shape. In particular, subgraph schemas do not need to be supergraphs themselves or to follow this spec in any way; neither is it an error for them to do so. Composers MAY place additional requirements on subgraph schemas to aid in composition; composers SHOULD document any such requirements. + +# Overview + +*This section is non-normative.* It describes the motivation behind the directives defined by this specification. + +A supergraph schema describes a GraphQL schema that can be served by a router. The router does not contain logic to resolve any of the schema's fields; instead, the supergraph schema contains directives starting with {@join__} that tell the router which subgraph endpoint can resolve each field, as well as other information needed in order to construct subgraph operations. + +The directives described in this specification are designed for a particular query planning algorithm, and so there are some restrictions on how they can be combined that originate from the requirements of this algorithm. For example, this specification describes a concept of [type ownership](#sec-Owned-fields-on-owned-types) which exists not because we believe it describes the ideal method of structuring your subgraphs, but because this query planning algorithm depends on type ownership. We hope that future versions of this specification can relax some of these restrictions. + +Each supergraph schema contains a list of its included subgraphs. The [{join__Graph}](#join__Graph) enum represents this list with an enum value for each subgraph. Each enum value is annotated with a [{@join__graph}](#@join__graph) directive telling the router what endpoint can be used to reach the subgraph, and giving the subgraph a human-readable name that can be used for purposes such as query plan visualization and server logs. + +To resolve a field, the router needs to know to which subgraphs it can delegate the field's resolution. One explicit way to indicate this in a supergraph schema is by annotating the field with a [{@join__field}](#@join__field) directive specifying which subgraph should be used to resolve that field. (There are other ways of indicating which subgraphs can resolve a field which will be described later.) + +In order for the router to send an operation that resolves a given field on a parent object to a subgraph, the operation needs to first resolve the parent object itself. There are several ways to accomplish this, described below. The examples below include abbreviated versions of the supergraph schemas which do not include the `schema` definition, directive definitions, or the `join__Graph` definition. This specification does not require the subgraph operations to be the same as those described in these examples; this is just intended to broadly describe the meanings of the directives. + +## Root fields + +If a field appears at the root of the overall operation (query or mutation), then it can be placed at the root of the subgraph operation. + +```graphql example -- Root fields +# Supergraph schema +type Query { + fieldA: String @join__field(graph: A) + fieldAlsoFromA: String @join__field(graph: A) + fieldB: String @join__field(graph: B) +} + +# Operation +{ fieldA fieldAlsoFromA fieldB } +# Generated subgraph operations +## On A: +{ fieldA fieldAlsoFromA } +## On B: +{ fieldB } +``` + + +## Fields on the same subgraph as the parent operation + +If a field's parent field will be resolved by an operation on the same subgraph, then it can be resolved as part of the same operation, by putting it in a nested selection set on the parent field's subgraph operation. Note that this example contains {@join__owner} and {@join__type} directives on an object type; these will be described later. + +```graphql example -- Fields on the same subgraph as the parent operation +# Supergraph schema +type Query { + fieldA: X @join__field(graph: A) +} + +type X @join__owner(graph: A) @join__type(graph: A, key: "nestedFieldA") { + nestedFieldA: String @join__field(graph: A) +} + +# Operation +{ fieldA { nestedFieldA } } +# Generated subgraph operations +## On A: +{ fieldA { nestedFieldA }} +``` + +## Fields provided by the parent field + +Sometimes, a subgraph {G} may be capable of resolving a field that is ordinarily resolved in a different subgraph if the field's parent object was resolved in {G}. Consider an example where the `Product.priceCents: Int!` field is usually resolved by the Products subgraph, which knows the `priceCents` for every `Product` in your system. In the Marketing subgraph, there is a `Query.todaysPromotion: Product!` field. While the Marketing subgraph cannot determine the `priceCents` of every product in your system, it does know the `priceCents` of the promoted products, and so the Marketing subgraph can resolve operations like `{ todaysPromotion { priceCents } }`. + +When this is the case, you can include a `provides` argument in the `@join__field` listing these "pre-calculated" fields. The router can now resolve these fields in the "providing" subgraph instead of in the subgraph that would usually be used to resolve those fields. + +```graphql example -- Provided fields +# Supergraph schema +type Query { + todaysPromotion: Product! @join__field(graph: MARKETING, provides: "priceCents") + randomProduct: Product! @join__field(graph: PRODUCTS) +} + +type Product @join__owner(graph: PRODUCTS) @join__type(graph: PRODUCTS, key: "id") { + id: ID! @join__field(graph: PRODUCTS) + priceCents: Int! @join__field(graph: PRODUCTS) +} + +# Operation showing that `priceCents` is typically resolved on PRODUCTS +{ randomProduct { priceCents } } +# Generated subgraph operations +## On PRODUCTS +{ randomProduct { priceCents } } + +# Operation showing that `provides` allows `priceCents` to be resolved on MARKETING +{ todaysPromotion { priceCents } } +# Generated subgraph operations +## On MARKETING +{ todaysPromotion { priceCents } } +``` + +## Fields on value types + +Some types have the property that all of their fields can be resolved by *any* subgraph that can resolve a field returning that type. These types are called *value types*. (Imagine a type `type T { x: Int, y: String }` where every resolver for a field of type `T` actually produces an object like `{x: 1, y: "z"}`, and the resolvers for the two fields on `T` just unpack the values already in the object.) In a supergraph schema, a type is a value type if it does not have a [{@join__owner}](#@join__owner) directive on it. + +```graphql example -- Value types +# Supergraph schema +type Query { + fieldA: X @join__field(graph: A) + fieldB: X @join__field(graph: B) +} + +type X { + anywhere: String +} + +# Operation +{ fieldA { anywhere } } +# Generated subgraph operations +## On A +{ fieldA { anywhere } } + +# Operation +{ fieldB { anywhere } } +# Generated subgraph operations +## On B +{ fieldB { anywhere } } +``` + +## Owned fields on owned types + +We've finally reached the most interesting case: a field that must be resolved by an operation on a different subgraph from the subgraph on which its parent field was resolved. In order to do this, we need a way to tell the subgraph to resolve that parent object. We do this by defining a special root field in the subgraph's schema: `Query._entities(representations: [_Any!]!): [_Entity]!`. This field takes a list of "representations" and returns a list of the same length of the corresponding objects resulting from looking up the representations in an application-dependent way. + +What is a representation? A representation is expressed as the scalar type `_Any`, and can be any JSON object with a top-level `__typename` key with a string value. Often, a representation will be something like `{__typename: "User", id: "abcdef"}`: the type name plus one or more fields that you can use to look up the object in a database. + +There are several ways that the router can calculate a representation to pass to a subgraph. In this specification, all non-value types have a specific subgraph referred to as its "owner", specified via a `@join__owner` directive on the type. Object types that are not value types are referred to as "entities"; the type `_Entity` referenced above is a union defined in each subgraph's schema consisting of the entity types defined by that subgraph. (Only subgraphs which define entities need to define the `Query._entities` field.) Entity types must also have at least one `@join__type` directive specifying the owning subgraph along with a {key}. For each additional subgraph which can resolve fields returning that type, there should be exactly one `@join__type` directive specifying that subgraph along with a {key}, which should be identical to one of the keys specified with the owning subgraph. + +A key is a set of fields on the type (potentially including sub-selections and inline fragments), specified as a string. If a type `T` is annotated with `@join__type(subgraph: G, key: "a b { c }")`, then it must be possible to resolve the full field set provided as a key on subgraph G. Additionally, if you take an object with the structure returned by resolving that field set and add a field `__typename: "T"`, then you should be able to pass the resulting value as a representation to the `Query._entities` field on subgraph G. + +In order to resolve a field on an entity on the subgraph that owns its parent type, where that subgraph is different from the subgraph that resolved its parent object, the router first resolves a key for that object on the previous subgraph, and then uses that representation on the owning subgraph. + +For convenience, you may omit `@join__field(graph: A)` directives on fields whose parent type is owned by `A`. + +```graphql example -- Owned fields on owned types +# Supergraph schema +type Query { + fieldB: X @join__field(graph: B) +} + +type X + @join__owner(graph: A) + # As the owner, A is allowed to have more than one key. + @join__type(graph: A, key: "x") + @join__type(graph: A, key: "y z") + # As non-owners, B and C can only have one key each and + # they must match a key from A. + @join__type(graph: B, key: "x") + @join__type(graph: C, key: "y z") +{ + # Because A owns X, we can omit @join__field(graph: A) + # from these three fields. + x: String + y: String + z: String +} + +# Operation +{ fieldB { y } } +# Generated subgraph operations +## On B. `y` is not available, so we need to fetch B's key for X. +{ fieldB { x } } +## On A +## $r = [{__typename: "X", x: "some-x-value"}] +query ($r: [_Any!]!) { _entities(representations: $r]) { y } } +``` + +## Extension fields on owned types + +The previous section described how to jump from one subgraph to another in order to resolve a field on the subgraph that owns the field's parent type. The situation is a bit more complicated when you want to resolve a field on a subgraph that doesn't own the field's parent type — what we call an extension field. That's because we no longer have the guarantee that the subgraph you're coming from and the subgraph you're going to share a key in common. In this case, we may need to pass through the owning type. + +```graphql example -- Extension fields on owned types +# Supergraph schema +type Query { + fieldB: X @join__field(graph: B) +} + +type X + @join__owner(graph: A) + # As the owner, A is allowed to have more than one key. + @join__type(graph: A, key: "x") + @join__type(graph: A, key: "y z") + # As non-owners, B and C can only have one key each and + # they must match a key from A. + @join__type(graph: B, key: "x") + @join__type(graph: C, key: "y z") +{ + x: String + y: String + z: String + c: String @join__field(graph: C) +} + +# Operation +{ fieldB { c } } +# Generated subgraph operations +## On B. `c` is not available on B, so we need to eventually get over to C. +## In order to do that, we need `y` and `z`... which aren't available on B +## either! So we need to take two steps. First we use B's key. +{ fieldB { x } } +## On A. We use B's key to resolve our `X`, and we extract C's key. +## $r = [{__typename: "X", x: "some-x-value"}] +query ($r: [_Any!]!) { _entities(representations: $r]) { y z } } +## On C. We can finally look up the field we need. +## $r = [{__typename: "X", y: "some-y-value", z: "some-z-value"}] +query ($r: [_Any!]!) { _entities(representations: $r]) { c } } +``` + +We only need to do this two-jump process because the fields needed for C's key are not available in B; otherwise a single jump would have worked, like in the owned-field case. + +Sometimes a particular extension field needs its parent object's representation to contain more information than its parent type's key requests. In this case, you can include a `requires` argument in the field's `@join__field` listing those required fields (potentially including sub-selections). **All required fields must be resolvable in the owning subgraph** (this restriction is why `requires` is only allowed on extension fields). + +```graphql example -- Required fields +# Supergraph schema +type Query { + fieldA: X @join__field(graph: A) +} + +type X + @join__owner(graph: A) + @join__type(graph: A, key: "x") + @join__type(graph: B, key: "x") +{ + x: String + y: String + z: String @join__field(graph: B, requires: "y") +} + +# Operation +{ fieldA { z } } +# Generated subgraph operations +## On A. `x` is included because it is B's key for `X`; `y` +## is included because of the `requires`. +{ fieldA { x y } } +## On B.. +## $r = [{__typename: "X", x: "some-x-value", y: "some-y-value"}] +query ($r: [_Any!]!) { _entities(representations: $r]) { z } } +``` + +# Basic Requirements + +Schemas using the `join` core feature MUST be valid [core schema documents](https://specs.apollo.dev/core/v0.1) with {@core} directives referencing the `core` specification and this specification. + +:::[example](photos.graphql#schema) -- {@core} directives for supergraphs + +As described in the [core schema specification](https://specs.apollo.dev/core/v0.1/#sec-Prefixing), your schema may use a prefix other than `join` for all of the directive and enum names defined by this specification by including an `as` argument to the `@core` directive which references this specification. All references to directive and enum names in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema. + +In order to use the directives described by this specification, GraphQL requires you to include their definitions in your schema. + +Processors MUST validate that you have defined the directives with the same arguments, locations, and `repeatable` flag as given below. + +:::[definition](spec.graphql) + +Processors MUST validate that the schema contains an enum named {join__Graph}; see [its section below](#join__Graph) for other required properties of this enum. + +As described in the core specification, all of the directives and enums defined by this schema should be removed from the supergraph's [API schema](https://specs.apollo.dev/core/v0.1/#sec-Parts-of-a-Core-Schema). For example, the {join__Graph} enum should not be visible via introspection. + +# Enums + +##! join__Graph + +Enumerate subgraphs. + +```graphql definition +enum join__Graph +``` + +Documents MUST define a {join__Graph} enum. Each enum value describes a subgraph. Each enum value MUST have a [{@join__graph}](#@join__graph) directive applied to it. + +:::[example](photos.graphql#join__Graph) -- Using join__Graph to define subgraphs and their endpoints + +The {join__Graph} enum is used as input to the [{@join__owner}](#@join__owner), [{@join__field}](#@join__field), and [{@join__type}](#@join__type) directives. + +# Directives + +##! @join__graph + +Declare subgraph metadata on {join__Graph} enum values. + +```graphql definition +directive @join__graph(name: String!, url: String!) on ENUM_VALUE +``` + +:::[example](photos.graphql#join__Graph) -- Using {@join__graph} to declare subgraph metadata on the {join__Graph} enum values. + +The {@join__graph} directive MUST be applied to each enum value on {join__Graph}, and nowhere else. Each application of {@join__graph} MUST have a distinct value for the `name` argument; this name is an arbitrary non-empty string that can be used as a human-readable identifier which may be used for purposes such as query plan visualization and server logs. The `url` argument is an endpoint that can resolve GraphQL queries for the subgraph. + +##! @join__type + +Declares an entity key for a type on a subgraph. + +```graphql definition +directive @join__type( + graph: join__Graph! + key: String! +) repeatable on OBJECT | INTERFACE +``` + +When this directive is placed on a type `T`, it means that subgraph `graph` MUST be able to: +- Resolve selections on objects of the given type that contain the field set in `key` +- Use `Query._entities` to resolve representations of objects containing `__typename: "T"` and the fields from the field set in `key` + +:::[example](photos.graphql#Image) -- Using {@join__type} to specify subgraph keys + +Every type with a {@join__type} MUST also have a [{@join__owner}](#@join__owner) directive. Any type with a [{@join__owner}](#@join__owner) directive MUST have at least one {@join__type} directive with the same `graph` as the [{@join__owner}](#@join__owner) directive (the "owning graph"), and MUST have at most one {@join__type} directive for each `graph` value other than the owning graph. Any value that appears as a `key` in a {@join__type} directive with a `graph` value other than the owning graph must also appear as a `key` in a {@join__type} directive with `graph` equal to the owning graph. + +##! @join__field + +Specify the graph that can resolve the field. + +```graphql definition +directive @join__field( + graph: join__Graph + requires: String + provides: String +) on FIELD_DEFINITION +``` + +The field's parent type MUST be annotated with a {@join__type} with the same value of `graph` as this directive, unless the parent type is a [root operation type](http://spec.graphql.org/draft/#sec-Root-Operation-Types). + +If a field is not annotated with {@join__field} (or if the `graph` argument is not provided or `null`) and its parent type is annotated with `@join__owner(graph: G)`, then a processor MUST treat the field as if it is annotated with `@join__field(graph: G)`. If a field is not annotated with {@join__field} (or if the `graph` argument is not provided or `null`) and its parent type is not annotated with {@join__owner} (ie, the parent type is a value type) then it MUST be resolvable in any subgraph that can resolve values of its parent type. + +:::[example](photos.graphql#User...Image) -- Using {@join__field} to join fields to subgraphs + +Every field on a root operation type MUST be annotated with {@join__field}. + +:::[example](photos.graphql#Query) -- {@join__field} on root fields + +The `requires` argument MUST only be specified on fields whose parent type has a [{@join__owner}](#@join__owner) directive specifying a different `graph` than this {@join__field} directive does. All fields (including nested fields) mentioned in this field set must be resolvable in the parent type's owning subgraph. When constructing a representation for a parent object of this field, a router will include the fields selected in this `requires` argument in addition to the appropriate `key` for the parent type. + +The `provides` argument specifies fields that can be resolved in operations run on subgraph `graph` as a nested selection under this field, even if they ordinarily can only be resolved on other subgraphs. + +##! @join__owner + +Specify the graph which owns the object type. + +```graphql definition +directive @join__owner(graph: join__Graph!) on OBJECT +``` + +The descriptions of [{@join__type}](#@join__type) and [{@join__field}](#@join__field) describes requirements on how {@join__owner} relates to {@join__type} and the `requires` argument to {@join__field}. + +Note: Type ownership is currently slated for removal in a future version of this spec. It is RECOMMENDED that router implementations consider approaches which function in the absence of these restrictions. The [overview](#sec-Owned-fields-on-owned-types) explains how the current router's query planning algorithm depends on concept of type ownership. diff --git a/join/v0.1/package.json b/join/v0.1/package.json new file mode 100644 index 0000000..d7e225f --- /dev/null +++ b/join/v0.1/package.json @@ -0,0 +1,18 @@ +{ + "name": "csdl", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "rsync -avz --exclude .dist . .dist && spec-md spec.md > .dist/index.html", + "dev": "npm run build || true && chokidar '**/*' -i '.dist' -c 'npm run build'" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@queerviolet/speck": "git://github.com/queerviolet/speck.git#main", + "chokidar-cli": "^2.1.0", + "watch": "^1.0.2" + } +} diff --git a/join/v0.1/photos.graphql b/join/v0.1/photos.graphql new file mode 100644 index 0000000..2eb8e3c --- /dev/null +++ b/join/v0.1/photos.graphql @@ -0,0 +1,62 @@ +schema + @core(feature: "https://specs.apollo.dev/core/v1.0") + @core(feature: "https://specs.apollo.dev/join/v1.0") { + query: Query +} + +directive @core(feature: String!) repeatable on SCHEMA + +directive @join__owner(graph: join__Graph!) on OBJECT + +directive @join__type( + graph: join__Graph! + key: String! +) repeatable on OBJECT | INTERFACE + +directive @join__field( + graph: join__Graph + requires: String + provides: String +) on FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +enum join__Graph { + AUTH @join__graph(name: "auth", url: "https://auth.api.com") + ALBUMS @join__graph(name: "albums", url: "https://albums.api.com") + IMAGES @join__graph(name: "images", url: "https://images.api.com") +} + +type Query { + me: User @join__field(graph: AUTH) + images: [Image] @join__field(graph: IMAGES) +} + +type User + @join__owner(graph: AUTH) + @join__type(graph: AUTH, key: "id") + @join__type(graph: ALBUMS, key: "id") { + id: ID! @join__field(graph: AUTH) + name: String @join__field(graph: AUTH) + albums: [Album!] @join__field(graph: ALBUMS) +} + +type Album + @join__owner(graph: ALBUMS) + @join__type(graph: ALBUMS, key: "id") { + id: ID! + user: User + photos: [Image!] +} + +type Image + @join__owner(graph: IMAGES) + @join__type(graph: ALBUMS, key: "url") + @join__type(graph: IMAGES, key: "url") { + url: Url @join__field(graph: IMAGES) + type: MimeType @join__field(graph: IMAGES) + albums: [Album!] @join__field(graph: ALBUMS) +} + +scalar Url +scalar MimeType diff --git a/join/v0.1/spec.graphql b/join/v0.1/spec.graphql new file mode 100644 index 0000000..876f2ad --- /dev/null +++ b/join/v0.1/spec.graphql @@ -0,0 +1,14 @@ +directive @join__owner(graph: join__Graph!) on OBJECT + +directive @join__type( + graph: join__Graph!, + key: String!, +) repeatable on OBJECT | INTERFACE + +directive @join__field( + graph: join__Graph, + requires: String, + provides: String, +) on FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE diff --git a/link/v1.0/index.html b/link/v1.0/index.html index 3625bac..60b93f9 100644 --- a/link/v1.0/index.html +++ b/link/v1.0/index.html @@ -1054,9 +1054,9 @@

      link v1.0

      Core schemas provide tools for linking definitions from different GraphQL schemas together into one.

      Example № 1 linking a directive from another schema
      extend schema
      -  # you link @link by @linking link 
      +  #          👇🏽 first, link @link from this url
         @link(url: "https://specs.apollo.dev/link/v1.0")
      -  #          👇🏽 schemas are identified by a url
      +  #          👇🏽 link other schemas by their urls
         @link(url: "https://internal.example.com/admin")
       
       type Query {
      @@ -1088,36 +1088,39 @@ 

      link v1.0

    10. 3Scope
      1. 3.1Entries added by @link
      2. 3.2Entry added by @id
      3. -
      4. 3.3Bootstrapping
    11. -
    12. 4Fully Valid Core Schemas
    13. -
    14. 5Definitions
        -
      1. 5.1@link
          -
        1. 5.1.1url: String!
        2. -
        3. 5.1.2as: String
        4. -
        5. 5.1.3import: [Import]
        6. -
        7. 5.1.4for: Purpose
        8. +
        9. 4Name Conventions
            +
          1. 4.1Bootstrapping
        10. -
        11. 5.2@id
        12. -
        13. 5.3Import
        14. -
        15. 5.4Purpose
            -
          1. 5.4.1SECURITY
          2. -
          3. 5.4.2EXECUTION
          4. +
          5. 5Fully Valid Core Schemas
          6. +
          7. 6Definitions
              +
            1. 6.1@link
                +
              1. 6.1.1url: String!
              2. +
              3. 6.1.2as: String
              4. +
              5. 6.1.3import: [Import]
              6. +
              7. 6.1.4for: Purpose
            2. +
            3. 6.2@id
            4. +
            5. 6.3Import
            6. +
            7. 6.4Purpose
                +
              1. 6.4.1SECURITY
              2. +
              3. 6.4.2EXECUTION
            8. -
            9. 6Appendix: Validations & Algorithms
                -
              1. 6.1Construct the document's scope
              2. -
              3. 6.2Get all bindings from a @link directive
              4. -
              5. 6.3Detecting a bootstrap directive
              6. -
              7. 6.4Locating definitions and references
            10. -
            11. 7Appendix: Versioning
                -
              1. 7.1Satisfaction
              2. +
              3. 7Appendix: Validations & Algorithms
                  +
                1. 7.1Construct the document's scope
                2. +
                3. 7.2Get all bindings from a @link directive
                4. +
                5. 7.3Detecting a bootstrap directive
                6. +
                7. 7.4Locating definitions and references
                8. +
                +
              4. +
              5. 8Appendix: Versioning
                  +
                1. 8.1Satisfaction
              6. §Index
              7. @@ -1227,14 +1230,14 @@

                3

                Core schemas have a document‐wide scope. A document’s scope is a map of ElementBinding. The scope is constructed from a document’s @link and @id directives and is used to attribute definitions and references within the document.

                Elements are the same as in global graph references. When used as scope keys, they carry the following meanings:

                  -
                • Schema(name) — a schema @linked from the document. name can be used as a prefix for definitions and references within the document, and name MUST either be a valid prefix or null, indicating the present schema.
                • +
                • Schema(name) — a schema @linked from the document. name can be used as a prefix for definitions and references within the document, and name MUST either be a valid prefix or null, indicating the present schema.
                • Directive(name) — a directive imported into the document
                • Type(name) — a type imported into the document

                A Binding contains:

                • gref: GRef — the global graph reference which is the target of the binding
                • -
                • implicit: Bool — indicating whether the binding was explicitly imported or created implicitly. Implicit bindings may be overwritten by explicit bindings and will not be formed if an explicit binding for the item alreaady exists
                • +
                • implicit: Bool — indicating whether the binding was explicitly imported or created implicitly. Implicit bindings are “soft”—they may be overwritten by explicit bindings and will not be formed if an explicit binding for the item alreaady exists.

                Similar to a gref‘s elements, different types of scoped items can have the same name without conflict. For example, a scope can contain both a type and schema named “User”, although this should generally be avoided if possible.

                The global graph reference mapped to the target MUST match the item’s type—a scope cannot map a schema to a directive, for instance. The algorithms provided in this document ensure this is always the case.

                @@ -1246,46 +1249,46 @@

                has a name. This somewhat‐blessed directive is the schema’s “root directive”

              @linking a foreign schema whose URL does not have a name will create a schema binding if and only if as: is specified, and will never create a root directive reference:

              A @link with imports will add these entries to the scope, in addition to entries for each import:

              Specifying as: changes the names of the scope items, but not their bound grefs:

              It is not an error to overwrite an implicit binding with an explicit one:

              But it is an error to overwrite an explicit binding, or for two implicit bindings to overlap:

              Document processors MAY reject schemas with such errors outright.

              @@ -1298,25 +1301,56 @@

              # 1. Schema() -> https://example.com/myself (explicit)

    15. -
      -

      3.3Bootstrapping

      +
      +
      +

      4Name Conventions

      +

      Within a core schema, type names and directives which begin with a valid namespace identifier followed by two underscores (__) will be attributed to the foreign schema bound to that name in the document scope if one exists.

      +
      Example № 14 using a prefixed name
      extend schema
      +  @link(url: "https://specs.apollo.dev/link/v1.0")  
      +# 1. Schema("link") -> "https://specs.apollo.dev/link/v1.0" (explicit)
      +# 2. Directive("link") -> "https://specs.apollo.dev/link/v1.0#@link" (implicit)
      +
      +#    👇🏽 🌍 https://specs.apollo.dev/link/v1.0/#Purpose
      +enum link__Purpose { SECURITY EXECUTION }
      +
      +

      If no schema has been linked to that name, it is interpreted as a local name:

      +
      Example № 15 a strange local name
      extend schema
      +  @link(url: "https://specs.apollo.dev/link/v1.0")  
      +# 1. Schema("link") -> "https://specs.apollo.dev/link/v1.0" (explicit)
      +# 2. Directive("link") -> "https://specs.apollo.dev/link/v1.0#@link" (implicit)
      +
      +#    👇🏽 🌍 #myOwn__Purpose (note, this document has no @id, so the url of this gref is null)
      +enum myOwn__Purpose { SECURITY EXECUTION }
      +
      +
      Example № 16 a strange local name in a document with an id
      extend schema
      +  @id(url: "https://api.example.com")
      +  @link(url: "https://specs.apollo.dev/link/v1.0", import: ["@id"]
      +
      +#    👇🏽 🌍 https://api.example.com#myOwn__Purpose (note, this document has no @id, so the url of this gref is null)
      +enum myOwn__Purpose { SECURITY EXECUTION }
      +
      +
      +Note +GraphQL name conventions strongly suggest against such naming. But amongst the core schema design principles is universality—the ability to represent and link any arbitrary set of GraphQL schemas, no matter how weird the names in them are.
      +
      +

      4.1Bootstrapping

      Documents can @link link itself. Indeed, if they MUST do so if they use @link at all and are intended to be fully valid:

      -
      @@ -1163,9 +1163,9 @@

      output2-->reader final-->reader
        -
      • Authors (either human or machine) write an initial core schema as specified in this document, including versioned @core requests for all directives they use
      • -
      • Machine processors can process core schemas and output new core schemas. The versioning of directives and associated schema elements provided by the @core allows processors to operate on directives they understand and pass through directives they do not.
      • -
      • Human readers can examine the core schema at various stages of processing. At any stage, they can examine the @core directives and follow URLs to the specification, receiving an explanation of the requirements of the specification and what new directives, types, and other schema objects are available within the document.
      • +
      • Authors (either human or machine) write an initial core schema as specified in this document, including versioned @core requests for all directives they use
      • +
      • Machine processors can process core schemas and output new core schemas. The versioning of directives and associated schema elements provided by the @core allows processors to operate on directives they understand and pass through directives they do not.
      • +
      • Human readers can examine the core schema at various stages of processing. At any stage, they can examine the @core directives and follow URLs to the specification, receiving an explanation of the requirements of the specification and what new directives, types, and other schema objects are available within the document.
      • Data cores can then pick up the processed core schema and provide some data‐layer service with it. Typically this means serving the schema’s API as a GraphQL endpoint, using metadata defined by machinery to inform how it processes operations it receives. However, data cores may perform other tasks described in the core schema, such as routing to backend services, caching commonly‐accessed fields and queries, and so on. The term “data core” is intended to capture this multiplicity of possible activities.

      @@ -1175,23 +1175,23 @@

      MUST be valid GraphQL schema documents,
    16. MUST contain exactly one SchemaDefinition, and
    17. -
    18. MUST use the @core directive on their schema definition to declare any features they reference by using @core to reference a well‐formed feature URL.
    19. +
    20. MUST use the @core directive on their schema definition to declare any features they reference by using @core to reference a well‐formed feature URL.
    21. -

      The first @core directive on the schema MUST reference the core spec itself, i.e. this document.

      -
      Example № 2 Basic core schema using @core and @example
      schema
      +

      The first @core directive on the schema MUST reference the core spec itself, i.e. this document.

      +
      Example № 2 Basic core schema using @core and @example
      schema
         @core(feature: "https://specs.apollo.dev/core/v0.1")
         @core(feature: "https://specs.example.com/example/v1.0")
       {
      -  query: Query
      +  query: Query
       }
       
       type Query {
      -  field: Int @example
      +  field: Int @example
       }
       
      -directive @example on FIELD_DEFINITION
      +directive @example on FIELD_DEFINITION
       
      -directive @core(feature: String!, as: String) repeatable on SCHEMA
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
       

      3.1Unspecified directives are passed through by default

      @@ -1199,48 +1199,48 @@

      Example № 3 Unspecified directives are passed through
      schema
         @core(feature: "https://specs.apollo.dev/core/v0.1")
       {
      -  query: Query
      +  query: Query
       }
       
       type SomeType {
      -  field: Int @another
      +  field: Int @another
       }
       
       # `@another` is unspecified. Core processors will not extract metadata from
       # it, but its definition and all usages within the schema will be exposed
       # in the API.
      -directive @another on FIELD_DEFINITION
      +directive @another on FIELD_DEFINITION
       
      -directive @core(feature: String!, as: String) repeatable on SCHEMA
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
       

      3.2Renaming core itself

      It is possible to rename the core feature itself with the same as: mechanism used for all features:

      -
      Example № 4 Renaming @core to @coreSchema
      schema
      +
      Example № 4 Renaming @core to @coreSchema
      schema
         @coreSchema(feature: "https://specs.apollo.dev/core/v0.1", as: "coreSchema")
         @coreSchema(feature: "https://example.com/example/v1.0")
       {
      -  query: Query
      +  query: Query
       }
       
       type SomeType {
      -  field: Int @example
      +  field: Int @example
       }
       
      -directive @coreSchema(feature: String!, as: String)
      -  repeatable on SCHEMA
      -directive @example on FIELD_DEFINITION
      +directive @coreSchema(feature: String!, as: String)
      +  repeatable on SCHEMA
      +directive @example on FIELD_DEFINITION
       

      4Directive Definitions

      -

      All core schemas use the @core directive to declare their use of the core feature itself as well as any other core features they use.

      +

      All core schemas use the @core directive to declare their use of the core feature itself as well as any other core features they use.

      In order to use these directives in your schema, GraphQL requires you to include their definitions in your schema.

      Processors MUST validate that you have defined the directives with the same arguments, locations, and repeatable flag as given below. Specifically, the bootstrapping algorithm validates that the @core directive has a definition matching the definition given below. (The bootstrapping algorithm does not require processors to validate other aspects of the directive declaration such as description strings or argument ordering. The main purpose of this validation is to ensure that directive arguments have the type and default values expected by the specification.)

      The following declares the directive defined by this specification. You SHOULD define the directives in your core schema by including the following text in your schema document.

      -
      directive @core(feature: String!, as: String) repeatable on SCHEMA
      +
      directive @core(feature: String!, as: String) repeatable on SCHEMA
       

      When writing a specification for your own core feature, you SHOULD include a section like this one with sample definitions to copy into schemas, and you SHOULD require processors to validate that directive definitions in documents match your sample definitions.

      @@ -1249,14 +1249,13 @@

      5.1@core

      Declare a core feature present in this schema.

      -
      directive @core(
      -  feature: String!,
      -  as: String)
      -  repeatable on SCHEMA
      -
      -

      Documents MUST include a definition for the @core directive which includes all of the arguments defined above with the same types and default values.

      -
      -

      5.1.1feature: String!

      +
      directive @core(
      +  feature: String!,
      +  as: String)
      +  repeatable on SCHEMA
      +

      Documents MUST include a definition for the @core directive which includes all of the arguments defined above with the same types and default values.

      +
      +

      5.1.1feature: String!

      A feature URL specifying the directive and associated schema elements. When viewed, the URL SHOULD provide the content of the appropriate version of the specification in some human‐readable form. In short, a human reader should be able to click the link and go to the docs for the version in use. There are specific requirements on the format of the URL, but it is not required that the content be machine‐readable in any particular way.

      Feature URLs contain information about the spec’s prefix and version.

      Feature URLs serve two main purposes:

      @@ -1281,23 +1280,23 @@

      5.1.1.2Why is versioning in the URL, not a directive argument?

      -

      The version is in the URL because when a human reader visits the URL, we would like them to be taken to the documentation for the version of the feature used by this document. Many text editors will turn URLs into hyperlinks, and it’s highly desirable that clicking the link takes the user to the correct version of the docs. Putting the version information in a separate argument to the @core directive would prevent this.

      +

      The version is in the URL because when a human reader visits the URL, we would like them to be taken to the documentation for the version of the feature used by this document. Many text editors will turn URLs into hyperlinks, and it’s highly desirable that clicking the link takes the user to the correct version of the docs. Putting the version information in a separate argument to the @core directive would prevent this.

      -
      -

      5.1.2as: String

      +
      +

      5.1.2as: String

      Change the names of directives and schema elements from this specification. The specified string MUST be a valid GraphQL name and MUST NOT contain the namespace separator (two underscores, "__") or end with an underscore.

      When as: is provided, processors looking for prefixed schema elements MUST look for elements whose names are the specified name with the prefix replaced with the name provided to the as: argument.

      -
      Example № 5 Using @core(feature:, as:) to use a feature with a custom name
      schema
      +
      Example № 5 Using @core(feature:, as:) to use a feature with a custom name
      schema
         @core(feature: "https://specs.apollo.dev/core/v0.1")
         @core(feature: "https://spec.example.com/example/v1.0", as: "eg")
       {
      -  query: Query
      +  query: Query
       }
       
       type User {
         # Specifying `as: "eg"` transforms @example into @eg
      -  name: String @eg(data: ITEM)
      +  name: String @eg(data: ITEM)
       }
       
       # Additional specified schema elements must have their prefixes set
      @@ -1306,15 +1305,15 @@ 

      # The spec at https://spec.example.com/example/v1.0 calls this enum # `example__Data`, but because of the `as:` argument above, processors # will use this `eg__Data` enum instead. -enum eg__Data { +enum eg__Data { ITEM } # Name transformation must also be applied to definitions pulled in from # specifications. -directive @eg(data: eg__Data) on FIELD_DEFINITION +directive @eg(data: eg__Data) on FIELD_DEFINITION -directive @core(feature: String!, as: String) repeatable on SCHEMA +directive @core(feature: String!, as: String) repeatable on SCHEMA

      @@ -1328,30 +1327,30 @@

      MUST NOT contain the core namespace separator, which is two underscores ("__"), and
    22. MUST NOT end with an underscore (which would create ambiguity between whether "x___y" is prefix x_ for element y or prefix x for element _y).
    23. -

      Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid GraphQL name. For instance, the core specification (which you are currently reading) introduces an element named @core, and the join specification introduces an element named @join__field (among others).

      +

      Prefixed names consist of the name of the feature, followed by two underscores, followed by the name of the element, which can be any valid GraphQL name. For instance, the core specification (which you are currently reading) introduces an element named @core, and the join specification introduces an element named @join__field (among others).

      Note that both parts must be valid GraphQL names, and GraphQL names cannot start with digits, so core feature specifications cannot introduce names like @feature__24hours.
      -

      A feature’s root directive is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the core specification introduces a @core directive. This directive has the same name as the feature (”core”), and so requires no prefix.

      +

      A feature’s root directive is an exception to the prefixing requirements. Feature specifications MAY introduce a single directive which carries only the name of the feature, with no prefix required. For example, the core specification introduces a @core directive. This directive has the same name as the feature (”core”), and so requires no prefix.

      Example № 6 Using the @core directive without changing the prefix
      schema
        @core(feature: "https://specs.apollo.dev/core/v0.1")
        @core(feature: "https://spec.example.com/example/v1.0") {
      -  query: Query
      +  query: Query
       }
       
       type User {
      -  name: String @example(data: ITEM)
      +  name: String @example(data: ITEM)
       }
       
       # An enum used to provide structured data to the example spec.
       # It is prefixed with the name of the spec.
      -enum example__Data {
      +enum example__Data {
         ITEM
       }
       
      -directive @example(data: example__Data) on FIELD_DEFINITION
      +directive @example(data: example__Data) on FIELD_DEFINITION
       
      -directive @core(feature: String!, as: String) repeatable on SCHEMA
      +directive @core(feature: String!, as: String) repeatable on SCHEMA
       

      The prefix MUST NOT be elided within documentation; definitions of schema elements provided within the spec MUST include the feature’s name as a prefix.

      @@ -1365,48 +1364,48 @@

      @core(feature: "https://specs.apollo.dev/core/v0.1") @core(feature: "https://spec.example.com/featureA/v1.0") @core(feature: "https://spec.example.com/featureB/v2.0", as: "B") { - query: Query + query: Query } """ featureA__SomeType is a type defined by feature A. """ -type featureA__SomeType { +type featureA__SomeType { """ nativeField is a field defined by featureA on a type also defined by featureA (namely featureA__SomeType) """ - nativeField: Int @featureA__fieldDirective + nativeField: Int @featureA__fieldDirective } """ featureA__SomeInput is an input specified by feature A """ -input featureA__SomeInput { +input featureA__SomeInput { """ nativeInputField is defined by featureA """ - nativeInputField: Int + nativeInputField: Int } """ featureA__Items is specified by feature A """ -enum featureA__Items { ONE, TWO, THREE @B } +enum featureA__Items { ONE, TWO, THREE @B } """ @B is the root directive defined by featureB Root directives are named after their feature """ -directive @B on ENUM_VALUE +directive @B on ENUM_VALUE """ @featureA__fieldDirective is a non-root (prefixed) directive defined by featureA """ -directive @featureA__fieldDirective on FIELD_DEFINITION +directive @featureA__fieldDirective on FIELD_DEFINITION -directive @core(feature: String!, as: String) repeatable on SCHEMA +directive @core(feature: String!, as: String) repeatable on SCHEMA

      @@ -1475,9 +1474,9 @@

      SHOULD pass through @core directives which reference unknown feature URLs +
    24. SHOULD pass through @core directives which reference unknown feature URLs
    25. SHOULD pass through prefixed directives, types, and other schema elements
    26. -
    27. SHOULD pass through directives which are not associated with a @core feature
    28. +
    29. SHOULD pass through directives which are not associated with a @core feature
    30. Processors MAY accept configuration which overrides these default behaviors.

      Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case‐by‐case basis.

      @@ -1492,9 +1491,9 @@

      rename the core feature within a document. This process determines the actual name for the core feature if one is present.

      • Fails the Has Schema validation if there are no SchemaDefinitions in the document
      • -
      • Fails the Has Core Feature validation if the core feature itself is not referenced with a @core directive within the document
      • -
      • Fails the Bootstrap Core Feature Listed First validation if the reference to the core feature is not the first @core directive on the document’s SchemaDefinition
      • -
      • Fails the Core Directive Incorrect Definition validation if the @core directive definition does not match the directive as defined by this specification.
      • +
      • Fails the Has Core Feature validation if the core feature itself is not referenced with a @core directive within the document
      • +
      • Fails the Bootstrap Core Feature Listed First validation if the reference to the core feature is not the first @core directive on the document’s SchemaDefinition
      • +
      • Fails the Core Directive Incorrect Definition validation if the @core directive definition does not match the directive as defined by this specification.

      For the purposes of this algorithm, a directive’s definition in a schema matches a definition provided in this specification if:

      Processors MAY accept configuration which overrides these default behaviors.

      Additionally, processors which prepare the schema for final public consumption MAY choose to eliminate all unknown directives and prefixed types in order to hide schema implementation details within the published schema. This will impair the operation of tooling which relies on these directives—such tools will not be able to run on the output schema, so the benefits and costs of this kind of information hiding should be weighed carefully on a case‐by‐case basis.

      @@ -1565,9 +1564,9 @@

      rename the core feature within a document. This process determines the actual name for the core feature if one is present.

      • Fails the Has Schema validation if there are no SchemaDefinitions in the document
      • -
      • Fails the Has Core Feature validation if the core feature itself is not referenced with a @core directive within the document
      • -
      • Fails the Bootstrap Core Feature Listed First validation if the reference to the core feature is not the first @core directive on the document’s SchemaDefinition
      • -
      • Fails the Core Directive Incorrect Definition validation if the @core directive definition does not match the directive as defined by this specification.
      • +
      • Fails the Has Core Feature validation if the core feature itself is not referenced with a @core directive within the document
      • +
      • Fails the Bootstrap Core Feature Listed First validation if the reference to the core feature is not the first @core directive on the document’s SchemaDefinition
      • +
      • Fails the Core Directive Incorrect Definition validation if the @core directive definition does not match the directive as defined by this specification.

      For the purposes of this algorithm, a directive’s definition in a schema matches a definition provided in this specification if:

        @@ -1593,7 +1592,7 @@

        d on schema,
        1. If d has a feature: argument which parses as a feature URL, and whose identity is "https://specs.apollo.dev/core/" and whose version is "v0.1", and either d has an as: argument whose value is equal to d‘s name or d does not have an as: argument and d‘s name is core:
          1. If any directive on schema listed before d has the same name as d, the Bootstrap Core Feature Listed First validation fails.
          2. -
          3. If the definition of the directive d does not match the definition of @core in this specification, the Core Directive Incorrect Definition validation fails.
          4. +
          5. If the definition of the directive d does not match the definition of @core in this specification, the Core Directive Incorrect Definition validation fails.
          6. Otherwise, Return d‘s name.
        2. @@ -1605,7 +1604,7 @@

          10.2Feature Collection

          -

          Collect a map of (featureName: String) → Directive, where Directive is a @core Directive which introduces the feature named featureName into the document.

          +

          Collect a map of (featureName: String) → Directive, where Directive is a @core Directive which introduces the feature named featureName into the document.

        @@ -1789,8 +1788,8 @@

        6.1core__Purpose
          -
        1. 6.1.1SECURITY
        2. -
        3. 6.1.2EXECUTION
        4. +
        5. 6.1.1SECURITY
        6. +
        7. 6.1.2EXECUTION
        diff --git a/federation/v2.0/federation-v2.0.graphql b/federation/v2.0/federation-v2.0.graphql new file mode 100644 index 0000000..ee73717 --- /dev/null +++ b/federation/v2.0/federation-v2.0.graphql @@ -0,0 +1,13 @@ +directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE +directive @requires(fields: FieldSet!) on FIELD_DEFINITION +directive @provides(fields: FieldSet!) on FIELD_DEFINITION +directive @external on OBJECT | FIELD_DEFINITION +directive @shareable on FIELD_DEFINITION | OBJECT +directive @extends on OBJECT | INTERFACE +directive @override(from: String!) on FIELD_DEFINITION +directive @inaccessible on + | FIELD_DEFINITION + | OBJECT + | INTERFACE + | UNION +scalar _FieldSet \ No newline at end of file diff --git a/federation/v2.0/federation-v2.0.md b/federation/v2.0/federation-v2.0.md index 73fd8f0..e1277a6 100644 --- a/federation/v2.0/federation-v2.0.md +++ b/federation/v2.0/federation-v2.0.md @@ -11,9 +11,7 @@ #! @key -```graphql definition -directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE -``` +:::[definition](./federation-v2.0.graphql#@key) The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. @@ -39,9 +37,7 @@ Note: Repeated directives (in this case, `@key`, used multiple times) require su #! @provides -```graphql definition -directive @provides(fields: FieldSet!) on FIELD_DEFINITION -``` +:::[definition](./federation-v2.0.graphql#@provides) The `@provides` directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example: @@ -61,9 +57,7 @@ When fetching `Review.product` from the Reviews service, it is possible to reque #! @requires -```graphql definition -directive @requires(fields: FieldSet!) on FIELD_DEFINITION -``` +:::[definition](./federation-v2.0.graphql#@requires) The `@requires` directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example: @@ -80,9 +74,7 @@ In this case, the Reviews service adds new capabilities to the `User` type by pr #! @external -```graphql definition -directive @external on FIELD_DEFINITION -``` +:::[definition](./federation-v2.0.graphql#@external) The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example: @@ -96,12 +88,53 @@ extend type User @key(fields: "email") { This type extension in the Reviews service extends the `User` type from the Users service. It extends it for the purpose of adding a new field called `reviews`, which returns a list of `Review`s. -#! _FieldSet +#! @shareable + +:::[definition](./federation-v2.0.graphql#@shareable) + +The `@shareable` directive is used to indicate that a field can be resolved by multiple subgraphs. Any subgraph that includes a shareable field can potentially resolve a query for that field. To successfully compose, a field must have the same shareability mode (either shareable or non-shareable) across all subgraphs. -```graphql definition -scalar _FieldSet +Any field using the [`@key` directive](#key) is automatically shareable. Adding the `@shareable` directive to an object is equivalent to marking each field on the object `@shareable`. + +```graphql example -- using {@shareable} +type Product @key(fields: "upc") { + upc: UPC! # shareable because upc is a key field + name: String # non-shareable + description: String @shareable # shareable +} + +type User @key(fields: "email") @shareable { + email: String # shareable because User is marked shareable + name: String # shareable because User is marked shareable +} ``` +#! @override + +:::[definition](./federation-v2.0.graphql#@override) + +The `@override` directive is used to indicate that the current subgraph is taking responsibility for resolving the marked field _away_ from the subgraph specified in the `from` argument. + +The following example will result in all query plans made to resolve `User.name` to be directed to SubgraphB. + +```graphql example -- using {@override} +# in SubgraphA +type User @key(fields: "id") { + id: ID! + name: String +} + +# in SubgraphB +type User @key(fields: "id") { + id: ID! + name: String @override(from: "SubgraphA") +} +``` + +#! _FieldSet + +:::[definition](./federation-v2.0.graphql#@_FieldSet) + A set of fields. ```graphql example -- Using `_FieldSet` with a single field in a `@key` diff --git a/federation/v2.0/index.html b/federation/v2.0/index.html index 91803ee..3cee4bc 100644 --- a/federation/v2.0/index.html +++ b/federation/v2.0/index.html @@ -1060,25 +1060,26 @@

        federation v2.0

      • 2@provides
      • 3@requires
      • 4@external
      • -
      • 5FieldSet
      • +
      • 5@shareable
      • +
      • 6@override
      • +
      • 7_FieldSet
      • 1@key

        -
        directive @key(fields: FieldSet!) repeatable on OBJECT | INTERFACE
        -
        +
        directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE

        The @key directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface.

        -
        Example № 1 using @key
        type Product @key(fields: "upc") {
        +
        Example № 1 using @key
        type Product @key(fields: "upc") {
           upc: UPC!
        -  name: String
        +  name: String
         }
         

        Multiple keys can be defined on a single object type:

        -
        Example № 2 defining multiple @keys
        type Product @key(fields: "upc") @key(fields: "sku") {
        +
        Example № 2 defining multiple @keys
        type Product @key(fields: "upc") @key(fields: "sku") {
           upc: UPC!
           sku: SKU!
        -  name: String
        +  name: String
         }
         
        @@ -1087,73 +1088,111 @@

        2@provides

        -
        directive @provides(fields: FieldSet!) on FIELD_DEFINITION
        -
        +
        directive @provides(fields: FieldSet!) on FIELD_DEFINITION

        The @provides directive is used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. Given the following example:

        -
        Example № 3 using @provides
        type Review @key(fields: "id") {
        -  product: Product @provides(fields: "name")
        +
        Example № 3 using @provides
        type Review @key(fields: "id") {
        +  product: Product @provides(fields: "name")
         }
         
         extend type Product @key(fields: "upc") {
        -  upc: String @external
        -  name: String @external
        +  upc: String @external
        +  name: String @external
         }
         

        When fetching Review.product from the Reviews service, it is possible to request the name with the expectation that the Reviews service can provide it when going from review to product. Product.name is an external field on an external type which is why the local type extension of Product and annotation of name is required. “””

        3@requires

        -
        directive @requires(fields: FieldSet!) on FIELD_DEFINITION
        -
        +
        directive @requires(fields: FieldSet!) on FIELD_DEFINITION

        The @requires directive is used to annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. For example:

        -
        Example № 4 using @requires
        # extended from the Users service
        +
        Example № 4 using @requires
        # extended from the Users service
         extend type User @key(fields: "id") {
        -  id: ID! @external
        -  email: String @external
        -  reviews: [Review] @requires(fields: "email")
        +  id: ID! @external
        +  email: String @external
        +  reviews: [Review] @requires(fields: "email")
         }
         

        In this case, the Reviews service adds new capabilities to the User type by providing a list of reviews related to a user. In order to fetch these reviews, the Reviews service needs to know the email of the User from the Users service in order to look up the reviews. This means the reviews field / resolver requires the email field from the base User type.

        4@external

        -
        directive @external on FIELD_DEFINITION
        -
        +
        directive @external on OBJECT | FIELD_DEFINITION

        The @external directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. For example:

        -
        Example № 5 using @external
        # extended from the Users service
        +
        Example № 5 using @external
        # extended from the Users service
         extend type User @key(fields: "email") {
        -  email: String @external
        -  reviews: [Review]
        +  email: String @external
        +  reviews: [Review]
         }
         

        This type extension in the Reviews service extends the User type from the Users service. It extends it for the purpose of adding a new field called reviews, which returns a list of Reviews.

        -
        -

        5FieldSet

        -
        scalar FieldSet
        +
        +

        5@shareable

        +
        directive @shareable on FIELD_DEFINITION | OBJECT
        +

        The @shareable directive is used to indicate that a field can be resolved by multiple subgraphs. Any subgraph that includes a shareable field can potentially resolve a query for that field. To successfully compose, a field must have the same shareability mode (either shareable or non‐shareable) across all subgraphs.

        +

        Any field using the @key directive is automatically shareable. Adding the @shareable directive to an object is equivalent to marking each field on the object @shareable.

        +
        Example № 6 using @shareable
        type Product @key(fields: "upc") {
        +  upc: UPC!                         # shareable because upc is a key field
        +  name: String                      # non-shareable
        +  description: String @shareable    # shareable
        +}
        +
        +type User @key(fields: "email") @shareable {
        +  email: String                    # shareable because User is marked shareable
        +  name: String                     # shareable because User is marked shareable
        +}
         
        +
        +
        +

        6@override

        +
        directive @override(from: String!) on FIELD_DEFINITION
        +

        The @override directive is used to indicate that the current subgraph is taking responsibility for resolving the marked field away from the subgraph specified in the from argument.

        +

        The following example will result in all query plans made to resolve User.name to be directed to SubgraphB.

        +
        Example № 7 using @override
        # in SubgraphA
        +type User @key(fields: "id") {
        +  id: ID!
        +  name: String
        +}
        +
        +# in SubgraphB
        +type User @key(fields: "id") {
        +  id: ID!
        +  name: String @override(from: "SubgraphA")
        +}
        +
        +
        +
        +

        7_FieldSet

        +
        directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
        +directive @requires(fields: FieldSet!) on FIELD_DEFINITION
        +directive @provides(fields: FieldSet!) on FIELD_DEFINITION
        +directive @external on OBJECT | FIELD_DEFINITION
        +directive @shareable on FIELD_DEFINITION | OBJECT
        +directive @extends on OBJECT | INTERFACE
        +directive @override(from: String!) on FIELD_DEFINITION
        +scalar _FieldSet

        A set of fields.

        -
        Example № 6 Using FieldSet with a single field in a @key
        type User @key(fields: "id") {
        -  id: ID! @external
        +
        Example № 8 Using _FieldSet with a single field in a @key
        type User @key(fields: "id") {
        +  id: ID! @external
         }
         
        -
        Example № 7 Using FieldSet with a multiple fields
        type User @key(fields: "uid realm") {
        -  uid: String
        -  realm: String
        +
        Example № 9 Using _FieldSet with a multiple fields
        type User @key(fields: "uid realm") {
        +  uid: String
        +  realm: String
         }
         

        Deeply nested fields are supported with standard GraphQL syntax

        -
        Example № 8 FieldSet with nested fields
        type User @key(fields: "contact { email }") {
        -  contact: Contact
        +
        Example № 10 _FieldSet with nested fields
        type User @key(fields: "contact { email }") {
        +  contact: Contact
         }
         
         type Contact {
        -  email: String
        +  email: String
         }
         

        Field arguments are not supported.

        -
        Counter Example № 9 FieldSet does not support field arguments
        type User @key(fields: "emails(first: 1)") {
        -  emails(first: Int): [String]
        +
        Counter Example № 11 _FieldSet does not support field arguments
        type User @key(fields: "emails(first: 1)") {
        +  emails(first: Int): [String]
         }
         
        @@ -1168,7 +1207,9 @@

        2@provides
      • 3@requires
      • 4@external
      • -
      • 5FieldSet
      • +
      • 5@shareable
      • +
      • 6@override
      • +
      • 7_FieldSet
      • diff --git a/inaccessible/v0.1/index.html b/inaccessible/v0.1/index.html index 5482806..2cd4880 100644 --- a/inaccessible/v0.1/index.html +++ b/inaccessible/v0.1/index.html @@ -1088,10 +1088,10 @@

        3Example: Sensitive User Data

        This section is non‐normative.

        We’ll refer to this example of a core schema with sensitive user data throughout the document:

        -
        Example № 1 Core schema example
        directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
        -directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
        +
        Example № 1 Core schema example
        directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
        +directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
         
        -enum core__Purpose {
        +enum core__Purpose {
           EXECUTION
           SECURITY
         }
        @@ -1100,31 +1100,31 @@ 

        @core(feature: "https://specs.apollo.dev/core/v0.2") @core(feature: "https://specs.apollo.dev/inaccessible/v0.1", for: SECURITY) { - query: Query + query: Query } type Query { - user(id: String!): User + user(id: String!): User } type User { - id: String! @inaccessible - name: String! - email: String! - bankAccount: BankAccount @inaccessible - accounts: [Account] + id: String! @inaccessible + name: String! + email: String! + bankAccount: BankAccount @inaccessible + accounts: [Account] } type BankAccount @inaccessible { - id: String! - accountNumber: String! + id: String! + accountNumber: String! } type ForumAccount { - handle: String! + handle: String! } -union Account = BankAccount | ForumAccount +union Account = BankAccount | ForumAccount

        The schema above contains both a field (User.id) and type (BankAccount) that are marked as @inaccessible. These symbols should be omitted from the processed schema anywhere they would appear. When the processed schema below is generated from this core schema, notice what has been removed:

        @@ -1134,9 +1134,9 @@

        Example № 2 Core schema after processing
        directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
        +
        Example № 2 Core schema after processing
        directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
         
        -enum core__Purpose {
        +enum core__Purpose {
           EXECUTION
           SECURITY
         }
        @@ -1144,24 +1144,24 @@ 

        schema @core(feature: "https://specs.apollo.dev/core/v0.2") { - query: Query + query: Query } type Query { - user(id: String!): User + user(id: String!): User } type User { - name: String! - email: String! - accounts: [Account] + name: String! + email: String! + accounts: [Account] } type ForumAccount { - handle: String! + handle: String! } -union Account = ForumAccount

        +union Account = ForumAccount

        4Overview

        @@ -1170,16 +1170,16 @@

        5Basic Requirements

        -

        Schemas using @inaccessible must be valid core schema documents and must reference this specification.

        +

        Schemas using @inaccessible must be valid core schema documents and must reference this specification.

        Here is an example @core usage:

        Example № 3 required @core directives
        schema
           @core(feature: "https://specs.apollo.dev/core/v0.2")
           @core(feature: "https://specs.apollo.dev/inaccessible/v0.1") {
        -  query: Query
        +  query: Query
         }

        As described in the core schema specification, your schema may rename the @inaccessible directive by including an as argument to the @core directive which references this specification. All references to @inaccessible in this specification MUST be interpreted as referring to names with the appropriate prefix chosen within your schema.

        In order to use the directive described by this specification, GraphQL requires you to include the definition in your schema.

        -
        directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
        +
        directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION

        5.1Producer Responsibilities

        Producers MUST include a definition of the directive compatible with the above definition and all usages in the document.

        diff --git a/join/v0.1/index.html b/join/v0.1/index.html index 478720c..e20e40a 100644 --- a/join/v0.1/index.html +++ b/join/v0.1/index.html @@ -1062,10 +1062,10 @@

        Join

        This document defines a core schema named join for describing core schemas which join multiple subgraph schemas into a single supergraph schema.

        This specification provides machinery to:

          -
        • define subgraphs with the join__Graph enum and the @join__graph directive
        • -
        • assign fields to subgraphs with the @join__field directive
        • -
        • declare additional data required and provided by subgraph field resolvers with the requires and provides arguments to @join__field
        • -
        • assign keys and ownership to types with the @join__type and @join__owner directives
        • +
        • define subgraphs with the join__Graph enum and the @join__graph directive
        • +
        • assign fields to subgraphs with the @join__field directive
        • +
        • declare additional data required and provided by subgraph field resolvers with the requires and provides arguments to @join__field
        • +
        • assign keys and ownership to types with the @join__type and @join__owner directives