Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@apollo/federation

Package Overview
Dependencies
Maintainers
1
Versions
144
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@apollo/federation - npm Package Compare versions

Comparing version 0.23.0 to 0.23.1

dist/utilities/assert.d.ts

4

CHANGELOG.md

@@ -7,2 +7,6 @@ # CHANGELOG for `@apollo/federation`

- This change is mostly a set of follow-up changes for PR #622. Most of these changes are internal (renaming, etc.). Some noteworthy changes worth mentioning are: a switch to graphql-js's `stripIgnoredCharacters` during field set printing, an update to the `join__Enum` generation algorithm, and some additional assertions. [PR #656](https://github.com/apollographql/federation/pull/656)
## v0.23.0
- __BREAKING__ - Update CSDL to the new core schema format, implementing the currently-being-introduced core and join specs. `composeAndValidate` now returns `supergraphSdl` in the new format instead of `composedSdl` in the previous CSDL format. [PR #622](https://github.com/apollographql/federation/pull/622)

@@ -9,0 +13,0 @@ ## v0.22.0

3

dist/composition/compose.js

@@ -14,2 +14,3 @@ "use strict";

const printSupergraphSdl_1 = require("../service/printSupergraphSdl");
const utilities_1 = require("../utilities");
const EmptyQueryDefinition = {

@@ -322,3 +323,3 @@ kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,

...schema.toConfig(),
...utils_1.mapValues(utils_1.defaultRootOperationNameLookup, typeName => typeName
...utilities_1.mapValues(utils_1.defaultRootOperationNameLookup, typeName => typeName
? schema.getType(typeName)

@@ -325,0 +326,0 @@ : undefined),

@@ -13,3 +13,3 @@ import { FieldDefinitionNode, StringValueNode, NameNode, DocumentNode, DirectiveNode, GraphQLNamedType, GraphQLError, GraphQLSchema, GraphQLObjectType, GraphQLField, SelectionNode, TypeDefinitionNode, TypeExtensionNode, ASTNode, DirectiveDefinitionNode, GraphQLDirective, OperationTypeNode, SchemaDefinitionNode } from 'graphql';

export declare function stripTypeSystemDirectivesFromTypeDefs(typeDefs: DocumentNode): DocumentNode;
export declare function parseSelections(source: string): readonly SelectionNode[];
export declare function parseSelections(source: string): ReadonlyArray<SelectionNode>;
export declare function hasMatchingFieldInDirectives({ directives, fieldNameToMatch, namedType, }: {

@@ -56,4 +56,2 @@ directives: DirectiveNode[];

};
export declare function mapValues<T, U = T>(object: Record<string, T>, callback: (value: T) => U): Record<string, U>;
export declare function isNotNullOrUndefined<T>(value: T | null | undefined): value is T;
export declare const executableDirectiveLocations: string[];

@@ -60,0 +58,0 @@ export declare function isFederationDirective(directive: GraphQLDirective): boolean;

@@ -6,5 +6,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.getFederationMetadata = exports.assertCompositionFailure = exports.assertCompositionSuccess = exports.compositionHasErrors = exports.defaultRootOperationNameLookup = exports.reservedRootFields = exports.isFederationDirective = exports.executableDirectiveLocations = exports.isNotNullOrUndefined = exports.mapValues = exports.defKindToExtKind = exports.typeNodesAreEquivalent = exports.diffTypeNodes = exports.isTypeNodeAnEntity = exports.selectionIncludesField = exports.findFieldsThatReturnType = exports.findTypesContainingFieldWithReturnType = exports.errorWithCode = exports.logDirective = exports.logServiceAndType = exports.hasMatchingFieldInDirectives = exports.parseSelections = exports.stripTypeSystemDirectivesFromTypeDefs = exports.stripExternalFieldsFromTypeDefs = exports.findDirectivesOnNode = exports.mapFieldNamesToServiceName = exports.isStringValueNode = void 0;
exports.getFederationMetadata = exports.assertCompositionFailure = exports.assertCompositionSuccess = exports.compositionHasErrors = exports.defaultRootOperationNameLookup = exports.reservedRootFields = exports.isFederationDirective = exports.executableDirectiveLocations = exports.defKindToExtKind = exports.typeNodesAreEquivalent = exports.diffTypeNodes = exports.isTypeNodeAnEntity = exports.selectionIncludesField = exports.findFieldsThatReturnType = exports.findTypesContainingFieldWithReturnType = exports.errorWithCode = exports.logDirective = exports.logServiceAndType = exports.hasMatchingFieldInDirectives = exports.parseSelections = exports.stripTypeSystemDirectivesFromTypeDefs = exports.stripExternalFieldsFromTypeDefs = exports.findDirectivesOnNode = exports.mapFieldNamesToServiceName = exports.isStringValueNode = void 0;
const graphql_1 = require("graphql");
const directives_1 = __importDefault(require("../directives"));
const utilities_1 = require("../utilities");
function isStringValueNode(node) {

@@ -72,4 +73,6 @@ return node.kind === graphql_1.Kind.STRING;

function parseSelections(source) {
return graphql_1.parse(`query { ${source} }`)
.definitions[0].selectionSet.selections;
const parsed = graphql_1.parse(`{${source}}`);
utilities_1.assert(parsed.definitions.length === 1, `Unexpected } found in FieldSet`);
return parsed.definitions[0].selectionSet
.selections;
}

@@ -87,3 +90,3 @@ exports.parseSelections = parseSelections;

: null)
.filter(isNotNullOrUndefined)
.filter(utilities_1.isNotNullOrUndefined)
.flatMap(selection => parseSelections(selection.keyArgument))

@@ -304,14 +307,2 @@ .some(field => field.kind === graphql_1.Kind.FIELD && field.name.value === fieldNameToMatch));

};
function mapValues(object, callback) {
const result = Object.create(null);
for (const [key, value] of Object.entries(object)) {
result[key] = callback(value);
}
return result;
}
exports.mapValues = mapValues;
function isNotNullOrUndefined(value) {
return value !== null && typeof value !== 'undefined';
}
exports.isNotNullOrUndefined = isNotNullOrUndefined;
exports.executableDirectiveLocations = [

@@ -318,0 +309,0 @@ 'QUERY',

@@ -6,3 +6,2 @@ "use strict";

const utils_1 = require("../../utils");
const service_1 = require("../../../service");
const keysMatchBaseService = function ({ schema, }) {

@@ -49,4 +48,6 @@ const errors = [];

function printFieldSet(selections) {
return selections.map(service_1.printWithReducedWhitespace).join(' ');
return selections
.map((selection) => graphql_1.stripIgnoredCharacters(graphql_1.print(selection)))
.join(' ');
}
//# sourceMappingURL=keysMatchBaseService.js.map

@@ -8,2 +8,3 @@ "use strict";

const utils_1 = require("../../utils");
const utilities_1 = require("../../../utilities");
const keyFieldsMissingExternal = ({ name: serviceName, typeDefs, }) => {

@@ -23,3 +24,3 @@ const errors = [];

: null)
.filter(utils_1.isNotNullOrUndefined);
.filter(utilities_1.isNotNullOrUndefined);
keyDirectiveInfoOnTypeExtensions.push(...keyDirectivesInfo);

@@ -26,0 +27,0 @@ },

import { GraphQLDirective, GraphQLEnumType, GraphQLScalarType } from 'graphql';
import { ServiceDefinition } from './composition';
export declare function getJoins(serviceList: ServiceDefinition[]): {
sanitizedServiceNames: Record<string, string>;
export declare function getJoinDefinitions(serviceList: ServiceDefinition[]): {
graphNameToEnumValueName: {
[k: string]: string;
};
FieldSetScalar: GraphQLScalarType;

@@ -6,0 +8,0 @@ JoinTypeDirective: GraphQLDirective;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getJoins = void 0;
exports.getJoinDefinitions = void 0;
const graphql_1 = require("graphql");
const mapGetOrSet_1 = require("./utilities/mapGetOrSet");
const FieldSetScalar = new graphql_1.GraphQLScalarType({

@@ -21,33 +22,40 @@ name: 'join__FieldSet',

function getJoinGraphEnum(serviceList) {
const nameMap = new Map();
const sanitizedServiceNames = Object.create(null);
function uniquifyAndSanitizeGraphQLName(name) {
const alphaNumericUnderscoreOnly = name.replace(/[^_a-zA-Z0-9]/g, '_');
const noNumericFirstChar = alphaNumericUnderscoreOnly.match(/^[0-9]/)
const sortedServiceList = serviceList
.slice()
.sort((a, b) => a.name.localeCompare(b.name));
function sanitizeGraphQLName(name) {
const alphaNumericUnderscoreOnly = name.replace(/[\W]/g, '_');
const noNumericFirstChar = alphaNumericUnderscoreOnly.match(/^\d/)
? '_' + alphaNumericUnderscoreOnly
: alphaNumericUnderscoreOnly;
const noUnderscoreNumericEnding = noNumericFirstChar.match(/_[0-9]+$/)
const noUnderscoreNumericEnding = noNumericFirstChar.match(/_\d+$/)
? noNumericFirstChar + '_'
: noNumericFirstChar;
const toUpper = noUnderscoreNumericEnding.toLocaleUpperCase();
const nameCount = nameMap.get(toUpper);
if (nameCount) {
nameMap.set(toUpper, nameCount + 1);
const uniquified = `${toUpper}_${nameCount + 1}`;
nameMap.set(uniquified, 1);
sanitizedServiceNames[name] = uniquified;
return uniquified;
return toUpper;
}
const sanitizedNameToServiceDefinitions = new Map();
for (const service of sortedServiceList) {
const { name } = service;
const sanitized = sanitizeGraphQLName(name);
mapGetOrSet_1.mapGetOrSet(sanitizedNameToServiceDefinitions, sanitized, []).push(service);
}
const enumValueNameToServiceDefinition = Object.create(null);
for (const [sanitizedName, services] of sanitizedNameToServiceDefinitions) {
if (services.length === 1) {
enumValueNameToServiceDefinition[sanitizedName] = services[0];
}
else {
nameMap.set(toUpper, 1);
sanitizedServiceNames[name] = toUpper;
return toUpper;
for (const [index, service] of services.entries()) {
enumValueNameToServiceDefinition[`${sanitizedName}_${index + 1}`] = service;
}
}
}
const entries = Object.entries(enumValueNameToServiceDefinition);
return {
sanitizedServiceNames,
graphNameToEnumValueName: Object.fromEntries(entries.map(([enumValueName, service]) => [service.name, enumValueName])),
JoinGraphEnum: new graphql_1.GraphQLEnumType({
name: 'join__Graph',
values: Object.fromEntries(serviceList.map((service) => [
uniquifyAndSanitizeGraphQLName(service.name),
values: Object.fromEntries(entries.map(([enumValueName, service]) => [
enumValueName,
{ value: service },

@@ -86,4 +94,4 @@ ])),

}
function getJoins(serviceList) {
const { sanitizedServiceNames, JoinGraphEnum } = getJoinGraphEnum(serviceList);
function getJoinDefinitions(serviceList) {
const { graphNameToEnumValueName, JoinGraphEnum } = getJoinGraphEnum(serviceList);
const JoinFieldDirective = getJoinFieldDirective(JoinGraphEnum);

@@ -105,3 +113,3 @@ const JoinOwnerDirective = getJoinOwnerDirective(JoinGraphEnum);

return {
sanitizedServiceNames,
graphNameToEnumValueName,
FieldSetScalar,

@@ -115,3 +123,3 @@ JoinTypeDirective,

}
exports.getJoins = getJoins;
exports.getJoinDefinitions = getJoinDefinitions;
//# sourceMappingURL=joinSpec.js.map

@@ -1,2 +0,2 @@

import { GraphQLSchema, GraphQLNamedType, ASTNode } from 'graphql';
import { GraphQLSchema, GraphQLNamedType } from 'graphql';
declare type Options = {

@@ -8,5 +8,4 @@ commentDescriptions?: boolean;

export declare function printType(type: GraphQLNamedType, options?: Options): string;
export declare function printWithReducedWhitespace(ast: ASTNode): string;
export declare function printBlockString(value: string, indentation?: string, preferMultipleLines?: boolean): string;
export {};
//# sourceMappingURL=printFederatedSchema.d.ts.map

@@ -22,3 +22,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.printBlockString = exports.printWithReducedWhitespace = exports.printType = exports.printIntrospectionSchema = exports.printSchema = void 0;
exports.printBlockString = exports.printType = exports.printIntrospectionSchema = exports.printSchema = void 0;
const graphql_1 = require("graphql");

@@ -170,8 +170,2 @@ const types_1 = require("../types");

}
function printWithReducedWhitespace(ast) {
return graphql_1.print(ast)
.replace(/\s+/g, ' ')
.trim();
}
exports.printWithReducedWhitespace = printWithReducedWhitespace;
function printBlock(items) {

@@ -178,0 +172,0 @@ return items.length !== 0 ? ' {\n' + items.join('\n') + '\n}' : '';

@@ -1,2 +0,2 @@

import { GraphQLSchema, GraphQLNamedType, ASTNode } from 'graphql';
import { GraphQLSchema, GraphQLNamedType } from 'graphql';
import { ServiceDefinition } from '../composition';

@@ -7,3 +7,3 @@ declare type Options = {

interface PrintingContext {
sanitizedServiceNames?: Record<string, string>;
graphNameToEnumValueName?: Record<string, string>;
}

@@ -13,5 +13,4 @@ export declare function printSupergraphSdl(schema: GraphQLSchema, serviceList: ServiceDefinition[], options?: Options): string;

export declare function printType(type: GraphQLNamedType, context: PrintingContext, options?: Options): string;
export declare function printWithReducedWhitespace(ast: ASTNode): string;
export declare function printBlockString(value: string, indentation?: string, preferMultipleLines?: boolean): string;
export {};
//# sourceMappingURL=printSupergraphSdl.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.printBlockString = exports.printWithReducedWhitespace = exports.printType = exports.printIntrospectionSchema = exports.printSupergraphSdl = void 0;
exports.printBlockString = exports.printType = exports.printIntrospectionSchema = exports.printSupergraphSdl = void 0;
const graphql_1 = require("graphql");
const utilities_1 = require("../utilities");
const coreSpec_1 = require("../coreSpec");

@@ -9,3 +10,3 @@ const joinSpec_1 = require("../joinSpec");

const config = schema.toConfig();
const { FieldSetScalar, JoinFieldDirective, JoinTypeDirective, JoinOwnerDirective, JoinGraphEnum, JoinGraphDirective, sanitizedServiceNames, } = joinSpec_1.getJoins(serviceList);
const { FieldSetScalar, JoinFieldDirective, JoinTypeDirective, JoinOwnerDirective, JoinGraphEnum, JoinGraphDirective, graphNameToEnumValueName, } = joinSpec_1.getJoinDefinitions(serviceList);
schema = new graphql_1.GraphQLSchema({

@@ -24,3 +25,3 @@ ...config,

const context = {
sanitizedServiceNames,
graphNameToEnumValueName,
};

@@ -108,3 +109,3 @@ return printFilteredSchema(schema, (n) => !graphql_1.isSpecifiedDirective(n), isDefinedType, context, options);

function printTypeJoinDirectives(type, context) {
var _a, _b, _c;
var _a, _b;
const metadata = (_a = type.extensions) === null || _a === void 0 ? void 0 : _a.federation;

@@ -123,4 +124,6 @@ if (!metadata)

const shouldPrintOwner = graphql_1.isObjectType(type);
const ownerGraphEnumValue = (_b = context.graphNameToEnumValueName) === null || _b === void 0 ? void 0 : _b[ownerService];
utilities_1.assert(ownerGraphEnumValue, `Unexpected enum value missing for subgraph ${ownerService}`);
const joinOwnerString = shouldPrintOwner
? `\n @join__owner(graph: ${(_c = (_b = context.sanitizedServiceNames) === null || _b === void 0 ? void 0 : _b[ownerService]) !== null && _c !== void 0 ? _c : ownerService})`
? `\n @join__owner(graph: ${ownerGraphEnumValue})`
: '';

@@ -131,4 +134,6 @@ return (joinOwnerString +

.map((selections) => {
var _a, _b;
return `\n @join__type(graph: ${(_b = (_a = context.sanitizedServiceNames) === null || _a === void 0 ? void 0 : _a[service]) !== null && _b !== void 0 ? _b : service}, key: ${printStringLiteral(printFieldSet(selections))})`;
var _a;
const typeGraphEnumValue = (_a = context.graphNameToEnumValueName) === null || _a === void 0 ? void 0 : _a[service];
utilities_1.assert(typeGraphEnumValue, `Unexpected enum value missing for subgraph ${service}`);
return `\n @join__type(graph: ${typeGraphEnumValue}, key: ${printStringLiteral(printFieldSet(selections))})`;
})

@@ -183,19 +188,16 @@ .join(''))

}
function printWithReducedWhitespace(ast) {
return graphql_1.print(ast)
.replace(/\s+/g, ' ')
.trim();
}
exports.printWithReducedWhitespace = printWithReducedWhitespace;
function printFieldSet(selections) {
return `${selections.map(printWithReducedWhitespace).join(' ')}`;
return selections
.map((selection) => graphql_1.stripIgnoredCharacters(graphql_1.print(selection)))
.join(' ');
}
function printJoinFieldDirectives(field, parentType, context) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
var _a, _b, _c, _d, _e;
let printed = ' @join__field(';
if (!((_a = field.extensions) === null || _a === void 0 ? void 0 : _a.federation)) {
if (((_c = (_b = parentType.extensions) === null || _b === void 0 ? void 0 : _b.federation) === null || _c === void 0 ? void 0 : _c.serviceName) &&
((_e = (_d = parentType.extensions) === null || _d === void 0 ? void 0 : _d.federation) === null || _e === void 0 ? void 0 : _e.keys)) {
return (printed +
`graph: ${(_h = (_f = context.sanitizedServiceNames) === null || _f === void 0 ? void 0 : _f[(_g = parentType.extensions) === null || _g === void 0 ? void 0 : _g.federation.serviceName]) !== null && _h !== void 0 ? _h : (_j = parentType.extensions) === null || _j === void 0 ? void 0 : _j.federation.serviceName})`);
const { serviceName, keys } = (_b = parentType.extensions) === null || _b === void 0 ? void 0 : _b.federation;
if (serviceName && keys) {
const enumValue = (_c = context.graphNameToEnumValueName) === null || _c === void 0 ? void 0 : _c[serviceName];
utilities_1.assert(enumValue, `Unexpected enum value missing for subgraph ${serviceName}`);
return printed + `graph: ${enumValue})`;
}

@@ -207,3 +209,3 @@ return '';

if (serviceName && serviceName.length > 0) {
directiveArgs.push(`graph: ${(_l = (_k = context.sanitizedServiceNames) === null || _k === void 0 ? void 0 : _k[serviceName]) !== null && _l !== void 0 ? _l : serviceName}`);
directiveArgs.push(`graph: ${(_e = (_d = context.graphNameToEnumValueName) === null || _d === void 0 ? void 0 : _d[serviceName]) !== null && _e !== void 0 ? _e : serviceName}`);
}

@@ -210,0 +212,0 @@ if (requires.length > 0) {

{
"name": "@apollo/federation",
"version": "0.23.0",
"version": "0.23.1",
"description": "Apollo Federation Utilities",

@@ -26,3 +26,3 @@ "main": "dist/index.js",

"dependencies": {
"apollo-graphql": "^0.6.0",
"apollo-graphql": "^0.9.0",
"lodash.xorby": "^4.7.0"

@@ -33,3 +33,3 @@ },

},
"gitHead": "8365ec404e6cdd6ef1f789be82fc523f5daed440"
"gitHead": "a69e377cdd5bc56f350bac227f3cc84ee5f9d777"
}
import { fixtures } from 'apollo-federation-integration-testsuite';
import { getJoins } from "../joinSpec";
import { getJoinDefinitions } from "../joinSpec";

@@ -20,3 +20,3 @@ const questionableNamesRemap = {

it('correctly uniquifies and sanitizes service names', () => {
const { sanitizedServiceNames } = getJoins(
const { graphNameToEnumValueName } = getJoinDefinitions(
fixturesWithQuestionableServiceNames,

@@ -31,3 +31,3 @@ );

* 4. Names are uppercased (all)
* 5. After transformations 1-4, duplicates are suffixed with _{n} where {n} is number of times we've seen the dupe (ServiceA + serviceA, servicea_2 + servicea_2_)
* 5. After transformations 1-5, duplicates are suffixed with _{n} where {n} is number of times we've seen the dupe (ServiceA + serviceA, servicea_2 + servicea_2_)
*

@@ -38,8 +38,8 @@ * Miscellany

*/
expect(sanitizedServiceNames).toMatchObject({
expect(graphNameToEnumValueName).toMatchObject({
'9product*!': '_9PRODUCT__',
ServiceA: 'SERVICEA',
ServiceA: 'SERVICEA_2',
reviews_9: 'REVIEWS_9_',
serviceA: 'SERVICEA_2',
servicea_2: 'SERVICEA_2_',
serviceA: 'SERVICEA_1',
servicea_2: 'SERVICEA_2__1',
servicea_2_: 'SERVICEA_2__2',

@@ -46,0 +46,0 @@ });

@@ -30,3 +30,2 @@ import {

typeNodesAreEquivalent,
mapValues,
isFederationDirective,

@@ -50,2 +49,3 @@ executableDirectiveLocations,

import { printSupergraphSdl } from '../service/printSupergraphSdl';
import { mapValues } from '../utilities';

@@ -52,0 +52,0 @@ const EmptyQueryDefinition = {

@@ -45,2 +45,3 @@ import {

import federationDirectives from '../directives';
import { assert, isNotNullOrUndefined } from '../utilities';

@@ -151,5 +152,15 @@ export function isStringValueNode(node: any): node is StringValueNode {

export function parseSelections(source: string) {
return (parse(`query { ${source} }`)
.definitions[0] as OperationDefinitionNode).selectionSet.selections;
/**
* For lack of a "home of federation utilities", this function is copy/pasted
* verbatim across the federation, gateway, and query-planner packages. Any changes
* made here should be reflected in the other two locations as well.
*
* @param source A string representing a FieldSet
* @returns A parsed FieldSet
*/
export function parseSelections(source: string): ReadonlyArray<SelectionNode> {
const parsed = parse(`{${source}}`);
assert(parsed.definitions.length === 1, `Unexpected } found in FieldSet`);
return (parsed.definitions[0] as OperationDefinitionNode).selectionSet
.selections;
}

@@ -561,22 +572,2 @@

// Transform an object's values via a callback function
export function mapValues<T, U = T>(
object: Record<string, T>,
callback: (value: T) => U,
): Record<string, U> {
const result: Record<string, U> = Object.create(null);
for (const [key, value] of Object.entries(object)) {
result[key] = callback(value);
}
return result;
}
export function isNotNullOrUndefined<T>(
value: T | null | undefined,
): value is T {
return value !== null && typeof value !== 'undefined';
}
export const executableDirectiveLocations = [

@@ -583,0 +574,0 @@ 'QUERY',

@@ -1,3 +0,9 @@

import { isObjectType, GraphQLError, SelectionNode } from 'graphql';
import {
isObjectType,
GraphQLError,
SelectionNode,
stripIgnoredCharacters,
print,
} from 'graphql';
import {
logServiceAndType,

@@ -8,3 +14,2 @@ errorWithCode,

import { PostCompositionValidator } from '.';
import { printWithReducedWhitespace } from '../../../service';

@@ -86,3 +91,5 @@ /**

function printFieldSet(selections: readonly SelectionNode[]): string {
return selections.map(printWithReducedWhitespace).join(' ');
return selections
.map((selection) => stripIgnoredCharacters(print(selection)))
.join(' ');
}

@@ -18,4 +18,4 @@ import {

errorWithCode,
isNotNullOrUndefined
} from '../../utils';
import { isNotNullOrUndefined } from '../../../utilities';

@@ -22,0 +22,0 @@ /**

@@ -10,2 +10,3 @@ import {

import { ServiceDefinition } from './composition';
import { mapGetOrSet } from './utilities/mapGetOrSet';

@@ -31,7 +32,8 @@ const FieldSetScalar = new GraphQLScalarType({

* Expectations
* 1. Non-Alphanumeric characters are replaced with _ (alphaNumericUnderscoreOnly)
* 2. Numeric first characters are prefixed with _ (noNumericFirstChar)
* 3. Names ending in an underscore followed by numbers `_\d+` are suffixed with _ (noUnderscoreNumericEnding)
* 4. Names are uppercased (toUpper)
* 5. After transformations 1-4, duplicates are suffixed with _{n} where {n} is number of times we've seen the dupe
* 1. The input is first sorted using `String.localeCompare`, so the output is deterministic
* 2. Non-Alphanumeric characters are replaced with _ (alphaNumericUnderscoreOnly)
* 3. Numeric first characters are prefixed with _ (noNumericFirstChar)
* 4. Names ending in an underscore followed by numbers `_\d+` are suffixed with _ (noUnderscoreNumericEnding)
* 5. Names are uppercased (toUpper)
* 6. After transformations 1-5, duplicates are suffixed with _{n} where {n} is number of times we've seen the dupe
*

@@ -41,14 +43,15 @@ * Note: Collisions with name's we've generated are also accounted for

function getJoinGraphEnum(serviceList: ServiceDefinition[]) {
// Track whether we've seen a name and how many times
const nameMap: Map<string, number> = new Map();
// Build a map of original service name to generated name
const sanitizedServiceNames: Record<string, string> = Object.create(null);
const sortedServiceList = serviceList
.slice()
.sort((a, b) => a.name.localeCompare(b.name));
function uniquifyAndSanitizeGraphQLName(name: string) {
// Transforms to ensure valid graphql `Name`
const alphaNumericUnderscoreOnly = name.replace(/[^_a-zA-Z0-9]/g, '_');
const noNumericFirstChar = alphaNumericUnderscoreOnly.match(/^[0-9]/)
function sanitizeGraphQLName(name: string) {
// replace all non-word characters (\W). Word chars are _a-zA-Z0-9
const alphaNumericUnderscoreOnly = name.replace(/[\W]/g, '_');
// prefix a digit in the first position with an _
const noNumericFirstChar = alphaNumericUnderscoreOnly.match(/^\d/)
? '_' + alphaNumericUnderscoreOnly
: alphaNumericUnderscoreOnly;
const noUnderscoreNumericEnding = noNumericFirstChar.match(/_[0-9]+$/)
// suffix an underscore + digit in the last position with an _
const noUnderscoreNumericEnding = noNumericFirstChar.match(/_\d+$/)
? noNumericFirstChar + '_'

@@ -59,27 +62,45 @@ : noNumericFirstChar;

const toUpper = noUnderscoreNumericEnding.toLocaleUpperCase();
return toUpper;
}
// Uniquifying post-transform
const nameCount = nameMap.get(toUpper);
if (nameCount) {
// Collision - bump counter by one
nameMap.set(toUpper, nameCount + 1);
const uniquified = `${toUpper}_${nameCount + 1}`;
// We also now need another entry for the name we just generated
nameMap.set(uniquified, 1);
sanitizedServiceNames[name] = uniquified;
return uniquified;
// duplicate enum values can occur due to sanitization and must be accounted for
// collect the duplicates in an array so we can uniquify them in a second pass.
const sanitizedNameToServiceDefinitions: Map<
string,
ServiceDefinition[]
> = new Map();
for (const service of sortedServiceList) {
const { name } = service;
const sanitized = sanitizeGraphQLName(name);
mapGetOrSet(sanitizedNameToServiceDefinitions, sanitized, []).push(service);
}
// if no duplicates for a given name, add it as is
// if duplicates exist, append _{n} (index-1) to each duplicate in the array
const enumValueNameToServiceDefinition: Record<
string,
ServiceDefinition
> = Object.create(null);
for (const [sanitizedName, services] of sanitizedNameToServiceDefinitions) {
if (services.length === 1) {
enumValueNameToServiceDefinition[sanitizedName] = services[0];
} else {
nameMap.set(toUpper, 1);
sanitizedServiceNames[name] = toUpper;
return toUpper;
for (const [index, service] of services.entries()) {
enumValueNameToServiceDefinition[
`${sanitizedName}_${index + 1}`
] = service;
}
}
}
const entries = Object.entries(enumValueNameToServiceDefinition);
return {
sanitizedServiceNames,
graphNameToEnumValueName: Object.fromEntries(
entries.map(([enumValueName, service]) => [service.name, enumValueName]),
),
JoinGraphEnum: new GraphQLEnumType({
name: 'join__Graph',
values: Object.fromEntries(
serviceList.map((service) => [
uniquifyAndSanitizeGraphQLName(service.name),
entries.map(([enumValueName, service]) => [
enumValueName,
{ value: service },

@@ -122,4 +143,4 @@ ]),

export function getJoins(serviceList: ServiceDefinition[]) {
const { sanitizedServiceNames, JoinGraphEnum } = getJoinGraphEnum(serviceList);
export function getJoinDefinitions(serviceList: ServiceDefinition[]) {
const { graphNameToEnumValueName, JoinGraphEnum } = getJoinGraphEnum(serviceList);
const JoinFieldDirective = getJoinFieldDirective(JoinGraphEnum);

@@ -143,3 +164,3 @@ const JoinOwnerDirective = getJoinOwnerDirective(JoinGraphEnum);

return {
sanitizedServiceNames,
graphNameToEnumValueName,
FieldSetScalar,

@@ -146,0 +167,0 @@ JoinTypeDirective,

@@ -78,3 +78,3 @@ import { fixtures } from 'apollo-federation-integration-testsuite';

reviews: [Review] @join__field(graph: REVIEWS)
relatedReviews: [Review!]! @join__field(graph: REVIEWS, requires: \\"similarBooks { isbn }\\")
relatedReviews: [Review!]! @join__field(graph: REVIEWS, requires: \\"similarBooks{isbn}\\")
}

@@ -255,3 +255,3 @@

@join__type(graph: ACCOUNTS, key: \\"id\\")
@join__type(graph: ACCOUNTS, key: \\"username name { first last }\\")
@join__type(graph: ACCOUNTS, key: \\"username name{first last}\\")
@join__type(graph: INVENTORY, key: \\"id\\")

@@ -267,3 +267,3 @@ @join__type(graph: PRODUCT, key: \\"id\\")

metadata: [UserMetadata] @join__field(graph: ACCOUNTS)
goodDescription: Boolean @join__field(graph: INVENTORY, requires: \\"metadata { description }\\")
goodDescription: Boolean @join__field(graph: INVENTORY, requires: \\"metadata{description}\\")
vehicle: Vehicle @join__field(graph: PRODUCT)

@@ -273,3 +273,3 @@ thing: Thing @join__field(graph: PRODUCT)

numberOfReviews: Int! @join__field(graph: REVIEWS)
goodAddress: Boolean @join__field(graph: REVIEWS, requires: \\"metadata { address }\\")
goodAddress: Boolean @join__field(graph: REVIEWS, requires: \\"metadata{address}\\")
}

@@ -276,0 +276,0 @@

@@ -34,3 +34,2 @@ /**

DEFAULT_DEPRECATION_REASON,
ASTNode,
} from 'graphql';

@@ -309,8 +308,2 @@ import { Maybe } from '../composition';

export function printWithReducedWhitespace(ast: ASTNode): string {
return print(ast)
.replace(/\s+/g, ' ')
.trim();
}
function printBlock(items: string[]) {

@@ -317,0 +310,0 @@ return items.length !== 0 ? ' {\n' + items.join('\n') + '\n}' : '';

@@ -28,8 +28,9 @@ import {

DEFAULT_DEPRECATION_REASON,
ASTNode,
SelectionNode,
stripIgnoredCharacters,
} from 'graphql';
import { Maybe, FederationType, FederationField, ServiceDefinition } from '../composition';
import { assert } from '../utilities';
import { CoreDirective } from '../coreSpec';
import { getJoins } from '../joinSpec';
import { getJoinDefinitions } from '../joinSpec';

@@ -51,3 +52,3 @@ type Options = {

// sanitized / uniquified enum value `Name` from the `join__Graph` enum
sanitizedServiceNames?: Record<string, string>;
graphNameToEnumValueName?: Record<string, string>;
}

@@ -77,4 +78,4 @@

JoinGraphDirective,
sanitizedServiceNames,
} = getJoins(serviceList);
graphNameToEnumValueName,
} = getJoinDefinitions(serviceList);

@@ -95,3 +96,3 @@ schema = new GraphQLSchema({

const context: PrintingContext = {
sanitizedServiceNames,
graphNameToEnumValueName,
}

@@ -253,6 +254,10 @@

const shouldPrintOwner = isObjectType(type);
const ownerGraphEnumValue = context.graphNameToEnumValueName?.[ownerService];
assert(
ownerGraphEnumValue,
`Unexpected enum value missing for subgraph ${ownerService}`,
);
const joinOwnerString = shouldPrintOwner
? `\n @join__owner(graph: ${
context.sanitizedServiceNames?.[ownerService] ?? ownerService
})`
? `\n @join__owner(graph: ${ownerGraphEnumValue})`
: '';

@@ -265,8 +270,13 @@

keys
.map(
(selections) =>
`\n @join__type(graph: ${
context.sanitizedServiceNames?.[service] ?? service
}, key: ${printStringLiteral(printFieldSet(selections))})`,
)
.map((selections) => {
const typeGraphEnumValue =
context.graphNameToEnumValueName?.[service];
assert(
typeGraphEnumValue,
`Unexpected enum value missing for subgraph ${service}`,
);
return `\n @join__type(graph: ${typeGraphEnumValue}, key: ${printStringLiteral(
printFieldSet(selections),
)})`;
})
.join(''),

@@ -362,8 +372,2 @@ )

export function printWithReducedWhitespace(ast: ASTNode): string {
return print(ast)
.replace(/\s+/g, ' ')
.trim();
}
/**

@@ -375,3 +379,5 @@ * Core change: print fieldsets for @join__field's @key, @requires, and @provides args

function printFieldSet(selections: readonly SelectionNode[]): string {
return `${selections.map(printWithReducedWhitespace).join(' ')}`;
return selections
.map((selection) => stripIgnoredCharacters(print(selection)))
.join(' ');
}

@@ -402,14 +408,10 @@

// without a corresponding `@join__type`, which is invalid according to the spec.
if (
parentType.extensions?.federation?.serviceName &&
parentType.extensions?.federation?.keys
) {
return (
printed +
`graph: ${
context.sanitizedServiceNames?.[
parentType.extensions?.federation.serviceName
] ?? parentType.extensions?.federation.serviceName
})`
const { serviceName, keys } = parentType.extensions?.federation;
if (serviceName && keys) {
const enumValue = context.graphNameToEnumValueName?.[serviceName];
assert(
enumValue,
`Unexpected enum value missing for subgraph ${serviceName}`,
);
return printed + `graph: ${enumValue})`;
}

@@ -429,3 +431,3 @@ return '';

directiveArgs.push(
`graph: ${context.sanitizedServiceNames?.[serviceName] ?? serviceName}`,
`graph: ${context.graphNameToEnumValueName?.[serviceName] ?? serviceName}`,
);

@@ -432,0 +434,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc