Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@micro-graphql/core

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@micro-graphql/core - npm Package Compare versions

Comparing version
0.1.2
to
0.2.0-alpha.0
+3
lib/helpers.d.ts
import { DocumentNode } from 'graphql/language/ast';
export declare function addRequiredFields(query: DocumentNode): DocumentNode;
//# sourceMappingURL=helpers.d.ts.map
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAiCpD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,CAenE"}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const kinds_1 = require("graphql/language/kinds");
const visitor_1 = require("graphql/language/visitor");
const isInlineFragment = (node) => node.kind === kinds_1.Kind.INLINE_FRAGMENT;
const hasField = (fieldName) => (set) => set.some(({ alias, name }) => (alias || name).value === fieldName);
const createField = (name) => ({
kind: 'Field',
alias: undefined,
name: {
kind: 'Name',
value: name
},
arguments: [],
directives: [],
selectionSet: undefined
});
const hasTypeNameField = hasField('__typename');
const hasEdgesField = hasField('edges');
const typeNameField = createField('__typename');
const connectionFields = ['edges', 'pageInfo'];
const excludeMetaFields = (node, _, parent) => node.selections
.some(isInlineFragment)
|| hasEdgesField(node.selections)
|| (!isInlineFragment(parent) && connectionFields.includes(parent.name.value));
function addRequiredFields(query) {
return visitor_1.visit(query, {
SelectionSet: (node, key, parent) => {
if (parent
&& (parent.kind === kinds_1.Kind.OPERATION_DEFINITION || excludeMetaFields(node, key, parent))) {
return undefined;
}
if (!hasTypeNameField(node.selections)) {
node.selections.unshift(typeNameField);
}
return node;
}
});
}
exports.addRequiredFields = addRequiredFields;
//# sourceMappingURL=helpers.js.map
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":";;AAEA,kDAA8C;AAC9C,sDAAiD;AAEjD,MAAM,gBAAgB,GAAG,CAAC,IAAS,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,YAAI,CAAC,eAAe,CAAC;AAEpF,MAAM,QAAQ,GAAG,CAAC,SAAc,EAAE,EAAE,CAAC,CAAC,GAAQ,EAAO,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EACjE,KAAK,EAAE,IAAI,EACN,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;AAEhD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAO,EAAE,CAAC,CAAC;IAC3C,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE;QACL,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,IAAI;KACX;IACD,SAAS,EAAE,EAAE;IACb,UAAU,EAAE,EAAE;IACd,YAAY,EAAE,SAAS;CACvB,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;AAChD,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;AACxC,MAAM,aAAa,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;AAEhD,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AAE/C,MAAM,iBAAiB,GAAG,CAAC,IAAS,EAAE,CAAM,EAAE,MAAW,EAAO,EAAE,CAAC,IAAI,CAAC,UAAU;KAChF,IAAI,CAAC,gBAAgB,CAAC;OACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC;OAC9B,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhF,SAAgB,iBAAiB,CAAC,KAAmB;IACpD,OAAO,eAAK,CAAC,KAAK,EAAE;QACnB,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,MAAW,EAAO,EAAE;YAC7C,IAAI,MAAM;mBACN,CAAC,MAAM,CAAC,IAAI,KAAK,YAAI,CAAC,oBAAoB,IAAI,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE;gBACxF,OAAO,SAAS,CAAC;aACjB;YAED,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;gBACtC,IAAI,CAAC,UAAkB,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;aAChD;YAED,OAAO,IAAI,CAAC;QACb,CAAC;KACD,CAAC,CAAC;AACJ,CAAC;AAfD,8CAeC"}
import { DocumentNode } from 'graphql/language/ast';
export declare type SubscribeCallback<TData> = (data: TData) => void;
export declare type UnsubscribeFunc = () => void;
export interface IMicroCache {
readQuery<TData, TVariables>(query: DocumentNode, variables: TVariables): TData | undefined;
writeQuery<TData, TVariables>(query: DocumentNode, variables: TVariables, data: TData): void;
subscribe<TData, TVariables>(query: DocumentNode, variables: TVariables, callback: SubscribeCallback<TData>): UnsubscribeFunc;
}
export declare function newCache(): IMicroCache;
//# sourceMappingURL=smart-cache.d.ts.map
{"version":3,"file":"smart-cache.d.ts","sourceRoot":"","sources":["../src/smart-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAQpD,oBAAY,iBAAiB,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;AAC7D,oBAAY,eAAe,GAAG,MAAM,IAAI,CAAC;AAEzC,MAAM,WAAW,WAAW;IAC3B,SAAS,CAAC,KAAK,EAAE,UAAU,EAC1B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,UAAU,GACnB,KAAK,GAAG,SAAS,CAAC;IAErB,UAAU,CAAC,KAAK,EAAE,UAAU,EAC3B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,UAAU,EACrB,IAAI,EAAE,KAAK,GACT,IAAI,CAAC;IAER,SAAS,CAAC,KAAK,EAAE,UAAU,EAC1B,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAChC,eAAe,CAAC;CACnB;AASD,wBAAgB,QAAQ,IAAI,WAAW,CAgEtC"}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const graphql_norm_1 = require("graphql-norm");
function newCache() {
let cache = {};
let subscriptions = [];
function notifySubscribers(triggers) {
subscriptions.forEach(s => {
if (s.triggers.length < 2
|| s.triggers.slice(1).some(trigger => triggers.some(t => t === trigger))) {
const { data, fields } = graphql_norm_1.denormalize(s.query, s.variables, cache);
s.triggers = Object.getOwnPropertyNames(fields);
if (data) {
s.callback(data);
}
}
});
}
return {
readQuery(query, variables) {
const { data } = graphql_norm_1.denormalize(query, variables, cache);
return data;
},
writeQuery(query, variables, data) {
const normalizedData = graphql_norm_1.normalize(query, variables, data);
cache = graphql_norm_1.merge(cache, normalizedData);
notifySubscribers(Object.getOwnPropertyNames(normalizedData));
},
subscribe(query, variables, callback) {
const { data, fields } = graphql_norm_1.denormalize(query, variables, cache);
if (typeof data !== 'undefined') {
callback(data);
}
subscriptions.push({
callback: callback,
triggers: Object.getOwnPropertyNames(fields),
query,
variables
});
return () => {
subscriptions = subscriptions.filter(s => s.callback !== callback);
};
}
};
}
exports.newCache = newCache;
//# sourceMappingURL=smart-cache.js.map
{"version":3,"file":"smart-cache.js","sourceRoot":"","sources":["../src/smart-cache.ts"],"names":[],"mappings":";;AACA,+CAKsB;AA+BtB,SAAgB,QAAQ;IACvB,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,aAAa,GAAoB,EAAE,CAAC;IAExC,SAAS,iBAAiB,CAAC,QAAkB;QAC5C,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACzB,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;mBACrB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,EACxE;gBACD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,0BAAW,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,SAAsB,EAAE,KAAK,CAAC,CAAC;gBAE/E,CAAC,CAAC,QAAQ,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;gBAEhD,IAAI,IAAI,EAAE;oBACT,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;iBACjB;aACD;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,OAAO;QACN,SAAS,CACR,KAAmB,EACnB,SAAqB;YAErB,MAAM,EAAE,IAAI,EAAE,GAAG,0BAAW,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;YAEtD,OAAO,IAAyB,CAAC;QAClC,CAAC;QAED,UAAU,CACT,KAAmB,EACnB,SAAqB,EACrB,IAAW;YAEX,MAAM,cAAc,GAAG,wBAAS,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YACzD,KAAK,GAAG,oBAAK,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;YAErC,iBAAiB,CAAC,MAAM,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAC,CAAC;QAC/D,CAAC;QAED,SAAS,CACR,KAAmB,EACnB,SAAqB,EACrB,QAAkC;YAElC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,0BAAW,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;YAE9D,IAAI,OAAO,IAAI,KAAK,WAAW,EAAE;gBAChC,QAAQ,CAAC,IAAa,CAAC,CAAC;aACxB;YAED,aAAa,CAAC,IAAI,CAAC;gBAClB,QAAQ,EAAE,QAAsC;gBAChD,QAAQ,EAAE,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC;gBAC5C,KAAK;gBACL,SAAS;aACT,CAAC,CAAC;YAEH,OAAO,GAAS,EAAE;gBACjB,aAAa,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;YACpE,CAAC,CAAC;QACH,CAAC;KACD,CAAC;AACH,CAAC;AAhED,4BAgEC"}
/* eslint-disable @typescript-eslint/no-explicit-any */
import { DocumentNode } from 'graphql/language/ast';
import { Kind } from 'graphql/language/kinds';
import { visit } from 'graphql/language/visitor';
const isInlineFragment = (node: any): boolean => node.kind === Kind.INLINE_FRAGMENT;
const hasField = (fieldName: any) => (set: any): any => set.some(({
alias, name
}: any) => (alias || name).value === fieldName);
const createField = (name: string): any => ({
kind: 'Field',
alias: undefined,
name: {
kind: 'Name',
value: name
},
arguments: [],
directives: [],
selectionSet: undefined
});
const hasTypeNameField = hasField('__typename');
const hasEdgesField = hasField('edges');
const typeNameField = createField('__typename');
const connectionFields = ['edges', 'pageInfo'];
const excludeMetaFields = (node: any, _: any, parent: any): any => node.selections
.some(isInlineFragment)
|| hasEdgesField(node.selections)
|| (!isInlineFragment(parent) && connectionFields.includes(parent.name.value));
export function addRequiredFields(query: DocumentNode): DocumentNode {
return visit(query, {
SelectionSet: (node, key, parent: any): any => {
if (parent
&& (parent.kind === Kind.OPERATION_DEFINITION || excludeMetaFields(node, key, parent))) {
return undefined;
}
if (!hasTypeNameField(node.selections)) {
(node.selections as any).unshift(typeNameField);
}
return node;
}
});
}
// eslint-disable-next-line import/no-extraneous-dependencies
import gql from 'graphql-tag';
export const query = gql`
query MockFilmQuery($filmID: ID) {
film(filmID: $filmID) {
id
title
episodeID
}
}
`;
export const variables = { filmID: 1 };
export const response = `
{
"data": {
"film": {
"__typename": "Film",
"id": "ZmlsbXM6MQ==",
"title": "A New Hope",
"episodeID": 4
}
}
}
`;
+8
-1
{
"name": "@micro-graphql/core",
"version": "0.1.2",
"version": "0.2.0-alpha.0",
"license": "MIT",

@@ -11,3 +11,10 @@ "main": "lib/index.js",

},
"dependencies": {
"graphql": "*",
"graphql-norm": "^1.3.6"
},
"devDependencies": {
"@types/graphql": "^14.5.0",
"graphql": "^14.6.0",
"graphql-tag": "^2.10.3",
"typescript": "^3.7.5"

@@ -14,0 +21,0 @@ },

+102
-42

@@ -1,62 +0,122 @@

import { objectHash } from './hash';
import { DocumentNode } from 'graphql/language/ast';
import {
normalize,
denormalize,
merge,
Variables
} from 'graphql-norm';
export interface IMicroGraphQLCacheResult<TValue> {
data?: TValue;
success: boolean;
}
import { addRequiredFields } from './helpers';
export type SubscribeCallback<TData> = (data: TData) => void;
export type UnsubscribeFunc = () => void;
export interface IMicroGraphQLCache {
tryGet<TValue>(
query: string,
variables: { [key: string]: unknown } | undefined
): IMicroGraphQLCacheResult<TValue>;
trySet<TValue>(
query: string,
variables: { [key: string]: unknown } | undefined,
data?: TValue
): boolean;
readQuery<TData, TVariables>(
query: DocumentNode,
variables?: TVariables
): TData | undefined;
writeQuery<TData, TVariables>(
query: DocumentNode,
variables: TVariables | undefined,
data: TData
): void;
subscribe<TData, TVariables>(
query: DocumentNode,
variables: TVariables | undefined,
callback: SubscribeCallback<TData>
): UnsubscribeFunc;
prepareQuery(query: DocumentNode): DocumentNode;
stringify(): string;
restore(data: string): void;
prepareQuery(query: string): string;
}
type SubscriptionArr = Array<{
callback: SubscribeCallback<unknown>;
triggers: string[];
query: DocumentNode;
variables: unknown;
}>;
export function createCache(): IMicroGraphQLCache {
let cache: { [key: string]: unknown } = {};
let cache = {};
let subscriptions: SubscriptionArr = [];
function notifySubscribers(triggers: string[]): void {
subscriptions.forEach(s => {
if (s.triggers.length < 2
|| s.triggers.slice(1).some(trigger => triggers.some(t => t === trigger))
) {
const { data, fields } = denormalize(s.query, s.variables as Variables, cache);
s.triggers = Object.getOwnPropertyNames(fields);
if (data) {
s.callback(data);
}
}
});
}
return {
tryGet: <TValue>(
query: string,
variables: { [key: string]: unknown } | undefined
): IMicroGraphQLCacheResult<TValue> => {
const key = objectHash({ query, variables });
stringify(): string {
return JSON.stringify(cache);
},
const success = key in cache;
const data = success ? cache[key] : undefined;
restore(data: string): void {
cache = JSON.parse(data) || {};
},
return {
data: data as TValue,
success
};
prepareQuery(query: DocumentNode): DocumentNode {
return addRequiredFields(query);
},
trySet: <TValue>(
query: string,
variables: { [key: string]: unknown } | undefined,
data?: TValue
): boolean => {
const key = objectHash({ query, variables });
cache[key] = data;
readQuery<TData, TVariables>(
query: DocumentNode,
variables?: TVariables
): TData | undefined {
const { data } = denormalize(query, variables, cache);
return false;
return data as TData | undefined;
},
stringify(): string {
return JSON.stringify(cache);
writeQuery<TData, TVariables>(
query: DocumentNode,
variables: TVariables | undefined,
data: TData
): void {
const normalizedData = normalize(query, variables, data);
cache = merge(cache, normalizedData);
notifySubscribers(Object.getOwnPropertyNames(normalizedData));
},
restore(data: string): void {
cache = JSON.parse(data);
},
prepareQuery(query: string): string {
return query;
subscribe<TData, TVariables>(
query: DocumentNode,
variables: TVariables | undefined,
callback: SubscribeCallback<TData>
): UnsubscribeFunc {
const { data, fields } = denormalize(query, variables, cache);
if (typeof data !== 'undefined') {
callback(data as TData);
}
subscriptions.push({
callback: callback as SubscribeCallback<unknown>,
triggers: Object.getOwnPropertyNames(fields),
query,
variables
});
return (): void => {
subscriptions = subscriptions.filter(s => s.callback !== callback);
};
}
};
}

@@ -1,3 +0,4 @@

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable import/extensions */
import { DocumentNode } from 'graphql/language/ast';
import { print } from 'graphql/language/printer';
import { GraphQLFormattedError } from 'graphql/error/formatError';

@@ -7,166 +8,114 @@ import { IMicroGraphQLCache } from './cache';

export interface IMicroGraphQLConfig {
cache: IMicroGraphQLCache;
url: string;
ssr?: boolean;
fetch(input: RequestInfo, init?: RequestInit | undefined): Promise<Response>;
}
export interface IMicroGraphQLError {
message: string;
path?: ReadonlyArray<string | number>;
extensions?: { [key: string]: unknown };
}
export interface IMicroGraphQLResult<TData> {
loading: boolean;
data?: TData;
errors?: IMicroGraphQLError[];
errors?: GraphQLFormattedError[];
}
export interface IMicroGraphQLQueryOptions<TQueryVariables extends { [key: string]: unknown }> {
export interface IMicroGraphQLQueryOptions {
skipCache?: boolean;
variables?: TQueryVariables;
}
export interface IMicroGraphQLSubscriptionOptions<
TQueryVariables extends { [key: string]: unknown }
>
extends IMicroGraphQLQueryOptions<TQueryVariables> {
query: string;
export interface IMicroGraphQLClient {
cache: IMicroGraphQLCache;
resolveQueries(): Promise<void>;
query<TData, TVariables>(
query: DocumentNode,
variables?: TVariables,
options?: IMicroGraphQLQueryOptions
): Promise<IMicroGraphQLResult<TData>>;
mutate<TData, TVariables>(
mutation: DocumentNode,
variables?: TVariables
): Promise<IMicroGraphQLResult<TData>>;
}
export interface IMicroGraphQLClient {
export interface IMicroGraphQLClientConfig {
cache: IMicroGraphQLCache;
fetch(input: RequestInfo, init?: RequestInit | undefined): Promise<Response>;
ssr?: boolean;
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-explicit-any
query<TData extends { [key: string]: any }, TQueryVariables extends { [key: string]: any }>(
query: string,
options?: IMicroGraphQLQueryOptions<TQueryVariables>
): Promise<IMicroGraphQLResult<TData>>;
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-explicit-any
subscribe: <TData extends { [key: string]: any }, TQueryVariables extends { [key: string]: any }>(
options: IMicroGraphQLSubscriptionOptions<TQueryVariables>,
subscription: (data: IMicroGraphQLResult<TData>) => void
) => () => void;
resolveQueries(): Promise<void>;
url: string;
}
export class MicroGraphQLKeyError extends Error {}
export const queryKeyError = 'error creating a unique key for the query';
export function createClient({
url,
cache,
fetch,
ssr,
fetch
}: IMicroGraphQLConfig): IMicroGraphQLClient {
const subscriptions = new Map<string, Array<(data: IMicroGraphQLResult<any> & {}) => void>>();
url
}: IMicroGraphQLClientConfig): IMicroGraphQLClient {
const requests: { [key: string]: Promise<unknown> } = {};
const queries: { [key: string]: Promise<IMicroGraphQLResult<unknown>> } = {};
async function doRequest<TData, TVariables>(
query: DocumentNode,
variables?: TVariables
): Promise<IMicroGraphQLResult<TData>> {
const resultPromise = (async (): Promise<IMicroGraphQLResult<TData>> => {
const response = await fetch(url, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: print(query),
variables
})
});
return {
cache,
ssr,
resolveQueries: async (): Promise<void> => {
await Promise.all(Object.getOwnPropertyNames(queries).map(key => queries[key]));
},
// eslint-disable-next-line max-len
subscribe: <TData extends { [key: string]: any }, TQueryVariables extends { [key: string]: any }>(
options: IMicroGraphQLSubscriptionOptions<TQueryVariables>,
subscription: (data: IMicroGraphQLResult<TData>) => void
): (() => void) => {
const query = cache.prepareQuery(options.query);
const json = (await response.json()) as IMicroGraphQLResult<TData>;
const cached = cache.tryGet<TData>(query, options.variables);
if (cached.success) {
subscription({
loading: false,
data: cached.data
});
if (json.data) {
cache.writeQuery<TData, TVariables>(query, variables, json.data);
}
const key = objectHash({ query, variables: options.variables });
return json;
})();
const subs = subscriptions.get(key) || [];
subs.push(subscription);
subscriptions.set(key, subs);
if (ssr) {
requests[objectHash({ query, variables })] = resultPromise;
}
return (): void => {
const toRemoveFrom = subscriptions.get(key)!;
subscriptions.set(
key,
toRemoveFrom.filter(s => s !== subscription)
);
};
},
// eslint-disable-next-line max-len
query: async <TData extends { [key: string]: any }, TQueryVariables extends { [key: string]: any }>(
inputQuery: string,
options?: IMicroGraphQLQueryOptions<TQueryVariables>
): Promise<IMicroGraphQLResult<TData>> => {
const { skipCache }: IMicroGraphQLQueryOptions<TQueryVariables> = {
skipCache: false,
...options
};
const result = await resultPromise;
const query = cache.prepareQuery(inputQuery);
if (!skipCache) {
const cachedResult = !skipCache && cache.tryGet<TData>(query, options && options.variables);
return result;
}
if (cachedResult.success) {
return {
loading: false,
data: cachedResult.data
};
}
}
return {
cache,
const key = objectHash({ query, variables: options && options.variables });
async resolveQueries(): Promise<void> {
await Promise.all(Object.getOwnPropertyNames(requests).map(key => requests[key]));
},
const subs = subscriptions.get(key);
if (subs) {
subs.forEach(sub => sub({
loading: true
}));
async query<TData, TVariables>(
query: DocumentNode,
variables?: TVariables,
options: IMicroGraphQLQueryOptions = {
skipCache: false
}
): Promise<IMicroGraphQLResult<TData>> {
const { skipCache } = options;
const resultPromise = (async (): Promise<IMicroGraphQLResult<TData>> => {
const response = await fetch(url, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query,
variables: options && options.variables
})
});
const preparedQuery = cache.prepareQuery(query);
const json = (await response.json()) as IMicroGraphQLResult<TData>;
if (!skipCache) {
const data = cache.readQuery<TData, TVariables>(preparedQuery, variables);
if (json.data) {
cache.trySet<TData>(query, options && options.variables, json.data);
if (typeof data !== 'undefined') {
return {
data
};
}
}
return {
errors: undefined,
...json,
loading: false
};
})();
const result = await doRequest<TData, TVariables>(query, variables);
if (ssr) {
queries[key] = resultPromise;
}
return result;
},
const result = await resultPromise;
async mutate<TData, TVariables>(
mutation: DocumentNode,
variables: TVariables
): Promise<IMicroGraphQLResult<TData>> {
const preparedMutation = cache.prepareQuery(mutation);
if (subs) {
subs.forEach(sub => sub(result));
}
const result = await doRequest<TData, TVariables>(preparedMutation, variables);

@@ -173,0 +122,0 @@ return result;

export * from './client';
export * from './cache';
export * from './gql';

@@ -1,12 +0,9 @@

/* eslint-disable import/extensions */
import {
createCache,
IMicroGraphQLCache,
gql,
frag
} from '../src';
// eslint-disable-next-line import/no-extraneous-dependencies
import gql from 'graphql-tag';
const fragment = frag`
import { createCache, IMicroGraphQLCache } from '../src/cache';
const fragment = gql`
fragment TestFrag on Film {
title
releaseDate
}

@@ -19,10 +16,78 @@ `;

id
${fragment}
...TestFrag
... on Film {
title
}
}
}
${fragment}
`;
const subQuery = gql`
query TestSubQuery($id: ID) {
film(filmID: $id) {
id
title
}
}
`;
interface IVariables {
id: string;
}
const variables = { id: 'abc' };
describe('cache', () => {
interface IExpected {
film: {
__typename: 'Film';
id: string;
title: string;
releaseDate: string;
};
}
const expected: IExpected = {
film: {
__typename: 'Film',
id: 'abc',
title: 'abc',
releaseDate: '2020-01-30'
}
};
const changed: IExpected = {
film: {
__typename: 'Film',
id: 'abc',
title: 'def',
releaseDate: '2020-02-30'
}
};
interface ISubExpected {
film: {
__typename: 'Film';
id: string;
title: string;
};
}
const subExpected: ISubExpected = {
film: {
__typename: 'Film',
id: 'abc',
title: 'abc'
}
};
const subChanged: ISubExpected = {
film: {
__typename: 'Film',
id: 'abc',
title: 'def'
}
};
describe('smart-cache', () => {
let cache: IMicroGraphQLCache;

@@ -34,38 +99,179 @@

it('returns success as false and undefined data for unset key', () => {
const cached = cache.tryGet(query, variables);
expect(cached).toBeTruthy();
expect(cached.success).toBe(false);
expect(cached.data).toBeUndefined();
});
it('skips initial callback if nothing in cache', () => {
let called = 0;
it('can set key and retrieve value', () => {
const data = { v: 10 };
const unsubscribe = cache.subscribe<IExpected, IVariables>(
cache.prepareQuery(query),
variables,
() => {
called += 1;
}
);
cache.trySet(query, variables, data);
const cached = cache.tryGet(query, variables);
expect(cached).toBeTruthy();
expect(cached.success).toBe(true);
expect(cached.data).toBe(data);
unsubscribe();
expect(called).toBe(0);
});
it('can write and read query', () => {
cache.writeQuery(cache.prepareQuery(query), variables, expected);
const data = cache.readQuery<IExpected, IVariables>(query, variables);
expect(data).toEqual(expected);
});
it('can stringify and restore', () => {
const data = { v: 10 };
cache.writeQuery(cache.prepareQuery(query), variables, expected);
const data = cache.readQuery<IExpected, IVariables>(query, variables);
expect(data).toEqual(expected);
cache.trySet(query, variables, data);
const stringified = cache.stringify();
const secondCache = createCache();
secondCache.restore(cache.stringify());
const data2 = cache.readQuery<IExpected, IVariables>(cache.prepareQuery(query), variables);
expect(data2).toEqual(expected);
});
const newCache = createCache();
newCache.restore(stringified);
it('can restore default', () => {
const secondCache = createCache();
secondCache.restore('""');
const data2 = cache.readQuery<IExpected, IVariables>(cache.prepareQuery(query), variables);
expect(data2).toBeUndefined();
});
const cached = cache.tryGet(query, variables);
expect(cached).toBeTruthy();
expect(cached.success).toBe(true);
expect(cached.data).toBe(data);
it('can subscribe and get initial value', () => {
cache.writeQuery<IExpected, IVariables>(cache.prepareQuery(query), variables, expected);
const restoredCached = cache.tryGet(query, variables);
expect(restoredCached).toBeTruthy();
expect(restoredCached.success).toBe(true);
expect(restoredCached.data).toEqual(data);
let called = 0;
let result: IExpected | undefined;
const unsubscribe = cache.subscribe<IExpected, IVariables>(
query,
variables,
(data: IExpected) => {
called += 1;
result = data;
}
);
unsubscribe();
expect(called).toBe(1);
expect(result).toEqual(expected);
});
it('skips callback if query not fulfilled', () => {
const unrelatedQuery = gql`
query TestNoFulfilledQuery {
rofl {
name
}
}
`;
let called = 0;
const unsubscribe = cache.subscribe<IExpected, IVariables>(
cache.prepareQuery(unrelatedQuery),
variables,
() => {
called += 1;
}
);
cache.writeQuery(cache.prepareQuery(query), variables, expected);
expect(called).toBe(0);
unsubscribe();
});
it('skips callback if query not fulfilled and existing data', () => {
const unrelatedQuery = gql`
query TestNoFulfilledQuery {
rofl {
name
}
}
`;
cache.writeQuery(cache.prepareQuery(unrelatedQuery), {}, {
rofl: {
__typename: 'Rofl',
name: 'rofl'
}
});
let called = 0;
const unsubscribe = cache.subscribe<IExpected, IVariables>(
cache.prepareQuery(unrelatedQuery),
variables,
() => {
called += 1;
}
);
expect(called).toBe(1);
cache.writeQuery(cache.prepareQuery(query), variables, expected);
expect(called).toBe(1);
unsubscribe();
});
it('can subscribe and get updated value', () => {
cache.writeQuery(query, variables, expected);
let called = 0;
let result: IExpected | undefined;
const unsubscribe = cache.subscribe<IExpected, IVariables>(
cache.prepareQuery(query),
variables,
(data: IExpected) => {
called += 1;
result = data;
}
);
expect(called).toBe(1);
expect(result).toEqual(expected);
cache.writeQuery(cache.prepareQuery(query), variables, changed);
unsubscribe();
expect(called).toBe(2);
expect(result).toEqual(changed);
});
it('can subscribe and get updated value from sub query', () => {
cache.writeQuery(cache.prepareQuery(query), variables, expected);
let called = 0;
let result: IExpected | undefined;
const unsubscribe = cache.subscribe<IExpected, IVariables>(
cache.prepareQuery(query),
variables,
(data: IExpected) => {
called += 1;
result = data;
}
);
expect(called).toBe(1);
expect(result).toEqual(expected);
cache.writeQuery(cache.prepareQuery(subQuery), variables, subChanged);
unsubscribe();
expect(called).toBe(2);
expect(result).toEqual({
film: {
...expected.film,
...subChanged.film
}
});
});
});

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

/* eslint-disable import/no-extraneous-dependencies */
// eslint-disable-next-line import/no-extraneous-dependencies
import 'jest-fetch-mock';

@@ -7,259 +7,143 @@

createClient,
IMicroGraphQLResult,
IMicroGraphQLCacheResult,
IMicroGraphQLConfig
IMicroGraphQLClient,
IMicroGraphQLResult
} from '../src';
import { query, variables, response } from './mock-film';
describe('client', () => {
const query = `
query TestQuery($id: ID) {
film(filmID: $id) {
title
}
}
`;
const variables = { id: 1 };
interface IQueryResult {
film: {
title: string;
};
}
const validateResult = (result?: IMicroGraphQLResult<IQueryResult>): void => {
expect(result).toBeTruthy();
expect(result!.data).toBeTruthy();
expect(result!.data!.film).toBeTruthy();
expect(result!.data!.film.title).toBe('A New Hope');
};
let options: IMicroGraphQLConfig;
beforeEach(() => {
it('skips cache if no data', async () => {
global.fetch.resetMocks();
global.fetch.mockResponse(`
{"data":{"film":{"title":"A New Hope"}}}
`);
global.fetch.mockResponse('{}');
options = {
const client = createClient({
cache: createCache(),
fetch: global.fetch,
url: 'https://swapi-graphql.netlify.com/.netlify/functions/index',
cache: createCache()
};
});
url: 'https://swapi-graphql.netlify.com/.netlify/functions/index'
});
it('can return cached result with prepare query', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cached: any = {};
await client.query(query, variables);
const client = createClient({
...options,
cache: {
tryGet: <TValue>(): IMicroGraphQLCacheResult<TValue> => ({
success: true,
data: cached
}),
trySet: (): boolean => false,
restore: jest.fn(),
stringify: jest.fn(),
prepareQuery: (q: string): string => q
}
});
const result = await client.query<IQueryResult, {}>(query, {
variables
});
expect(result).toBeTruthy();
expect(result.data).toBe(cached);
const data = client.cache.readQuery(query, variables);
expect(data).toBeUndefined();
});
it('can skip cached result with prepare query', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cached: any = {};
describe('query', () => {
const expected = JSON.parse(response);
const client = createClient({
...options,
cache: {
tryGet: <TValue>(): IMicroGraphQLCacheResult<TValue> => ({
success: true,
data: cached
}),
trySet: (): boolean => false,
restore: jest.fn(),
stringify: jest.fn(),
prepareQuery: (q: string): string => q
}
});
let client: IMicroGraphQLClient;
let promise: Promise<IMicroGraphQLResult<unknown>>;
beforeEach(() => {
global.fetch.resetMocks();
global.fetch.mockResponse(response);
const result = await client.query<IQueryResult, {}>(query, {
variables,
skipCache: true
});
expect(result).toBeTruthy();
});
client = createClient({
cache: createCache(),
fetch: global.fetch,
url: 'https://swapi-graphql.netlify.com/.netlify/functions/index'
});
it('can return cached result', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cached: any = {};
const client = createClient({
...options,
cache: {
tryGet: <TValue>(): IMicroGraphQLCacheResult<TValue> => ({
success: true,
data: cached
}),
trySet: (): boolean => false,
restore: jest.fn(),
stringify: jest.fn(),
prepareQuery: (q: string): string => q
}
promise = client.query(query, variables);
});
const result = await client.query<IQueryResult, {}>(query, {
variables
it('can query', async () => {
const result = await promise;
expect(result).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(1);
});
expect(result).toBeTruthy();
expect(result.data).toBe(cached);
});
it('can get cached result when subscribing', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cached: any = {};
it('can use cache', async () => {
const result = await promise;
expect(result).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(1);
let prepared = false;
const client = createClient({
...options,
cache: {
tryGet: <TValue>(): IMicroGraphQLCacheResult<TValue> => ({
success: true,
data: cached
}),
trySet: (): boolean => false,
restore: jest.fn(),
stringify: jest.fn(),
prepareQuery: (q: string): string => {
prepared = true;
return q;
}
}
const secondResult = await client.query(query, variables);
expect(secondResult).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(1);
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let result: any;
client.subscribe<IQueryResult, {}>({
query,
variables
}, (res) => {
result = res;
});
expect(result).toBeTruthy();
expect(result.data).toBe(cached);
expect(prepared).toBe(true);
});
it('can skip cache', async () => {
const result = await promise;
expect(result).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(1);
it('skips cache if no data', async () => {
global.fetch.resetMocks();
global.fetch.mockResponse(`
{}
`);
const client = createClient(options);
const result = await client.query<IQueryResult, {}>(query, {
variables
const secondResult = await client.query(query, variables, { skipCache: true });
expect(secondResult).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(2);
});
expect(result.data).toBeUndefined();
expect(client.cache!.tryGet(query, variables).success).toBe(false);
});
it('can make query', async () => {
const client = createClient(options);
const result = await client.query<IQueryResult, {}>(query, {
variables
it('stores in cache', async () => {
await promise;
const data = client.cache.readQuery(query, variables);
expect(data).toEqual(expected.data);
});
validateResult(result);
});
it('can make ssr query', async () => {
const client = createClient({ ...options, ssr: true });
describe('query ssr', () => {
const expected = JSON.parse(response);
const result = client.query<IQueryResult, {}>(query, {
variables
});
let client: IMicroGraphQLClient;
beforeEach(() => {
global.fetch.resetMocks();
global.fetch.mockResponse(response);
await client.resolveQueries();
client = createClient({
cache: createCache(),
fetch: global.fetch,
url: 'https://swapi-graphql.netlify.com/.netlify/functions/index',
ssr: true
});
validateResult(await result);
});
it('can set provided cache', async () => {
const client = createClient({
...options,
cache: createCache()
client.query(query, variables);
});
const result = await client.query<IQueryResult, {}>(query, {
variables
});
validateResult(result);
it('can resolve queries', async () => {
await client.resolveQueries();
const secondResult = await client.query<IQueryResult, {}>(query, {
variables
const result = await client.query(query, variables);
expect(result).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(1);
});
expect(secondResult.data).toBe(result.data);
});
it('can skip cache', async () => {
const client = createClient({
...options,
cache: createCache()
});
describe('mutate', () => {
const expected = JSON.parse(response);
const result = await client.query<IQueryResult, {}>(query, {
variables
});
validateResult(result);
let client: IMicroGraphQLClient;
let promise: Promise<IMicroGraphQLResult<unknown>>;
beforeEach(() => {
global.fetch.resetMocks();
global.fetch.mockResponse(response);
const secondResult = await client.query<IQueryResult, {}>(query, {
skipCache: true,
variables
client = createClient({
cache: createCache(),
fetch: global.fetch,
url: 'https://swapi-graphql.netlify.com/.netlify/functions/index'
});
promise = client.mutate(query, variables);
});
expect(secondResult.data).not.toBe(result.data);
expect(secondResult.data).toEqual(result.data);
});
it('can receive subscription value', async () => {
const client = createClient({
...options,
cache: createCache()
it('can mutate', async () => {
const result = await promise;
expect(result).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(1);
});
let subscriptionCount = 0;
let dataFromSubscription: IMicroGraphQLResult<IQueryResult>;
const unsubscribe = client.subscribe<IQueryResult, {}>(
{ query, variables },
data => {
dataFromSubscription = data;
subscriptionCount += 1;
}
);
it('can mutate multiple times', async () => {
const result = await promise;
expect(result).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(1);
const result = await client.query<IQueryResult, {}>(query, {
variables
const secondResult = await client.mutate(query, variables);
expect(secondResult).toEqual(expected);
expect(global.fetch.mock.calls.length).toBe(2);
});
validateResult(result);
validateResult(dataFromSubscription!);
unsubscribe();
const secondResult = await client.query<IQueryResult, {}>(query, {
skipCache: true,
variables
it('stores in cache', async () => {
await promise;
const data = client.cache.readQuery(query, variables);
expect(data).toEqual(expected.data);
});
expect(secondResult.data).not.toBe(result.data);
expect(secondResult.data).toEqual(result.data);
expect(subscriptionCount).toBe(2);
});
});
export function gql(strings: TemplateStringsArray, ...fragments: IMicroGraphQLFragment[]): string {
const frags = fragments.map(fragment => fragment.definition).join('\n').trim();
return strings.map((str, i) => {
const cat = fragments[i] ? `...${fragments[i].name}` : '';
return str + cat;
}).join('').trim() + (frags ? `\n${frags}` : '');
}
export interface IMicroGraphQLFragment {
definition: string;
name: string;
}
export const noFragmentNameError = 'no name found for the provided fragment';
export function frag(
strings: TemplateStringsArray
): IMicroGraphQLFragment {
const definition = gql(strings);
const name = definition.match(/fragment\s([A-z][\w\d]*)\son/);
if (!name || !name[1]) {
throw new Error(noFragmentNameError);
}
return {
definition,
name: name[1]
};
}
import { frag, gql, noFragmentNameError } from '../src';
describe('gql', () => {
it('should support no fragments', () => {
const tag = gql`
query TestQuery($id: ID) {
film(filmID: $id) {
title
}
}
`;
expect(tag).toBe(`query TestQuery($id: ID) {
film(filmID: $id) {
title
}
}`);
});
it('throws if no fragment name', () => {
expect(() => frag`
fragment on Film {
id
}
`).toThrow(noFragmentNameError);
});
it('can parse fragment', () => {
const fragTag = frag`
fragment FilmInfo on Film {
id
}
`;
expect(fragTag.name).toBe('FilmInfo');
expect(fragTag.definition).toBe(`fragment FilmInfo on Film {
id
}`);
});
it('should support single fragment', () => {
const fragTag = frag`
fragment FilmInfo on Film {
id
}
`;
const tag = gql`
query TestQuery($id: ID) {
film(filmID: $id) {
title
${fragTag}
}
}
`;
expect(tag).toBe(`query TestQuery($id: ID) {
film(filmID: $id) {
title
...FilmInfo
}
}
fragment FilmInfo on Film {
id
}`);
});
it('should support multiple fragment', () => {
const fragTag1 = frag`
fragment FilmInfo on Film {
id
}
`;
const fragTag2 = frag`
fragment FilmDetails on Film {
id
episodeID
}
`;
const tag = gql`
query TestQuery($id: ID) {
film(filmID: $id) {
title
${fragTag1}
${fragTag2}
}
}
`;
expect(tag).toBe(`query TestQuery($id: ID) {
film(filmID: $id) {
title
...FilmInfo
...FilmDetails
}
}
fragment FilmInfo on Film {
id
}
fragment FilmDetails on Film {
id
episodeID
}`);
});
});

Sorry, the diff of this file is not supported yet