UNPKG

@graphql-inspector/core

Version:

Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.

176 lines (175 loc) 5.42 kB
export function keyMap(list, keyFn) { return list.reduce((map, item) => { map[keyFn(item)] = item; return map; }, Object.create(null)); } export function isEqual(a, b) { if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let index = 0; index < a.length; index++) { if (!isEqual(a[index], b[index])) { return false; } } return true; } if (a && b && typeof a === 'object' && typeof b === 'object') { const aRecord = a; const bRecord = b; const aKeys = Object.keys(aRecord); const bKeys = Object.keys(bRecord); if (aKeys.length !== bKeys.length) return false; for (const key of aKeys) { if (!isEqual(aRecord[key], bRecord[key])) { return false; } } return true; } return a === b || (!a && !b); } export function isNotEqual(a, b) { return !isEqual(a, b); } export function isVoid(a) { return typeof a === 'undefined' || a === null; } export function diffArrays(a, b) { return a.filter(c => !b.some(d => isEqual(d, c))); } function extractName(name) { if (typeof name === 'string') { return name; } return name.value; } export function compareLists(oldList, newList, callbacks) { const oldMap = keyMap(oldList, ({ name }) => extractName(name)); const newMap = keyMap(newList, ({ name }) => extractName(name)); const added = []; const removed = []; const mutual = []; for (const oldItem of oldList) { const newItem = newMap[extractName(oldItem.name)] ?? null; if (newItem === null) { removed.push(oldItem); } else { mutual.push({ newVersion: newItem, oldVersion: oldItem ?? null, }); } } for (const newItem of newList) { if (oldMap[extractName(newItem.name)] === undefined) { added.push(newItem); } } if (callbacks) { if (callbacks.onRemoved) { for (const item of removed) { callbacks.onRemoved(item); } } if (callbacks.onAdded) { for (const item of added) { callbacks.onAdded(item); } } if (callbacks.onMutual) { for (const item of mutual) { callbacks.onMutual(item); } } } return { added, removed, mutual, }; } /** * This is special because directives can be repeated and a name alone is not enough * to identify whether or not an instance was changed, added, or removed. * The best option is to assume the order is the same, and treat the changes as they come. * So `type T @foo` to `type T @foo(f: 'bar') @foo` would be adding an argument `f: 'bar'` and * then adding a new directive `@foo`. Rather than adding `@foo(f: 'bar')` */ export function compareDirectiveLists(oldList, newList, callbacks) { // collect all the usages, in order, by name for the old and new version of the schema const oldMap = keyMapList(oldList, ({ name }) => extractName(name)); const newMap = keyMapList(newList, ({ name }) => extractName(name)); const added = []; const removed = []; const mutual = []; for (const oldItem of oldList) { // check if the oldItem exists in the new schema const newItems = newMap[extractName(oldItem.name)] ?? null; // if not, then it's been removed if (newItems === null) { removed.push(oldItem); } else { // if so, then consider this a mutual change, and remove it from the list of newItems to avoid counting it in the future const [newItem, ...rest] = newItems; if (rest.length > 0) { newMap[extractName(oldItem.name)] = rest; } else { delete newMap[extractName(oldItem.name)]; } mutual.push({ newVersion: newItem, oldVersion: oldItem ?? null, }); } } for (const newItem of newList) { const existingItems = oldMap[extractName(newItem.name)] ?? null; if (existingItems === null) { added.push(newItem); } else { const [_, ...rest] = existingItems; if (rest.length > 0) { oldMap[extractName(newItem.name)] = rest; } else { delete oldMap[extractName(newItem.name)]; } } } if (callbacks) { if (callbacks.onRemoved) { for (const item of removed) { callbacks.onRemoved(item); } } if (callbacks.onAdded) { for (const item of added) { callbacks.onAdded(item); } } if (callbacks.onMutual) { for (const item of mutual) { callbacks.onMutual(item); } } } return { added, removed, mutual, }; } export function keyMapList(list, keyFn) { return list.reduce((map, item) => { const key = keyFn(item); map[key] = [...(map[key] ?? []), item]; return map; }, Object.create(null)); }