first commit
This commit is contained in:
101
docs/api-stack/api/api-graphql/src/federation/merge-sdl.md
Normal file
101
docs/api-stack/api/api-graphql/src/federation/merge-sdl.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# `merge-sdl`
|
||||
|
||||
## Purpose
|
||||
|
||||
Federate one or more plugin SDL contributions into a single merged schema.
|
||||
This is the engine-agnostic federation step described in design §10.2: each
|
||||
plugin hands the host a `SdlContribution`; the host calls `mergeSdl` to
|
||||
produce the canonical SDL string and field map an engine consumes when
|
||||
building its executable schema.
|
||||
|
||||
The merger has no `graphql.js` dependency — it parses just enough SDL to
|
||||
detect field-level collisions and apply the `graphql.namespace` wrapper.
|
||||
Engines re-parse the result with their own AST machinery; the merger's role
|
||||
is the cross-plugin invariant check.
|
||||
|
||||
## Features
|
||||
|
||||
- Unions `extend type Query { ... }` blocks from multiple plugins.
|
||||
- Detects `Type.field` collisions and raises `GRAPHQL.SCHEMA_COLLISION`
|
||||
with `details.path` and `details.plugins = [A, B]` alphabetically sorted.
|
||||
- Applies the `graphql.namespace` wrapper: `Query`/`Mutation`/`Subscription`
|
||||
extensions land under `<Namespace>Query` etc., with the root operation
|
||||
type extended to expose the wrapper.
|
||||
- Strict mode (`{ strictNamespace: true }`) raises
|
||||
`GRAPHQL.NAMESPACE_REQUIRED` when any plugin omits a namespace.
|
||||
- Returns an immutable `MergedSchema` with the concatenated SDL, the
|
||||
`Type → fields[]` map, and the alphabetically-sorted plugin list.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { mergeSdl } from "@suaveplan/api-graphql/federation";
|
||||
|
||||
const merged = mergeSdl([
|
||||
{
|
||||
plugin: "Billing",
|
||||
typeDefs: ["extend type Query { invoices: [Invoice!]! } type Invoice { id: ID! }"],
|
||||
},
|
||||
{
|
||||
plugin: "Identity",
|
||||
typeDefs: ["extend type Query { users: [User!]! } type User { id: ID! }"],
|
||||
},
|
||||
]);
|
||||
|
||||
console.log(merged.sdl);
|
||||
console.log(merged.typeFields.Query); // ["invoices", "users"]
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Strict mode + namespace wrapping for a multi-tenant host where every plugin
|
||||
must be reachable under its own root field:
|
||||
|
||||
```ts
|
||||
import { mergeSdl, GraphqlError } from "@suaveplan/api-graphql";
|
||||
|
||||
try {
|
||||
mergeSdl(
|
||||
[
|
||||
{
|
||||
plugin: "Billing",
|
||||
namespace: "Billing",
|
||||
typeDefs: ["extend type Query { invoices: [Invoice!]! }"],
|
||||
},
|
||||
{
|
||||
plugin: "Identity",
|
||||
// no namespace — strict mode rejects
|
||||
typeDefs: ["extend type Query { users: [User!]! }"],
|
||||
},
|
||||
],
|
||||
{ strictNamespace: true }
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof GraphqlError) {
|
||||
console.error(e.graphqlCode); // "GRAPHQL.NAMESPACE_REQUIRED"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `mergeSdl(contributions, options?) → MergedSchema` — federation entry
|
||||
point.
|
||||
- `SdlContribution` — `{ plugin, typeDefs, namespace? }`.
|
||||
- `MergedSchema` — `{ sdl, typeFields, plugins }`.
|
||||
- `MergeOptions` — `{ strictNamespace? }`.
|
||||
- `SchemaCollisionDetail` — error-detail shape for the collision code.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The collision detector tracks the first plugin that registered each
|
||||
`Type.field` and raises on the second non-self registration. Repeated
|
||||
declarations from the same plugin are tolerated (the federation step is
|
||||
re-runnable). Sorting the plugin pair before raising keeps error messages
|
||||
deterministic in tests.
|
||||
|
||||
`extend` vs concrete `type` is recorded but not enforced by the merger
|
||||
itself — engines reject schemas with multiple concrete declarations of the
|
||||
same type at schema-build time. The merger's invariant is field-level: two
|
||||
plugins MAY both add fields to `Query` (via `extend`), but they MUST NOT
|
||||
add the same field name.
|
||||
Reference in New Issue
Block a user