Skip to content
This repository has been archived by the owner on Jul 6, 2020. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
Implement initial cache writer
  • Loading branch information
kitten committed Feb 24, 2019
0 parents commit 18bc0c1
Show file tree
Hide file tree
Showing 13 changed files with 7,606 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
@@ -0,0 +1,9 @@
root = true

[*]
charset = utf-8
indent_size = 2
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
15 changes: 15 additions & 0 deletions .gitignore
@@ -0,0 +1,15 @@
node_modules
/lib
es
types
coverage
.vscode
yarn-error.log*
package-lock.json
package-lock.json.*
bundles
dist
webpack-build
.rts2_cache*
cypress/screenshots
.DS_Store
13 changes: 13 additions & 0 deletions .npmignore
@@ -0,0 +1,13 @@
/*
!LICENSE
!CHANGELOG.md
!README.md
!/dist/**/*.{d.ts,js,js.map}
!/src/**/*.{d.ts,ts,tsx}
__snapshots__/
test-utils/
*.test.js
*.test.jsx
*.test.ts
*.test.d.ts
*.test.tsx
102 changes: 102 additions & 0 deletions package.json
@@ -0,0 +1,102 @@
{
"name": "graphcache",
"version": "0.1.0",
"description": "A normalizing GraphQL cache configurable by defining exceptions to the rule",
"main": "dist/graphcache.js",
"module": "dist/graphcache.es.js",
"types": "dist/types/index.d.ts",
"source": "src/index.ts",
"sideEffects": false,
"scripts": {
"prebuild": "rimraf dist",
"build": "run-p build:types build:bundle",
"build:clean": "rimraf dist",
"build:types": "tsc -d --emitDeclarationOnly --outDir dist/types",
"build:bundle": "microbundle --format es,cjs --no-compress",
"build:prune": "rimraf dist/types/**/*.test.d.ts",
"postbuild:bundle": "terser dist/graphcache.es.js -o dist/graphcache.es.min.js",
"test": "jest",
"bundlesize": "bundlesize",
"lint": "tslint --project .",
"check-formatting": "prettier --write src/**/*.{ts,tsx}",
"prepublishOnly": "run-s build build:prune"
},
"author": "Phil Plückthun <phil@kitten.sh>",
"repository": "https://github.com/kitten/graphcache.git",
"license": "MIT",
"prettier": {
"singleQuote": true,
"trailingComma": "es5"
},
"jest": {
"setupFiles": [
"./scripts/setupTests.js"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(src/.*(\\.|/)(test|spec))\\.tsx?$",
"moduleFileExtensions": [
"js",
"jsx",
"ts",
"tsx",
"json"
],
"collectCoverageFrom": [
"<rootDir>/src/**/*.{ts,tsx}"
],
"coveragePathIgnorePatterns": [
"<rootDir>/src.*/index.ts"
]
},
"lint-staged": {
"*.{json,md}": [
"prettier --write",
"git add"
],
"*.{ts,tsx}": [
"tslint --fix",
"prettier --write",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"bundlesize": [
{
"path": "./dist/*.min.js",
"maxSize": "10 kB"
}
],
"devDependencies": {
"@types/graphql": "^14.0.7",
"@types/jest": "^23.3.13",
"bundlesize": "^0.17.0",
"coveralls": "^3.0.0",
"graphql": "^14.1.1",
"graphql-tag": "^2.10.1",
"husky": "^1.2.0",
"jest": "^23.6.0",
"lint-staged": "^8.1.0",
"microbundle": "^0.9.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.15.2",
"rimraf": "^2.6.2",
"terser": "^3.16.1",
"ts-jest": "^23.10.5",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.16.0",
"tslint-react": "^3.6.0",
"typescript": "^3.1.6"
},
"peerDependencies": {
"graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
},
"dependencies": {
"graphql-anywhere": "^4.1.28"
}
}
3 changes: 3 additions & 0 deletions scripts/setupTests.js
@@ -0,0 +1,3 @@
process.on('unhandledRejection', error => {
throw error;
});
44 changes: 44 additions & 0 deletions src/cacheUtils.ts
@@ -0,0 +1,44 @@
import { Cache, Entity, Link } from './types';

export const createCache = (initial?: Cache): Cache => {
const records = Object.create(null);
const links = Object.create(null);
if (initial !== undefined) {
Object.assign(records, initial.records);
Object.assign(links, initial.links);
}

return { records, links };
};

export const getOrCreateEntity = (cache: Cache, key: string): Entity => {
const { records } = cache;
let entity = records[key];
if (entity === undefined) {
entity = records[key] = Object.create(null);
}

return entity;
};

export const writeLink = (cache: Cache, linkKey: string, childKey: Link) => {
const { links } = cache;
links[linkKey] = childKey;
};

export const deleteLink = (cache: Cache, linkKey: string) => {
const { links } = cache;
delete links[linkKey];
};

export const writeFieldValue = (
entity: Entity,
fieldName: string,
fieldValue: any
) => {
entity[fieldName] = fieldValue;
};

export const deleteFieldValue = (entity: Entity, fieldName: string) => {
delete entity[fieldName];
};
30 changes: 30 additions & 0 deletions src/keys.ts
@@ -0,0 +1,30 @@
import { Entity } from './types';

const isOperation = (typeName: string) =>
typeName === 'Query' ||
typeName === 'Mutation' ||
typeName === 'Subscription';

export const keyOfEntity = (entity: Entity): null | string => {
const { __typename: typeName } = entity;
const id = entity.id === undefined ? entity._id : entity.id;

if (typeName === undefined || typeName === null) {
return null;
} else if (isOperation(typeName)) {
return typeName;
} else if (id === null || id === undefined) {
return null;
}

return `${typeName}:${id}`;
};

export const keyForLink = (
parentKey: string,
fieldName: string,
args: null | object
) => {
const key = `${parentKey}->${fieldName}`;
return args ? `${key}(${JSON.stringify(args)})` : key;
};
127 changes: 127 additions & 0 deletions src/responseToCache.test.ts
@@ -0,0 +1,127 @@
import gql from 'graphql-tag';

import { createCache } from './cacheUtils';
import responseToCache from './responseToCache';
import { Cache } from './types';

let cache: Cache;

beforeEach(() => {
cache = createCache();
});

it('caches flat scalars', () => {
const query = gql`
{
__typename
test
}
`;

const response = {
__typename: 'Query',
test: 'value',
};

responseToCache(cache, { query }, response);

expect(cache).toEqual({
records: {
Query: response,
},
links: {},
});
});

it('caches embedded objects', () => {
const query = gql`
{
__typename
test {
__typename
value
}
}
`;

const response = {
__typename: 'Query',
test: { __typename: null, value: '123' },
};

responseToCache(cache, { query }, response);

expect(cache).toEqual({
records: {
Query: response,
},
links: {},
});
});

it('caches flat entities', () => {
const query = gql`
{
__typename
test {
__typename
id
}
}
`;

const response = {
__typename: 'Query',
test: {
__typename: 'Test',
id: 'test',
},
};

responseToCache(cache, { query }, response);

expect(cache).toEqual({
records: {
Query: { __typename: 'Query' },
'Test:test': response.test,
},
links: {
'Query->test': 'Test:test',
},
});
});

it('caches entity lists', () => {
const query = gql`
{
__typename
test {
__typename
id
}
}
`;

const response = {
__typename: 'Query',
test: [
{
__typename: 'Test',
id: 'test',
},
null,
],
};

responseToCache(cache, { query }, response);

expect(cache).toEqual({
records: {
Query: { __typename: 'Query' },
'Test:test': response.test[0],
},
links: {
'Query->test': ['Test:test', null],
},
});
});

0 comments on commit 18bc0c1

Please sign in to comment.