microfiber
Advanced tools
Comparing version 0.0.6 to 0.0.7
@@ -6,958 +6,27 @@ "use strict"; | ||
}); | ||
exports.Microfiber = exports.KINDS = void 0; | ||
exports.digUnderlyingType = digUnderlyingType; | ||
exports.isReservedType = isReservedType; | ||
exports.typesAreSame = typesAreSame; | ||
var _lodash = _interopRequireDefault(require("lodash.get")); | ||
var _microfiber = require("./microfiber"); | ||
var _lodash2 = _interopRequireDefault(require("lodash.unset")); | ||
var _lodash3 = _interopRequireDefault(require("lodash.defaults")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
// Constructor Options | ||
const ANALYZE_DEFAULT = true; | ||
const NORMALIZE_DEFAULT = true; | ||
const FIX_QUERY_MUTATION_TYPES_DEFAULT = true; | ||
const REMOVE_UNUSED_TYPES_DEFAULT = true; // Method options | ||
// const REMOVE_FIELDS_OF_TYPE_DEFAULT = true | ||
// const REMOVE_INPUT_FIELDS_OF_TYPE_DEFAULT = true | ||
// const REMOVE_POSSIBLE_TYPES_OF_TYPE_DEFAULT = true | ||
// const REMOVE_ARGS_OF_TYPE_DEFAULT = true | ||
const CLEANUP_DEFAULT = true; // GraphQL constants | ||
const KIND_SCALAR = 'SCALAR'; | ||
const KIND_OBJECT = 'OBJECT'; | ||
const KIND_INTERFACE = 'INTERFACE'; | ||
const KIND_UNION = 'UNION'; | ||
const KIND_ENUM = 'ENUM'; | ||
const KIND_INPUT_OBJECT = 'INPUT_OBJECT'; | ||
const KIND_LIST = 'LIST'; | ||
const KIND_NON_NULL = 'NON_NULL'; | ||
const KINDS = Object.freeze({ | ||
SCALAR: KIND_SCALAR, | ||
OBJECT: KIND_OBJECT, | ||
INTERFACE: KIND_INTERFACE, | ||
UNION: KIND_UNION, | ||
ENUM: KIND_ENUM, | ||
INPUT_OBJECT: KIND_INPUT_OBJECT, | ||
LIST: KIND_LIST, | ||
NON_NULL: KIND_NON_NULL | ||
}); // TODO: | ||
// | ||
// interfaces | ||
// | ||
// optimize to only clean if "dirty" and when pulling schema out | ||
exports.KINDS = KINDS; | ||
const defaultOpts = Object.freeze({ | ||
analyze: ANALYZE_DEFAULT, | ||
normalize: NORMALIZE_DEFAULT, | ||
fixQueryAndMutationTypes: FIX_QUERY_MUTATION_TYPES_DEFAULT, | ||
// Remove Types that are not referenced anywhere by anything | ||
removeUnusedTypes: REMOVE_UNUSED_TYPES_DEFAULT, | ||
// Remove things whose Types are not found due to being removed | ||
removeFieldsWithMissingTypes: true, | ||
removeArgsWithMissingTypes: true, | ||
removeInputFieldsWithMissingTypes: true, | ||
removePossibleTypesOfMissingTypes: true, | ||
// Remove all the types and things that are unreferenced immediately? | ||
cleanupSchemaImmediately: true | ||
}); // Map some opts to their corresponding removeType params for proper defaulting | ||
const optsToRemoveTypeParamsMap = Object.freeze({ | ||
// removeFieldsOfType: 'removeFieldsWithMissingTypes', | ||
// removeArgsOfType: 'removeArgsWithMissingTypes', | ||
// removeInputFieldsOfType: 'removeInputFieldsWithMissingTypes', | ||
// removePossibleTypesOfType: 'removePossibleTypesOfMissingTypes', | ||
removeFieldsWithMissingTypes: 'removeFieldsOfType', | ||
removeArgsWithMissingTypes: 'removeArgsOfType', | ||
removeInputFieldsWithMissingTypes: 'removeInputFieldsOfType', | ||
removePossibleTypesOfMissingTypes: 'removePossibleTypesOfType' | ||
Object.keys(_microfiber).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _microfiber[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function () { | ||
return _microfiber[key]; | ||
} | ||
}); | ||
}); | ||
const kindToFieldPropertyMap = Object.freeze({ | ||
[KIND_OBJECT]: 'fields', | ||
[KIND_INPUT_OBJECT]: 'inputFields' // [KIND_INTERFACE]: 'interfaces', | ||
}); | ||
var _etc = require("./etc"); | ||
function digUnderlyingType(type) { | ||
while ([KIND_NON_NULL, KIND_LIST].includes(type.kind)) { | ||
type = type.ofType; | ||
} | ||
return type; | ||
} | ||
function isReservedType(type) { | ||
return type.name.startsWith('__'); | ||
} | ||
class Microfiber { | ||
constructor(response, opts = {}) { | ||
if (!response) { | ||
throw new Error('No response provided!'); | ||
Object.keys(_etc).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _etc[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function () { | ||
return _etc[key]; | ||
} | ||
opts = (0, _lodash3.default)({}, opts, defaultOpts); | ||
this.setOpts(opts); // The rest of the initialization can be handled by this public method | ||
this.setResponse(response); | ||
} | ||
setOpts(opts) { | ||
this.opts = opts || {}; | ||
} // Set/change the response on the instance | ||
setResponse(responseIn) { | ||
const response = JSON.parse(JSON.stringify(responseIn)); | ||
const normalizedResponse = Microfiber.normalizeIntrospectionResponse(response); | ||
if (normalizedResponse !== response) { | ||
this._wasNormalized = true; | ||
} | ||
this.schema = (0, _lodash.default)(normalizedResponse, '__schema'); | ||
if (this.opts.fixQueryAndMutationTypes) { | ||
this.fixQueryAndMutationTypes(); | ||
} // OK, time to validate | ||
this.validate(); | ||
if (this.opts.analyze) { | ||
this.analyze(); | ||
} | ||
if (this.opts.cleanupSchemaImmediately) { | ||
this._cleanSchema(); | ||
} | ||
} | ||
static normalizeIntrospectionResponse(response) { | ||
if (response && response.data) { | ||
return response.data; | ||
} | ||
return response; | ||
} | ||
static digUnderlyingType(type) { | ||
return digUnderlyingType(type); | ||
} | ||
validate() { | ||
if (!this.schema) { | ||
throw new Error('No schema property detected!'); | ||
} | ||
if (!this.schema.types) { | ||
throw new Error('No types detected!'); | ||
} // Must have a Query type...but not necessarily a Mutation type | ||
if (!(0, _lodash.default)(this.schema, `queryType.name`)) { | ||
throw new Error(`No queryType detected!`); | ||
} | ||
} | ||
analyze() { | ||
// Map the kind + name to the index in the types array | ||
this.typeToIndexMap = {}; | ||
this.fieldsOfTypeMap = {}; | ||
this.inputFieldsOfTypeMap = {}; // AKA Unions | ||
this.possibleTypesOfTypeMap = {}; | ||
this.argsOfTypeMap = {}; // Need to keep track of these so that we never remove them for not being referenced | ||
this.queryTypeName = (0, _lodash.default)(this.schema, 'queryType.name'); | ||
this.mutationTypeName = (0, _lodash.default)(this.schema, 'mutationType.name'); | ||
this.subscriptionTypeName = (0, _lodash.default)(this.schema, 'subscriptionType.name'); | ||
for (let typesIdx = 0; typesIdx < this.schema.types.length; typesIdx++) { | ||
const type = this.schema.types[typesIdx]; | ||
if (isUndef(type)) { | ||
continue; | ||
} | ||
const { | ||
kind, | ||
name | ||
} = type; // These come in as null, not undefined | ||
const fields = type.fields || []; | ||
const inputFields = type.inputFields || []; | ||
const possibleTypes = type.possibleTypes || []; | ||
const typesKey = buildKey({ | ||
kind, | ||
name | ||
}); | ||
this.typeToIndexMap[typesKey] = typesIdx; | ||
for (let fieldsIdx = 0; fieldsIdx < fields.length; fieldsIdx++) { | ||
const field = fields[fieldsIdx]; | ||
if (isUndef(field)) { | ||
continue; | ||
} | ||
const fieldType = digUnderlyingType(field.type); // This should always be arrays...maybe empty, never null | ||
const args = field.args || []; | ||
const fieldsKey = buildKey(fieldType); | ||
if (!this.fieldsOfTypeMap[fieldsKey]) { | ||
this.fieldsOfTypeMap[fieldsKey] = []; | ||
} | ||
const fieldPath = `types.${typesIdx}.fields.${fieldsIdx}`; | ||
this.fieldsOfTypeMap[fieldsKey].push(fieldPath); | ||
for (let argsIdx = 0; argsIdx < args.length; argsIdx++) { | ||
const arg = args[argsIdx]; | ||
if (isUndef(arg)) { | ||
continue; | ||
} | ||
const argType = digUnderlyingType(arg.type); | ||
const argsKey = buildKey(argType); | ||
if (!this.argsOfTypeMap[argsKey]) { | ||
this.argsOfTypeMap[argsKey] = []; | ||
} | ||
const argPath = `${fieldPath}.args.${argsIdx}`; | ||
this.argsOfTypeMap[argsKey].push(argPath); | ||
} | ||
} | ||
for (let inputFieldsIdx = 0; inputFieldsIdx < inputFields.length; inputFieldsIdx++) { | ||
const inputField = inputFields[inputFieldsIdx]; | ||
if (isUndef(inputField)) { | ||
continue; | ||
} | ||
const inputFieldType = digUnderlyingType(inputField.type); | ||
const inputFieldsKey = buildKey(inputFieldType); | ||
if (!this.inputFieldsOfTypeMap[inputFieldsKey]) { | ||
this.inputFieldsOfTypeMap[inputFieldsKey] = []; | ||
} | ||
const inputFieldPath = `types.${typesIdx}.inputFields.${inputFieldsIdx}`; | ||
this.inputFieldsOfTypeMap[inputFieldsKey].push(inputFieldPath); | ||
} | ||
for (let possibleTypesIdx = 0; possibleTypesIdx < possibleTypes.length; possibleTypesIdx++) { | ||
const possibleType = possibleTypes[possibleTypesIdx]; | ||
if (isUndef(possibleType)) { | ||
continue; | ||
} | ||
const possibleTypeType = digUnderlyingType(possibleType); | ||
const possibleTypeKey = buildKey(possibleTypeType); | ||
if (!this.possibleTypesOfTypeMap[possibleTypeKey]) { | ||
this.possibleTypesOfTypeMap[possibleTypeKey] = []; | ||
} | ||
const possibleTypePath = `types.${typesIdx}.possibleTypes.${possibleTypesIdx}`; | ||
this.possibleTypesOfTypeMap[possibleTypeKey].push(possibleTypePath); | ||
} | ||
} | ||
} | ||
getResponse() { | ||
const clonedResponse = { | ||
__schema: this._cloneSchema() | ||
}; | ||
if (this._wasNormalized) { | ||
return { | ||
data: clonedResponse | ||
}; | ||
} | ||
return clonedResponse; | ||
} | ||
getType({ | ||
kind = KIND_OBJECT, | ||
name | ||
}) { | ||
return this.schema.types[this.getTypeIndex({ | ||
kind, | ||
name | ||
})]; | ||
} | ||
getAllTypes({ | ||
includeReserved = false, | ||
includeQuery = false, | ||
includeMutation = false, | ||
includeSubscription = false | ||
} = {}) { | ||
const queryType = this.getQueryType(); | ||
const mutationType = this.getMutationType(); | ||
const subscriptionType = this.getSubscriptionType(); | ||
return this.schema.types.filter(type => { | ||
if (!includeReserved && isReservedType(type)) { | ||
return false; | ||
} | ||
if (queryType && !includeQuery && typesAreSame(type, queryType)) { | ||
return false; | ||
} | ||
if (mutationType && !includeMutation && typesAreSame(type, mutationType)) { | ||
return false; | ||
} | ||
if (subscriptionType && !includeSubscription && typesAreSame(type, subscriptionType)) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
} | ||
getTypeIndex({ | ||
kind, | ||
name | ||
}) { | ||
const key = buildKey({ | ||
kind, | ||
name | ||
}); | ||
if (Object.prototype.hasOwnProperty.call(this.typeToIndexMap, key)) { | ||
return this.typeToIndexMap[key]; | ||
} | ||
return false; | ||
} | ||
getQueryType() { | ||
if (!this.queryTypeName) { | ||
return false; | ||
} | ||
return this.getType({ | ||
kind: KIND_OBJECT, | ||
name: this.queryTypeName | ||
}); | ||
} | ||
getQuery({ | ||
name | ||
}) { | ||
const queryType = this.getQueryType(); | ||
if (!queryType) { | ||
return false; | ||
} | ||
return this.getField({ | ||
typeKind: queryType.kind, | ||
typeName: queryType.name, | ||
fieldName: name | ||
}); | ||
} | ||
getMutationType() { | ||
if (!this.mutationTypeName) { | ||
return false; | ||
} | ||
return this.getType({ | ||
kind: KIND_OBJECT, | ||
name: this.mutationTypeName | ||
}); | ||
} | ||
getMutation({ | ||
name | ||
}) { | ||
const mutationType = this.getMutationType(); | ||
if (!mutationType) { | ||
return false; | ||
} | ||
return this.getField({ | ||
typeKind: mutationType.kind, | ||
typeName: mutationType.name, | ||
fieldName: name | ||
}); | ||
} | ||
getSubscriptionType() { | ||
if (!this.subscriptionTypeName) { | ||
return false; | ||
} | ||
return this.getType({ | ||
kind: KIND_OBJECT, | ||
name: this.subscriptionTypeName | ||
}); | ||
} | ||
getSubscription({ | ||
name | ||
}) { | ||
const subscriptionType = this.getSubscriptionType(); | ||
if (!subscriptionType) { | ||
return false; | ||
} | ||
return this.getField({ | ||
typeKind: subscriptionType.kind, | ||
typeName: subscriptionType.name, | ||
fieldName: name | ||
}); | ||
} | ||
getField({ | ||
typeKind = KIND_OBJECT, | ||
typeName, | ||
fieldName | ||
}) { | ||
const type = this.getType({ | ||
kind: typeKind, | ||
name: typeName | ||
}); | ||
if (!type) { | ||
return; | ||
} | ||
const fieldsProperty = kindToFieldPropertyMap[typeKind]; | ||
if (!(fieldsProperty && type[fieldsProperty])) { | ||
return; | ||
} | ||
return type[fieldsProperty].find(field => field.name === fieldName); | ||
} | ||
getInputField({ | ||
inputName, | ||
inputFieldName | ||
}) { | ||
return this.getField({ | ||
typeName: inputName, | ||
typeKind: KIND_INPUT_OBJECT, | ||
fieldName: inputFieldName | ||
}); | ||
} | ||
getArg({ | ||
typeKind, | ||
typeName, | ||
fieldName, | ||
argName | ||
}) { | ||
const field = this.getField({ | ||
typeKind, | ||
typeName, | ||
fieldName | ||
}); | ||
if (!(field && field.args.length)) { | ||
return; | ||
} | ||
return field.args.find(arg => arg.name === argName); | ||
} | ||
fixQueryAndMutationTypes(response) { | ||
for (const [key, defaultTypeName] of [['queryType', 'Query'], ['mutationType', 'Mutation']]) { | ||
const queryOrMutationTypeName = (0, _lodash.default)(response, `__schema.${key}.name`); | ||
if (queryOrMutationTypeName && !this.getType({ | ||
kind: KIND_OBJECT, | ||
name: queryOrMutationTypeName | ||
})) { | ||
this.schema[key] = { | ||
name: defaultTypeName | ||
}; | ||
} | ||
} | ||
} | ||
removeType({ | ||
kind = KIND_OBJECT, | ||
name, | ||
removeFieldsOfType, | ||
removeInputFieldsOfType, | ||
removePossibleTypesOfType, | ||
removeArgsOfType, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
const typeKey = buildKey({ | ||
kind, | ||
name | ||
}); | ||
if (!Object.prototype.hasOwnProperty.call(this.typeToIndexMap, typeKey)) { | ||
return false; | ||
} | ||
const typeIndex = this.typeToIndexMap[typeKey]; | ||
if (isUndef(typeIndex)) { | ||
return false; | ||
} // Create an object of some of the opts, but mapped to keys that match the params | ||
// of this method. They will then be used as the default value for the params | ||
// so that constructor opts will be the default, but they can be overridden in | ||
// the call. | ||
const mappedOpts = mapProps({ | ||
props: this.opts, | ||
map: optsToRemoveTypeParamsMap | ||
}); | ||
const mergedOpts = (0, _lodash3.default)({ | ||
removeFieldsOfType, | ||
removeInputFieldsOfType, | ||
removePossibleTypesOfType, | ||
removeArgsOfType | ||
}, mappedOpts); // If we are going to clean up afterwards, then the others should not have to | ||
const shouldOthersClean = !cleanup; | ||
const originalSchema = this._cloneSchema(); | ||
try { | ||
delete this.schema.types[typeIndex]; | ||
delete this.typeToIndexMap[typeKey]; | ||
if (mergedOpts.removeArgsOfType) { | ||
this.removeArgumentsOfType({ | ||
kind, | ||
name, | ||
cleanup: shouldOthersClean | ||
}); | ||
} | ||
if (mergedOpts.removeFieldsOfType) { | ||
this.removeFieldsOfType({ | ||
kind, | ||
name, | ||
cleanup: shouldOthersClean | ||
}); | ||
} | ||
if (mergedOpts.removeInputFieldsOfType) { | ||
this.removeInputFieldsOfType({ | ||
kind, | ||
name, | ||
cleanup: shouldOthersClean | ||
}); | ||
} // AKA Unions | ||
if (mergedOpts.removePossibleTypesOfType) { | ||
this.removePossibleTypesOfType({ | ||
kind, | ||
name, | ||
cleanup: shouldOthersClean | ||
}); | ||
} | ||
if (cleanup) { | ||
this._cleanSchema(); | ||
} | ||
return true; | ||
} catch (err) { | ||
this.schema = originalSchema; | ||
throw err; | ||
} | ||
} | ||
removeField({ | ||
typeKind, | ||
typeName, | ||
fieldName, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
const type = this.getType({ | ||
kind: typeKind, | ||
name: typeName | ||
}); | ||
if (!(type && type.fields)) { | ||
return false; | ||
} // TODO: build a map for the locations of fields on types? | ||
type.fields = type.fields.filter(field => field.name !== fieldName); | ||
if (cleanup) { | ||
this._cleanSchema(); | ||
} | ||
} | ||
removeArg({ | ||
typeKind, | ||
typeName, | ||
fieldName, | ||
argName, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
const field = this.getField({ | ||
typeKind, | ||
typeName, | ||
fieldName | ||
}); // field.args should alwys be an array, never null | ||
if (!field) { | ||
return false; | ||
} // TODO: build a map for the locations of args on fields? | ||
field.args = field.args.filter(arg => arg.name !== argName); | ||
if (cleanup) { | ||
this._cleanSchema(); | ||
} | ||
} | ||
removeQuery({ | ||
name, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
if (!this.queryTypeName) { | ||
return false; | ||
} | ||
this.removeField({ | ||
typeKind: KIND_OBJECT, | ||
typeName: this.queryTypeName, | ||
fieldName: name, | ||
cleanup | ||
}); | ||
} | ||
removeMutation({ | ||
name, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
if (!this.mutationTypeName) { | ||
return false; | ||
} | ||
this.removeField({ | ||
typeKind: KIND_OBJECT, | ||
typeName: this.mutationTypeName, | ||
fieldName: name, | ||
cleanup | ||
}); | ||
} | ||
removeSubscription({ | ||
name, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
if (!this.subscriptionTypeName) { | ||
return false; | ||
} | ||
this.removeField({ | ||
typeKind: KIND_OBJECT, | ||
typeName: this.subscriptionTypeName, | ||
fieldName: name, | ||
cleanup | ||
}); | ||
} | ||
removeFieldsOfType({ | ||
kind, | ||
name, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
return this._removeThingsOfType({ | ||
kind, | ||
name, | ||
map: this.fieldsOfTypeMap, | ||
cleanup | ||
}); | ||
} | ||
removeInputFieldsOfType({ | ||
kind, | ||
name, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
return this._removeThingsOfType({ | ||
kind, | ||
name, | ||
map: this.inputFieldsOfTypeMap, | ||
cleanup | ||
}); | ||
} // AKA Unions | ||
removePossibleTypesOfType({ | ||
kind, | ||
name, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
return this._removeThingsOfType({ | ||
kind, | ||
name, | ||
map: this.possibleTypesOfTypeMap, | ||
cleanup | ||
}); | ||
} | ||
removeArgumentsOfType({ | ||
kind, | ||
name, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
return this._removeThingsOfType({ | ||
kind, | ||
name, | ||
map: this.argsOfTypeMap, | ||
cleanup | ||
}); | ||
} // Remove just a single possible value for an Enum, but not the whole Enum | ||
removeEnumValue({ | ||
name, | ||
value | ||
}) { | ||
const type = this.getType({ | ||
kind: KIND_ENUM, | ||
name | ||
}); | ||
if (!(type && type.enumValues)) { | ||
return false; | ||
} | ||
type.enumValues = type.enumValues.filter(enumValue => enumValue.name !== value); | ||
} // private | ||
_removeThingsOfType({ | ||
kind, | ||
name, | ||
map, | ||
cleanup = CLEANUP_DEFAULT | ||
}) { | ||
const key = buildKey({ | ||
kind, | ||
name | ||
}); | ||
for (const path of map[key] || []) { | ||
(0, _lodash2.default)(this.schema, path); | ||
} | ||
delete map[key]; | ||
if (cleanup) { | ||
this._cleanSchema(); | ||
} | ||
} | ||
_cloneSchema() { | ||
return JSON.parse(JSON.stringify(this.schema)); | ||
} // Removes all the undefined gaps created by various removals | ||
_cleanSchema() { | ||
// Used to compare the schema before and after it was cleaned | ||
const schemaToStart = JSON.stringify(this.schema); | ||
const typesEncountered = new Set(); | ||
const types = []; // The Query, Mutation and Subscription Types should never be removed due to not being referenced | ||
// by anything | ||
if (this.queryTypeName) { | ||
typesEncountered.add(buildKey({ | ||
kind: KIND_OBJECT, | ||
name: this.queryTypeName | ||
})); | ||
} | ||
if (this.mutationTypeName) { | ||
typesEncountered.add(buildKey({ | ||
kind: KIND_OBJECT, | ||
name: this.mutationTypeName | ||
})); | ||
} | ||
if (this.subscriptionTypeName) { | ||
typesEncountered.add(buildKey({ | ||
kind: KIND_OBJECT, | ||
name: this.subscriptionTypeName | ||
})); | ||
} | ||
for (const type of this.schema.types) { | ||
if (!type) { | ||
continue; | ||
} | ||
types.push(type); | ||
const fields = []; | ||
for (const field of type.fields || []) { | ||
if (isUndef(field)) { | ||
continue; | ||
} | ||
const fieldType = digUnderlyingType(field.type); // Don't add it if its return type does not exist | ||
if (!this._hasType(fieldType)) { | ||
continue; | ||
} // Keep track of this so we know what we can remove | ||
typesEncountered.add(buildKey(fieldType)); | ||
const args = []; | ||
for (const arg of field.args || []) { | ||
if (isUndef(arg)) { | ||
continue; | ||
} | ||
const argType = digUnderlyingType(arg.type); // Don't add it if its return type does not exist | ||
if (!this._hasType(argType)) { | ||
continue; | ||
} // Keep track of this so we know what we can remove | ||
typesEncountered.add(buildKey(argType)); | ||
args.push(arg); | ||
} // Args will always be an array. Possible empty, but never null | ||
field.args = args; | ||
fields.push(field); | ||
} // Fields will be null rather than empty array if no fields | ||
type.fields = fields.length ? fields : null; | ||
const inputFields = []; // Don't add it in if it's undefined, or the type is gone | ||
for (const inputField of type.inputFields || []) { | ||
if (isUndef(inputField)) { | ||
continue; | ||
} | ||
const inputFieldType = digUnderlyingType(inputField.type); // Don't add it if its return type does not exist | ||
if (!this._hasType(inputFieldType)) { | ||
continue; | ||
} // Keep track of this so we know what we can remove | ||
typesEncountered.add(buildKey(inputFieldType)); | ||
inputFields.push(inputField); | ||
} // InputFields will be null rather than empty array if no inputFields | ||
type.inputFields = inputFields.length ? inputFields : null; | ||
const possibleTypes = []; | ||
for (const possibleType of type.possibleTypes || []) { | ||
if (isUndef(possibleType)) { | ||
continue; | ||
} // possibleTypes array entries have no envelope for the type | ||
// so do not do possibleType.type here | ||
const possibleTypeType = digUnderlyingType(possibleType); // Don't add it if its return type does not exist | ||
if (!this._hasType(possibleTypeType)) { | ||
continue; | ||
} // Keep track of this so we know what we can remove | ||
typesEncountered.add(buildKey(possibleTypeType)); | ||
possibleTypes.push(possibleType); | ||
} // PossibleTypes will be null rather than emptry array if no possibleTypes | ||
type.possibleTypes = possibleTypes.length ? possibleTypes : null; | ||
} // Only include Types that we encountered - if the options say to do so | ||
const possiblyFilteredTypes = this.opts.removeUnusedTypes ? types.filter(type => isReservedType(type) || typesEncountered.has(buildKey(type))) : types; // Replace the Schema | ||
this.schema = _objectSpread(_objectSpread({}, this.schema), {}, { | ||
types: possiblyFilteredTypes | ||
}); // Need to re-analyze it, too | ||
this.analyze(); // If the schema was changed by this cleanup, we should run it again to see if other things | ||
// should be removed...and continue to do so until the schema is stable. | ||
if (schemaToStart !== JSON.stringify(this.schema)) { | ||
return this._cleanSchema(); | ||
} | ||
} | ||
_hasType({ | ||
kind, | ||
name | ||
}) { | ||
const key = buildKey({ | ||
kind, | ||
name | ||
}); | ||
return Object.prototype.hasOwnProperty.call(this.typeToIndexMap, key); | ||
} | ||
} | ||
exports.Microfiber = Microfiber; | ||
function buildKey({ | ||
kind, | ||
name | ||
}) { | ||
return kind + ':' + name; | ||
} | ||
function isUndef(item) { | ||
return typeof item === 'undefined'; | ||
} | ||
function typesAreSame(typeA, typeB) { | ||
return typeA.kind === typeB.kind && typeA.name === typeB.name; | ||
} | ||
function mapProps({ | ||
props, | ||
map | ||
}) { | ||
return Object.entries(map).reduce((acc, [from, to]) => { | ||
if (Object.prototype.hasOwnProperty.call(props, from)) { | ||
acc[to] = props[to]; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
}); | ||
}); |
{ | ||
"name": "microfiber", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"description": "A library to query and manipulate GraphQL Introspection Query results in some useful ways.", | ||
@@ -5,0 +5,0 @@ "author": "Chris Newhouse", |
339
README.md
@@ -8,11 +8,344 @@ <a href="https://www.useanvil.com"><img src="/static/anvil.png" width="50"></a> | ||
A library to query and manipulate GraphQL Introspection Query results in some useful ways. What ways you ask? | ||
A library to query and manipulate GraphQL Introspection Query results in some useful ways. | ||
How about: | ||
- Digging through your Introspection Query Results for a specific Query, Mutation, Type, Field, Argument or Subscription. | ||
- Removing a specific Query, Mutation, Type, Field/InputField, Argument or Subscription from your Introspection Query Results. | ||
- Removing Queries, Mutations, Fields/InputFields or Arguments that refer to Type that does not exist in - or has been removed from - your Introspection Query Results. | ||
Yay! | ||
It's called `microfiber` because it is heavily used to do the cleaning and manipulation in [SpectaQL][spectaql]...it *cleans* the *spectacles*, get it?! | ||
But, we also wanted to have a more intuitive, literal name so that people could find it. Hence it's also known as `@anvilco/graphql-introspection-tools`. | ||
## Getting Started | ||
1. Install `microfiber` | ||
```sh | ||
npm install microfiber | ||
# OR | ||
yarn add microfiber | ||
``` | ||
2. Clean your GraphQL Introspection Query Results | ||
```node | ||
import { Microfiber } from 'microfiber' | ||
const introspectionQueryResults = {...} | ||
const microfiber = new Microfiber(introspectionQueryResults) | ||
// ...do some things to your schema with `microfiber` | ||
const cleanedIntrospectonQueryResults = microfiber.getResponse() | ||
// ...do something with your cleaned Introspection Query Results. | ||
``` | ||
## Usage | ||
Coming soon! Please avoid usage until then as things are changing rapidly. | ||
### class Microfiber | ||
Most of the useful stuff in this library is done through creating a new Microfiber class instance with your Introspection Query Results, and querying or manipulating it via that instance. Here are most of the interesting bits to know about class behavior. | ||
--- | ||
#### constructor | ||
```node | ||
const introspectionQueryResponse = {...} | ||
// Here are the publicly supported options and their sane defaults: | ||
const options = { | ||
// Some GraphQL implementations have non-standard Query, Mutation and/or Subscription | ||
// type names. This option will fix them if they're messed up in the Introspection Query | ||
// Results | ||
fixQueryAndMutationAndSubscriptionTypes: true, | ||
// Remove Types that are not referenced anywhere by anything | ||
removeUnusedTypes: true, | ||
// Remove things whose Types are not found due to being removed | ||
removeFieldsWithMissingTypes: true, | ||
removeArgsWithMissingTypes: true, | ||
removeInputFieldsWithMissingTypes: true, | ||
removePossibleTypesOfMissingTypes: true, | ||
// Remove all the types and things that are unreferenced immediately? | ||
cleanupSchemaImmediately: true, | ||
} | ||
const microfiber = new Microfiber(introspectionQueryResponse, options) | ||
``` | ||
--- | ||
#### cleanSchema | ||
Clean up the schema by removing: | ||
- Fields or Input Fields whose Type does not exist in the schema. | ||
- Args whose Type does not exist in the schema. | ||
- Possible Types in a Union that do not exist in the schema. | ||
- Queries or Mutations whose return Type does not exist in the schema. | ||
This method is usually called after altering the schema in any way so as to not leave any dangling/orphaned things around the schema. | ||
```node | ||
microfiber.cleanSchema() | ||
``` | ||
--- | ||
#### getResponse | ||
Get out the Introspection Query Result that you have manipulated with Microfiber as an Object. | ||
```node | ||
const cleanedResponse = microfiber.getResponse() | ||
``` | ||
--- | ||
#### getAllTypes | ||
Get all the Types from your schema as an Array of Objects. Supported options and their sane defaults are shown. | ||
```node | ||
const allTypes = microfiber.getAllTypes({ | ||
// Include reserved GraphQL types? | ||
includeReserved: false, | ||
// Include the Query type? | ||
includeQuery: false, | ||
// Include the Mutation type? | ||
includeMutation: false, | ||
// Include the Subscription type? | ||
includeSubscription: false, | ||
}) | ||
``` | ||
--- | ||
#### getType | ||
Get a specific Type from yoyur schema. Supported params and their sane defaults are shown. | ||
```node | ||
const type = microfiber.getType({ kind: 'OBJECT', name }) | ||
``` | ||
--- | ||
#### getQueryType | ||
Get the Query Type from your schema. | ||
```node | ||
const queryType = microfiber.getQueryType() | ||
``` | ||
--- | ||
#### getQuery | ||
Get a specific Query from your schema. | ||
```node | ||
const query = microfiber.getQuery({ name }) | ||
``` | ||
--- | ||
#### getMutationType | ||
Get the Mutation Type from your schema. | ||
```node | ||
const mutationType = microfiber.getMutationType() | ||
``` | ||
--- | ||
#### getMutation | ||
Get a specific Mutation from your schema. | ||
```node | ||
const mutation = microfiber.getMutation({ name }) | ||
``` | ||
--- | ||
#### getSubscriptionType | ||
Get the Subscription Type from your schema. | ||
```node | ||
const subscriptionType = microfiber.getSubscription() | ||
``` | ||
--- | ||
#### getSubscription | ||
Get a specific Subscription from your schema. | ||
```node | ||
const subscription = microfiber.getSubscription({ name }) | ||
``` | ||
--- | ||
#### getField | ||
Get a specific Field from your schema. Supported params and their sane defaults are shown. | ||
```node | ||
const field = microfiber.getField({ typeKind: 'OBJECT', typeName, fieldName }) | ||
``` | ||
--- | ||
#### getInputField | ||
Get a specific InputField from your schema. | ||
```node | ||
const inputField = microfiber.getInputField({ typeName, fieldName }) | ||
``` | ||
--- | ||
#### getArg | ||
Get a specific Arg from your schema. Supported params and their sane defaults are shown. | ||
```node | ||
const arg = microfiber.getArg({ typeKind: 'OBJECT', typeName, fieldName, argName }) | ||
``` | ||
--- | ||
#### removeType | ||
Remove a Type from your schema, and optionally the references to that Type elsewhere in your schema. Supported params and their sane defaults are shown. | ||
```node | ||
microfiber.removeType({ | ||
kind: 'OBJECT', | ||
name, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
// Remove occurances of this Type from other places? | ||
removeFieldsOfType: constructorOptions.removeFieldsWithMissingTypes, | ||
removeInputFieldsOfType: constructorOptions.removeInputFieldsWithMissingTypes, | ||
removePossibleTypesOfType: constructorOptions.removePossibleTypesOfMissingTypes, | ||
removeArgsOfType: constructorOptions.removeArgsWithMissingTypes, | ||
}) | ||
``` | ||
--- | ||
#### removeField | ||
Remove a specific Field from a specific Type in your schema. Supported params and their sane defaults are shown. | ||
```node | ||
microfiber.removeField({ | ||
typeKind: 'OBJECT', | ||
typeName, | ||
fieldName, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
}) | ||
``` | ||
--- | ||
#### removeInputField | ||
Remove a specific Input Field from a specific Input Object in your schema. Supported params and their sane defaults are shown. | ||
```node | ||
microfiber.removeInputField({ | ||
typeName, | ||
fieldName, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
}) | ||
``` | ||
--- | ||
#### removeArg | ||
Remove a specific Arg from a specific Field or Input Field in your schema. Supported params and their sane defaults are shown. | ||
```node | ||
microfiber.removeArg({ | ||
typeKind, | ||
typeName, | ||
fieldName, | ||
argName, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
}) | ||
``` | ||
--- | ||
#### removeEnumValue | ||
Remove a specifc Enum value from an Enum Type in your schema. Supported params are shown. | ||
```node | ||
microfiber.removeEnumValue({ | ||
// The name of the Enum Type | ||
name, | ||
// The Enum value you want to remove | ||
value, | ||
}) | ||
``` | ||
--- | ||
#### removePossibleType | ||
Remove a Possible Type from a specific Union Type in your schema. Supported params and sane defaults are shown. | ||
```node | ||
microfiber.removePossibleType({ | ||
// The name of the Union Type | ||
typeName, | ||
// The Kind of the possible Type you want to remove | ||
possibleTypeKind, | ||
// The name of the possible Type you want to remove | ||
possibleTypeName, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
}) | ||
``` | ||
--- | ||
#### removeQuery | ||
Remove a specific Query from your schema. Supported params and their sane defaults are shown. | ||
```node | ||
microfiber.removeQuery({ | ||
name, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
}) | ||
``` | ||
--- | ||
#### removeMutation | ||
Remove a specific Mutation from your schema. Supported params and their sane defaults are shown. | ||
```node | ||
microfiber.removeMutation({ | ||
name, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
}) | ||
``` | ||
--- | ||
#### removeSubscription | ||
Remove a specific Subscription from your schema. Supported params and their sane defaults are shown. | ||
```node | ||
microfiber.removeSubscription({ | ||
name, | ||
// Clean up the schema afterwards? | ||
cleanup: true, | ||
}) | ||
``` | ||
### Other exports from this library | ||
There are some other exports from this library, not just the `Microfiber` class. | ||
--- | ||
#### KINDS | ||
An Object containing all the GraphQL Kind values you may encounter. | ||
```node | ||
import { KINDS } from 'microfiber' | ||
console.log(KINDS) | ||
// { | ||
// SCALAR: 'SCALAR', | ||
// OBJECT: 'OBJECT', | ||
// INTERFACE: 'INTERFACE', | ||
// UNION: 'UNION', | ||
// ENUM: 'ENUM', | ||
// INPUT_OBJECT: 'INPUT_OBJECT', | ||
// LIST: 'LIST', | ||
// NON_NULL: 'NON_NULL' | ||
// } | ||
``` | ||
--- | ||
#### typesAreSame | ||
A function that compares 2 types and determines if they have the same Kind and Name. | ||
```node | ||
import { typesAreSame } from 'microfiber' | ||
const typeA = { kind: 'OBJECT', name: 'Foo' } | ||
const typeB = { kind: 'OBJECT', name: 'Bar' } | ||
typesAreSame(typeA, typeB) // false | ||
typesAreSame(typeA, typeA) // true | ||
``` | ||
--- | ||
#### digUnderlyingType | ||
A function that digs through any Non-Null and List nesting and returns the underlying Type. | ||
```node | ||
import { digUnderlyingType } from 'microfiber' | ||
const nonNullableString = { | ||
name: null, | ||
kind: 'NON_NULL', | ||
ofType: { | ||
name: null, | ||
kind: 'LIST', | ||
ofType: { | ||
name: 'String', | ||
kind: 'SCALAR', | ||
} | ||
} | ||
} | ||
digUnderlyingType(nonNullableString) // { name: 'String', kind: 'SCALAR' }= | ||
``` | ||
--- | ||
#### isReservedType | ||
A function that returns a Boolean indicating whether a Type is special GraphQL reserved Type. | ||
```node | ||
import { isReservedType } from 'microfiber' | ||
const myType = { name: 'Foo', ... } | ||
const reservedType = { name: '__Foo', ... } | ||
isReservedType(myType) // false | ||
isReservedType(reservedType) // true | ||
``` | ||
[npm]: https://badge.fury.io/js/microfiber.svg | ||
[npm-downloads]: https://img.shields.io/npm/dw/microfiber | ||
[npm-url]: https://www.npmjs.com/package/microfiber | ||
[npm-url]: https://www.npmjs.com/package/microfiber | ||
[spectaql]: https://github.com/anvilco/spectaql |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
38683
6
856
350
1