84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
import {
|
|
type GraphqlRegistry,
|
|
type MergedSchema,
|
|
mergeSdl,
|
|
type ResolverMap,
|
|
type SdlContribution,
|
|
} from "@suaveplan/api-graphql";
|
|
|
|
/**
|
|
* Aggregate one or more plugin registries into a single `MergedSchema` +
|
|
* combined resolver map. The Yoga engine accepts a *flat* list of
|
|
* `(registry, plugin, namespace?)` triples — one per contributing plugin —
|
|
* and produces:
|
|
*
|
|
* - `merged` — output of `mergeSdl(...)` (raises
|
|
* `GRAPHQL.SCHEMA_COLLISION` on cross-plugin field collisions).
|
|
* - `resolvers` — combined `ResolverMap` (Type → field → resolver) where
|
|
* each `Type` block is a shallow object-spread of every contributing
|
|
* plugin's block. Within a single Type, fields contributed by the same
|
|
* plugin via multiple `addResolvers` calls layer in order.
|
|
*/
|
|
export interface PluginRegistryEntry {
|
|
readonly plugin: string;
|
|
readonly registry: FreezableRegistry;
|
|
readonly namespace?: string;
|
|
}
|
|
|
|
type FreezableRegistry = GraphqlRegistry & {
|
|
readonly freeze: () => {
|
|
readonly typeDefs: ReadonlyArray<string>;
|
|
readonly resolvers: ReadonlyArray<ResolverMap>;
|
|
};
|
|
};
|
|
|
|
export interface FederationResult {
|
|
readonly merged: MergedSchema;
|
|
readonly resolvers: Record<string, Record<string, unknown>>;
|
|
}
|
|
|
|
/**
|
|
* Merge plugin contributions into a single (sdl, resolvers) pair ready for
|
|
* `makeExecutableSchema`. Collisions in resolvers across plugins are
|
|
* surfaced by the SDL collision detection step that runs first — by the
|
|
* time control reaches resolver merging, every `(Type, field)` pair is
|
|
* single-plugin-owned.
|
|
*/
|
|
export function federatePlugins(
|
|
entries: ReadonlyArray<PluginRegistryEntry>
|
|
): FederationResult {
|
|
const contributions: SdlContribution[] = [];
|
|
const frozens: ReadonlyArray<ResolverMap>[] = [];
|
|
|
|
for (const entry of entries) {
|
|
const snap = entry.registry.freeze();
|
|
const contribution: SdlContribution =
|
|
entry.namespace !== undefined
|
|
? {
|
|
plugin: entry.plugin,
|
|
typeDefs: snap.typeDefs,
|
|
namespace: entry.namespace,
|
|
}
|
|
: { plugin: entry.plugin, typeDefs: snap.typeDefs };
|
|
contributions.push(contribution);
|
|
frozens.push(snap.resolvers);
|
|
}
|
|
|
|
const merged = mergeSdl(contributions);
|
|
|
|
const combined: Record<string, Record<string, unknown>> = {};
|
|
for (const maps of frozens) {
|
|
for (const map of maps) {
|
|
for (const typeName of Object.keys(map)) {
|
|
const incoming = (map as Record<string, unknown>)[
|
|
typeName
|
|
] as Record<string, unknown>;
|
|
const existing = combined[typeName] ?? {};
|
|
combined[typeName] = { ...existing, ...incoming };
|
|
}
|
|
}
|
|
}
|
|
|
|
return { merged, resolvers: combined };
|
|
}
|