├── demo ├── dev.sh ├── favicon.ico ├── images │ ├── Joey.jpeg │ ├── Logo.png │ ├── Conor.jpeg │ ├── JasonC.jpeg │ ├── JasonL.jpeg │ ├── npm-logo.png │ ├── GitHubLogo.png │ ├── LinkedInLogo.png │ ├── hacheQL-image.png │ ├── bestofbothworld.png │ └── graphql-versus-rest.png ├── Dockerfile-postgres.dockerfile ├── .gitignore ├── Dockerfile-dev.dockerfile ├── client │ ├── index.jsx │ ├── index.html │ ├── App.jsx │ ├── Containers │ │ ├── Headers.jsx │ │ ├── Info.jsx │ │ ├── Team.jsx │ │ └── Demo.jsx │ ├── Components │ │ ├── Scrollview.jsx │ │ ├── Graph.jsx │ │ ├── Dashboard.jsx │ │ └── TeamMember.jsx │ └── styles.css ├── Dockerfile ├── .ebextensions │ └── 00_postgres_client.config ├── .eslintrc.cjs ├── docker-compose-dev-hot.yml ├── server │ ├── models │ │ └── starWarsModel.js │ ├── server.js │ ├── graphql │ │ └── types.js │ └── mydb.sql ├── package.json ├── webpack.config.js └── favicon.svg ├── library ├── .npmignore ├── tests │ ├── HTTPStatusCodes.js │ ├── mockReqRes.js │ ├── mockFunctions.js │ ├── fetch.test.js │ └── middleware.test.js ├── .eslintrc.cjs ├── package.json ├── hacheql.js ├── README.md └── hacheql-server.js ├── pull-request.png ├── LICENSE ├── .gitignore ├── CONTRIBUTING.md ├── README.md └── DOCUMENTATION.md /demo/dev.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | docker-compose -f ./docker-compose-dev-hot.yml up 4 | -------------------------------------------------------------------------------- /demo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/favicon.ico -------------------------------------------------------------------------------- /library/.npmignore: -------------------------------------------------------------------------------- 1 | # .npmignore 2 | coverage 3 | node_modules 4 | tests 5 | .eslintrc.cjs -------------------------------------------------------------------------------- /pull-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/pull-request.png -------------------------------------------------------------------------------- /demo/images/Joey.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/Joey.jpeg -------------------------------------------------------------------------------- /demo/images/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/Logo.png -------------------------------------------------------------------------------- /demo/images/Conor.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/Conor.jpeg -------------------------------------------------------------------------------- /demo/images/JasonC.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/JasonC.jpeg -------------------------------------------------------------------------------- /demo/images/JasonL.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/JasonL.jpeg -------------------------------------------------------------------------------- /demo/images/npm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/npm-logo.png -------------------------------------------------------------------------------- /demo/images/GitHubLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/GitHubLogo.png -------------------------------------------------------------------------------- /demo/images/LinkedInLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/LinkedInLogo.png -------------------------------------------------------------------------------- /demo/images/hacheQL-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/hacheQL-image.png -------------------------------------------------------------------------------- /demo/Dockerfile-postgres.dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:14.2 2 | COPY ./server/mydb.sql /docker-entrypoint-initdb.d/ 3 | -------------------------------------------------------------------------------- /demo/images/bestofbothworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/bestofbothworld.png -------------------------------------------------------------------------------- /demo/images/graphql-versus-rest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/hacheQL/HEAD/demo/images/graphql-versus-rest.png -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .elasticbeanstalk 2 | # Elastic Beanstalk Files 3 | .elasticbeanstalk/* 4 | !.elasticbeanstalk/*.cfg.yml 5 | !.elasticbeanstalk/*.global.yml 6 | -------------------------------------------------------------------------------- /demo/Dockerfile-dev.dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.14.2 2 | RUN npm install -g webpack 3 | WORKDIR /usr/src/app 4 | COPY package*.json /usr/src/app 5 | RUN npm install 6 | EXPOSE 8080 7 | -------------------------------------------------------------------------------- /demo/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './App'; 4 | import './styles.css' 5 | 6 | 7 | render( 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /library/tests/HTTPStatusCodes.js: -------------------------------------------------------------------------------- 1 | const HASH_NOT_FOUND = 303; 2 | 3 | // Disabling prefer-default-export in case we want to export more status codes later on. 4 | // eslint-disable-next-line import/prefer-default-export 5 | export { HASH_NOT_FOUND }; 6 | -------------------------------------------------------------------------------- /demo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.14.2 2 | WORKDIR /usr/src/app 3 | COPY . /usr/src/app 4 | RUN npm install 5 | RUN ["./node_modules/.bin/webpack", "--config", "./webpack.config.js"] 6 | EXPOSE 3000 7 | ENTRYPOINT [ "node", "--experimental-specifier-resolution=node", "./server/server.js" ] 8 | -------------------------------------------------------------------------------- /demo/.ebextensions/00_postgres_client.config: -------------------------------------------------------------------------------- 1 | packages: 2 | yum: 3 | amazon-linux-extras: [] 4 | 5 | commands: 6 | 01_postgres_activate: 7 | command: sudo amazon-linux-extras enable postgresql13 8 | 02_postgres_install: 9 | command: sudo yum install -y postgresql-devel -------------------------------------------------------------------------------- /demo/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HacheQL 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'plugin:react/recommended', 9 | 'airbnb', 10 | ], 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | ecmaVersion: 'latest', 16 | sourceType: 'module', 17 | }, 18 | plugins: [ 19 | 'react', 20 | ], 21 | rules: { 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /demo/client/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Headers from './Containers/Headers.jsx'; 3 | import Demo from './Containers/Demo'; 4 | import Team from './Containers/Team.jsx'; 5 | import Info from './Containers/Info'; 6 | 7 | function App() { 8 | 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /demo/client/Containers/Headers.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Logo from '../../images/Logo.png'; 3 | 4 | // router for different sections of landing page 5 | export default function Headers() { 6 | return ( 7 |
8 |
9 | 10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /demo/client/Components/Scrollview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Scrollview(props) { 4 | const { queryResult } = props; 5 | 6 | // export this to css file 7 | 8 | return ( 9 |
15 | {queryResult} 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /library/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | 'jest/globals': true, 7 | }, 8 | extends: [ 9 | 'airbnb-base', 10 | 'plugin:jest/recommended', 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | sourceType: 'module', 15 | }, 16 | plugins: ['jest'], 17 | rules: { 18 | 'max-len': 'off', 19 | 'newline-per-chained-call': 'off', 20 | 'no-param-reassign': 0, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /demo/client/Components/Graph.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Trend from 'react-trend'; 3 | 4 | export default function graph(props) { 5 | const { fetchTimes } = props; 6 | 7 | return ( 8 |
9 |

Network Trend Graph

10 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /demo/client/Components/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Dashboard(props) { 4 | const { fetchTimes } = props; 5 | const uncachedTime = parseFloat(fetchTimes[2]).toFixed(2) 6 | const latestTime = parseFloat(fetchTimes[fetchTimes.length - 1]).toFixed(2); 7 | const avgFetchTime = parseFloat(fetchTimes.slice(2).reduce((a, b) => a + b, 0)/(fetchTimes.length-1)).toFixed(2); 8 | 9 | // console.log('average fetch time' + avgFetchTime) 10 | 11 | return ( 12 |
13 |

Metrics:

14 |
Uncached Time: 15 |
{isNaN(uncachedTime) ? '0.00 ms': uncachedTime + ' ms'}
16 |
17 |
Latest Fetch Time: 18 |
{latestTime + ' ms'}
19 |
20 |
Average Fetch time: 21 |
{avgFetchTime + ' ms'}
22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /demo/client/Components/TeamMember.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import gitHubLogo from '../../images/GitHubLogo.png'; 3 | import linkedInLogo from '../../images/LinkedInLogo.png'; 4 | 5 | export default function TeamMember(props) { 6 | const { 7 | name, profilePicture, bio, gitHub, linkedIn, 8 | } = props; 9 | return ( 10 |
11 | 12 |

{name}

13 |

{bio}

14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /demo/docker-compose-dev-hot.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | dev: 4 | depends_on: 5 | - postgres-db 6 | image: hacheql/hql-dev 7 | container_name: hql-dev-hot 8 | ports: 9 | - "8080:8080" 10 | volumes: 11 | - .:/usr/src/app 12 | - node_modules:/usr/src/app/node_modules 13 | - ../library/.:/usr/src/app/node_modules/hacheql/ 14 | command: ["./node_modules/.bin/concurrently", "\"NODE_ENV=development ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --open --hot\"", "\"NODE_ENV=development ./node_modules/nodemon/bin/nodemon.js --verbose --watch server --experimental-specifier-resolution=node server/server.js\""] 15 | postgres-db: 16 | image: hacheql/hql-postgres 17 | container_name: hql-database 18 | environment: 19 | - POSTGRES_PASSWORD=admin 20 | - POSTGRES_USER=hqladmin 21 | - POSTGRES_DB=hqldb 22 | volumes: 23 | - dev-db-volume:/var/lib/postgresql/data 24 | volumes: 25 | node_modules: {} 26 | dev-db-volume: {} 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/server/models/starWarsModel.js: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | const { Pool } = pg; 3 | 4 | const config = { 5 | max: 5, 6 | idleTimeoutMillis: 30000, 7 | }; 8 | 9 | if (process.env.NODE_ENV === 'development') { 10 | config.user = 'hqladmin'; 11 | config.database = 'hqldb'; 12 | config.password = 'admin'; 13 | config.host = 'postgres-db'; 14 | config.port = 5432; 15 | } else if (process.env.NODE_ENV === 'production') { 16 | config.user = process.env.RDS_USERNAME; 17 | config.database = process.env.RDS_DB_NAME; 18 | config.password = process.env.RDS_PASSWORD; 19 | config.host = process.env.RDS_HOSTNAME; 20 | config.port = process.env.RDS_PORT; 21 | } 22 | 23 | 24 | const pool = new Pool(config); 25 | 26 | pool.on('error', function (err, client) { 27 | console.error('idle client error', err.message, err.stack); 28 | }); 29 | 30 | const starWarsModel = { 31 | query: (text, params, callback) => { 32 | console.log('executed query', text, params); 33 | return pool.query(text, params, callback); 34 | }, 35 | }; 36 | 37 | export const connect = function (cb) { 38 | return pool.connect(cb); 39 | }; 40 | 41 | export default starWarsModel; 42 | -------------------------------------------------------------------------------- /library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hacheql", 3 | "version": "1.0.2", 4 | "description": "A library for HTTP caching with GraphQL", 5 | "main": "hacheql.js", 6 | "exports": { 7 | ".": "./hacheql.js", 8 | "./server": "./hacheql-server.js" 9 | }, 10 | "type": "module", 11 | "scripts": { 12 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", 13 | "test-coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/oslabs-beta/hacheQL.git" 18 | }, 19 | "author": "Conor Chinitz, Jason Chan, Jason Lin, Joey Torsella", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/oslabs-beta/hacheQL/issues" 23 | }, 24 | "homepage": "https://github.com/oslabs-beta/hacheQL#readme", 25 | "devDependencies": { 26 | "@jest/globals": "^27.5.1", 27 | "eslint": "^8.20.0", 28 | "eslint-config-airbnb-base": "^15.0.0", 29 | "eslint-plugin-import": "^2.25.4", 30 | "eslint-plugin-jest": "^26.1.3", 31 | "jest": "^27.5.1", 32 | "node-mocks-http": "^1.11.0" 33 | }, 34 | "dependencies": { 35 | "ci-info": "^3.3.0", 36 | "sha1": "^1.1.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "description": "A minimal client/server to interact with a GraphQL API", 6 | "main": "server/server.js", 7 | "scripts": {}, 8 | "author": "", 9 | "license": "MIT", 10 | "dependencies": { 11 | "dotenv": "^16.0.0", 12 | "express": "^4.17.3", 13 | "express-graphql": "^0.12.0", 14 | "graphiql": "^1.8.1", 15 | "graphql": "^16.3.0", 16 | "graphql-scalars": "^1.16.0", 17 | "hacheql": "^1.0.1", 18 | "pg": "^8.7.3", 19 | "react": "^17.0.2", 20 | "react-dom": "^17.0.2", 21 | "react-trend": "^1.2.5", 22 | "redis": "^4.0.6", 23 | "sha1": "^1.1.1", 24 | "style-loader": "^3.3.1", 25 | "uuid": "^8.3.2" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.17.8", 29 | "@babel/preset-env": "^7.16.11", 30 | "@babel/preset-react": "^7.16.7", 31 | "babel-loader": "^8.2.3", 32 | "concurrently": "^7.0.0", 33 | "css-loader": "^6.7.1", 34 | "eslint": "^8.13.0", 35 | "eslint-config-airbnb": "^19.0.4", 36 | "eslint-plugin-import": "^2.25.4", 37 | "eslint-plugin-jsx-a11y": "^6.5.1", 38 | "eslint-plugin-react": "^7.29.4", 39 | "eslint-plugin-react-hooks": "^4.3.0", 40 | "file-loader": "^6.2.0", 41 | "html-webpack-plugin": "^5.5.0", 42 | "nodemon": "^2.0.15", 43 | "sass": "^1.49.9", 44 | "webpack": "^5.70.0", 45 | "webpack-cli": "^4.9.2", 46 | "webpack-dev-server": "^4.7.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 2 | import path, { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | 5 | const folderPath = dirname(fileURLToPath(import.meta.url)); 6 | 7 | const config = { 8 | mode: 'development', 9 | entry: './client/index.jsx', 10 | resolve: { 11 | extensions: ['.js', '.jsx'], 12 | }, 13 | output: { 14 | filename: 'bundle.js', 15 | path: path.resolve(folderPath, 'build'), 16 | clean: true, 17 | }, 18 | plugins: [new HtmlWebpackPlugin({ 19 | template: './client/index.html', 20 | favicon: 'favicon.svg', 21 | })], 22 | devtool: 'eval-source-map', 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.jsx?$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: 'babel-loader', 30 | options: { 31 | presets: ['@babel/preset-env', '@babel/preset-react'], 32 | }, 33 | }, 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: ['style-loader', 'css-loader'], 38 | }, 39 | { 40 | test: /\.(png|jpe?g|gif)$/i, 41 | use: { 42 | loader: 'file-loader', 43 | }, 44 | }, 45 | ], 46 | }, 47 | devServer: { 48 | static: { 49 | directory: path.resolve(folderPath, 'build'), 50 | publicPath: '/', 51 | }, 52 | port: 8080, 53 | host: '0.0.0.0', 54 | historyApiFallback: true, 55 | compress: true, 56 | hot: true, 57 | headers: { 'Access-Control-Allow-Origin': '*' }, 58 | proxy: { 59 | '/graphql': { 60 | target: 'http://localhost:3000', secure: false, 61 | }, 62 | }, 63 | }, 64 | }; 65 | 66 | export default config; 67 | -------------------------------------------------------------------------------- /demo/server/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import path, { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import process from 'process'; 5 | import { graphqlHTTP } from 'express-graphql'; 6 | import { expressHacheQL, httpCache } from 'hacheql/server'; 7 | import schema from './graphql/types'; 8 | 9 | const PORT = 3000; 10 | 11 | // ESM model for __dirname 12 | const folderPath = dirname(fileURLToPath(import.meta.url)); 13 | 14 | const app = express(); 15 | 16 | app.use(express.json()); 17 | 18 | // serve static files 19 | app.use(express.static(path.resolve(folderPath, '../build'))); 20 | 21 | // graphiql req 22 | app.use( 23 | '/graphql', 24 | expressHacheQL({}), 25 | httpCache(), 26 | graphqlHTTP({ 27 | schema, 28 | graphiql: true, 29 | }), 30 | ); 31 | 32 | // catch all for pages not found 33 | app.use((req, res) => res.sendStatus(404)); 34 | 35 | // error handler 36 | app.use((err, req, res, next) => { 37 | const defaultErr = { 38 | log: 'Express error handler caught unknown middleware error!', 39 | status: 500, 40 | message: { err: 'An error occurred!' }, 41 | }; 42 | const errorObj = { ...defaultErr, ...err }; 43 | return res.status(errorObj.status).json(errorObj.message); 44 | }); 45 | 46 | app.listen(PORT, () => console.log(`Listening on port ${PORT}`)); 47 | 48 | function shutdown() { 49 | try { 50 | console.log('Successfully shutting down.'); 51 | process.exit(0); 52 | } catch (e) { 53 | console.log('Error in shutdown process.'); 54 | console.error(e); 55 | process.exit(1); 56 | } 57 | } 58 | 59 | process.on('SIGTERM', () => { 60 | console.log('caught SIGTERM.'); 61 | shutdown(); 62 | }); 63 | 64 | process.on('SIGINT', () => { 65 | console.log('\nGracefully shutting down API server'); 66 | shutdown(); 67 | }); 68 | -------------------------------------------------------------------------------- /library/tests/mockReqRes.js: -------------------------------------------------------------------------------- 1 | import { HASH_NOT_FOUND } from './HTTPStatusCodes'; 2 | 3 | const endpointURL = 'graphql'; 4 | const requestOptions = { 5 | method: 'POST', 6 | headers: { 7 | 'Content-Type': 'application/json', 8 | accepts: 'application/json', 9 | }, 10 | body: JSON.stringify( 11 | { 12 | query: `query { 13 | films { 14 | _id 15 | title 16 | } 17 | }`, 18 | }, 19 | ), 20 | }; 21 | const serverResponse200 = { 22 | status: 200, 23 | body: JSON.stringify({ 24 | data: { 25 | characters: [ 26 | { 27 | _id: 1, 28 | name: 'Mario', 29 | win_rate: '54%', 30 | best_time: '1:50.713', 31 | favorite_item: 'Golden Mushroom', 32 | arch_nemesis: { 33 | name: 'Bowser', 34 | }, 35 | }, 36 | { 37 | _id: 2, 38 | name: 'Princess Peach', 39 | win_rate: '78%', 40 | best_time: '1:23.402', 41 | favorite_item: 'Bob-omb', 42 | arch_nemesis: { 43 | name: 'Luigi', 44 | }, 45 | }, 46 | { 47 | _id: 3, 48 | name: 'Luigi', 49 | win_rate: '41%', 50 | best_time: '2:09.250', 51 | favorite_item: 'Triple Bananas', 52 | arch_nemesis: { 53 | name: 'Bowser', 54 | }, 55 | }, 56 | { 57 | _id: 4, 58 | name: 'Bowser', 59 | win_rate: '48%', 60 | best_time: '1:56.917', 61 | favorite_item: 'Piranha Plant', 62 | arch_nemesis: { 63 | name: 'Mario', 64 | }, 65 | }, 66 | ], 67 | }, 68 | }), 69 | }; 70 | const serverResponse304 = { 71 | status: 304, 72 | }; 73 | const serverResponseHashNotFound = { 74 | status: HASH_NOT_FOUND, 75 | }; 76 | 77 | export { 78 | endpointURL, 79 | requestOptions, 80 | serverResponse200, 81 | serverResponse304, 82 | serverResponseHashNotFound, 83 | }; 84 | -------------------------------------------------------------------------------- /demo/client/Containers/Info.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import graphql_rest from '../../images/graphql-versus-rest.png'; 3 | import bothWorld from '../../images/bestofbothworld.png'; 4 | import hacheql_image from '../../images/hacheQL-image.png'; 5 | import npm from '../../images/npm-logo.png'; 6 | 7 | // Should return a narrative on HacheQL and OspreyLabs 8 | export default function info() { 9 | return ( 10 |
11 |
12 |

HacheQL is a lightweight, open source JavaScript library that provides an HTTP caching solution for GraphQL. Download our package and get started:

13 | 14 | 15 | 16 |

GraphQL provides an alternative to traditional RESTful architecture, emphasizing customization over optimization. This tradeoff highlights one of the prevalent shortcomings of GraphQL architecture: its rocky relationship with caching.

17 |
18 | 19 | By Jordan Panasewicz, Medium.com 20 |
21 |

HacheQL acts like a courier between your client and server. On the client, HacheQL takes a GraphQL request and sends it to the server in a way that makes the response HTTP cacheable. Subsequent requests from the client for the same data can be fulfilled from the browser or proxy caches.

22 | 23 |
24 | 25 |
26 |

With HacheQL you can have "the best of both worlds", allowing you to make use of the native caching mechanism over HTTP that is accessible in traditional RESTful architecture.

27 |
28 | 29 |
30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /library/hacheql.js: -------------------------------------------------------------------------------- 1 | import sha1 from 'sha1'; 2 | /** 3 | * @module hacheQL 4 | */ 5 | 6 | // Uncacheable GraphQl operation types - HacheQL will pass unaffected 7 | const uncacheable = { mutation: true, subscription: true }; 8 | 9 | /** 10 | * @param {string} endpoint - the string representing the endpoint the client would like to query. 11 | * @param {Object} options - options object corresponding to the fetch API specification. 12 | * @returns {Promise} - promise that resolves to the value returned either from the cache or the server, 13 | * or terminates in an error, unless the error is that the server does not recognize our query param, 14 | * in which case the promise does not resolve until a second fetch is sent and returned. 15 | */ 16 | 17 | function hacheQL(endpoint, options) { 18 | // If the body is undefined then let the request pass through as is 19 | if (!Object.hasOwn(options, 'body')) { 20 | return fetch(endpoint, options); 21 | } 22 | // Check if operation type is uncacheable, if so pass through as is 23 | const { query } = JSON.parse(options.body); 24 | const operationType = query.split('{')[0].trim(); 25 | if (Object.hasOwn(uncacheable, operationType)) { 26 | return fetch(endpoint, options); 27 | } 28 | // Reconstruct request as a GET request to make response HTTP cacheable 29 | // Hash body to store with URL constraint 30 | const newOpts = { ...options, method: 'GET' }; 31 | const HASH = sha1(newOpts.body); 32 | delete newOpts.body; 33 | // Construct new Promise to allow wrapper function to behave as a normal fetch 34 | return new Promise((resolve, reject) => { 35 | fetch(`${endpoint}/?hash=${HASH}`, newOpts) 36 | .then((data) => { 37 | // Status 303 indicates that hash is not found in server cache 38 | // Upon receipt, send original request as follow-up 39 | if (data.status === 303) { 40 | fetch(`${endpoint}/?hash=${HASH}`, options) 41 | .then((res) => resolve(res)) 42 | .catch((altErr) => reject(altErr)); 43 | } else { 44 | resolve(data); 45 | } 46 | }) 47 | .catch((err) => { 48 | reject(err); 49 | }); 50 | }); 51 | } 52 | 53 | export default hacheQL; 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Database files 10 | demo/server/data/* 11 | demo/*.rdb 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | demo/build/* 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # DS Store 111 | .DS_Store 112 | -------------------------------------------------------------------------------- /library/tests/mockFunctions.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { jest } from '@jest/globals'; 3 | import { 4 | serverResponse200, 5 | serverResponse304, 6 | serverResponseHashNotFound, 7 | } from './mockReqRes'; 8 | 9 | // MOCK FUNCTIONS ============================================== 10 | // These functions are used in the tests in place of the actual fetch API. 11 | 12 | // Returns an object with metadata about the HTTP request being sent. 13 | const getFetchRequestProfile = jest.fn((endpoint, { method, headers, body }) => { 14 | const fetchRequest = {}; 15 | if (endpoint !== undefined) fetchRequest.endpoint = endpoint; 16 | if (method !== undefined) fetchRequest.method = method; 17 | if (headers !== undefined) fetchRequest.headers = headers; 18 | if (body !== undefined) fetchRequest.body = body; 19 | 20 | return Promise.resolve(fetchRequest); 21 | }); 22 | 23 | // Imitates server responses when the requested hash is not present in the server's cache. 24 | // When receiving a GET request: responds with status code 800. 25 | // When receiving a POST request: responds with status code 200 and a body of JSON-formatted data, imitating data from a database. 26 | const mockServer_HashNotFound = jest.fn((endpoint, options) => { 27 | if (options.method === 'GET') { 28 | return Promise.resolve(serverResponseHashNotFound); 29 | } 30 | if (options.method === 'POST') { 31 | return Promise.resolve(serverResponse200); 32 | } 33 | return Promise.resolve('Neither a GET nor POST request. Weird...'); 34 | }); 35 | 36 | // Imitates server responses when the requested hash is present in the server's cache. 37 | // When receiving a GET request: responds with status code 200 and a body of JSON-formatted data, imitating data from a database. 38 | const mockServer_HashFound = jest.fn(() => Promise.resolve(serverResponse200)); 39 | 40 | // Imitates server responses when the requested hash is present in the server's cache and the requested resource has not been modified. 41 | const mockServer_NotModified = jest.fn(() => Promise.resolve(serverResponse304)); 42 | 43 | // Simulates an error during the fetch API call. 44 | const mockErrorGET = jest.fn(() => Promise.reject(new Error('Ouch! GET me to a doctor!'))); 45 | const mockErrorPOST = jest.fn((endpoint, options) => { 46 | if (options.method === 'GET') { 47 | return Promise.resolve(serverResponseHashNotFound); 48 | } 49 | if (options.method === 'POST') { 50 | return Promise.reject(new Error('Yikes! This one\'s going to need a POSTmortem!')); 51 | } 52 | return Promise.resolve('Neither a GET nor POST request. Weird...'); 53 | }); 54 | 55 | export { 56 | getFetchRequestProfile, 57 | mockServer_HashNotFound, 58 | mockServer_HashFound, 59 | mockServer_NotModified, 60 | mockErrorGET, 61 | mockErrorPOST, 62 | }; 63 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Thank you for helping to maintain and improve HacheQL! 4 | 5 | ## Reporting Bugs 6 | 7 | Before reporting a bug, please check the list of [open issues](https://github.com/oslabs-beta/hacheQL/issues) to see if the bug has already been reported. To report a new bug, open a [new issue](https://github.com/oslabs-beta/hacheQL/issues/new). 8 | 9 | ## Requesting Features 10 | 11 | Before requesting a feature, please check the list of [open issues](https://github.com/oslabs-beta/hacheQL/issues) to see if the feature has already been requested. If it has, leave a comment to show your support for the idea. If you plan to implement the feature, say so in your comment. 12 | 13 | To make a new feature request, open a [new issue](https://github.com/oslabs-beta/hacheQL/issues/new). If you plan to implement the feature, include a comment saying so. 14 | 15 | ## Getting Started 16 | 17 | Before starting to work, check the [open issues](https://github.com/oslabs-beta/hacheQL/issues) to see if anyone is already working on the same bug/feature. If someone is, you can leave a comment offering to help, or you can pick a different issue to work on. If someone has claimed an issue but doesn't respond to your comments and hasn't left an update for two weeks, it's okay to start working on it yourself. 18 | 19 | Once you've settled on an issue to work on: 20 | 21 | 1. Fork the project and clone the forked repo to your computer. 22 | 2. Add the main repo as an upstream remote. 23 | ``` 24 | git remote add upstream https://github.com/oslabs-beta/hacheQL.git 25 | ``` 26 | 3. Run `npm install`. 27 | 4. Create your feature branch and give it a descriptive name. Begin the branch name with the ticket number of the issue you're working on. 28 | - Ex: `git checkout -b 42-feature/ice-cream-sundae-machine` 29 | 5. This project uses [test-driven development](https://www.agilealliance.org/glossary/tdd/#q=~(infinite~false~filters~(postType~(~'page~'post~'aa_book~'aa_event_session~'aa_experience_report~'aa_glossary~'aa_research_paper~'aa_video)~tags~(~'tdd))~searchTerm~'~sort~false~sortDirection~'asc~page~1)). Please write tests for your bugfix/feature BEFORE implementing it. Before implementing the bugfix/feature, all your tests should fail. After implementing it, all your tests should pass. 30 | - Our test suite is written with [Jest](https://jestjs.io/). 31 | 6. Write some code! 32 | - As you work, make frequent commits and leave comments on the Github issue about your progress. 33 | - Feel free to update the website to reflect a new feature, if it makes sense to do so. The source files are in the `demo/` directory. 34 | 35 | ## Making A Pull Request 36 | 37 | 1. Make sure that: 38 | - your bugfix/feature branch is up-to-date with the repo's main branch 39 | - your code follows the project's `eslint` configuration 40 | - your code passes the test suite (you did add tests, right??) 41 | 4. If you've changed the API, please update [Documentation.md](DOCUMENTATION.md). 42 | 5. Commit your changes. 43 | - Ex: `git commit -m 'Add ice cream sundae machine.'` 44 | 6. Push to your fork of the main repo. 45 | - Ex: `git push origin 42-feature/ice-cream-sundae-machine` 46 | 7. Open a pull request on GitHub. 47 | ![](pull-request.png) 48 | 49 | ## License 50 | By contributing, you agree that your contributions will be licensed under HacheQL's [MIT License](LICENSE). -------------------------------------------------------------------------------- /demo/client/Containers/Team.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import TeamMember from '../Components/TeamMember'; 4 | import conor from '../../images/Conor.jpeg'; 5 | import jasonC from '../../images/JasonC.jpeg'; 6 | import jasonL from '../../images/JasonL.jpeg'; 7 | import joey from '../../images/Joey.jpeg'; 8 | 9 | // Container for team member components 10 | export default function Team() { 11 | // for rendering components 12 | const teamMembers = [ 13 | { 14 | name: 'Conor Chinitz', 15 | profilePicture: conor, 16 | bio: 'Conor likes writing code that’s hard to write and easy to read. He also likes writing really good documentation. His favorite parts of working on HacheQL are writing automated tests with Jest and working with async/await in Node/Express. His favorite things when not working on HacheQL are playing board/card games, playing musical theater songs on the piano, and taking long hikes in the woods.', 17 | gitHub: 'https://github.com/conorchinitz', 18 | linkedin: 'https://www.linkedin.com/in/conorchinitz/', 19 | }, 20 | { 21 | name: 'Joey Torsella', 22 | profilePicture: joey, 23 | bio: 'Joey is a software engineer who is driven to understand how systems of all kinds work. In the work he did on HacheQL, he especially enjoyed engineering middleware to function under a wide range of use conditions and thinking systematically about what those conditions might be. When not programming, he enjoys fitness and philosophy.', 24 | gitHub: 'https://github.com/neovimnovum', 25 | linkedin: 'https://www.linkedin.com/in/joseph-r-torsella/', 26 | }, 27 | { 28 | name: 'Jason Chan', 29 | profilePicture: jasonC, 30 | bio: 'Jason is a software engineer based in NYC who is passionate about learning new technology and hopes to make a meaningful impact to society. Prior to his programming journey, he had over 5 years of work experience as an auditor working with clients in financial services to expand his understanding of their business. During his free time, he enjoys being active through partaking in Tonehouse workout classes, playing basketball, and spontaneous travels.', 31 | gitHub: 'https://github.com/JayC1765', 32 | linkedin: 'https://www.linkedin.com/in/jason-chan-cpa-106921bb/', 33 | }, 34 | { 35 | name: 'Jason Lin', 36 | profilePicture: jasonL, 37 | bio: "Jason Lin is a full-stack software engineer based in New York City with experience in React, GraphQL, Node.JS and Express. His current fascination is the implication of machine learning in web development. In his downtime, you can find Jason tending to his cats, practicing his knife skills in the kitchen or finding himself immerse in fantasy RPG's.", 38 | gitHub: 'https://github.com/jvenlin', 39 | linkedin: 'https://www.linkedin.com/in/jplin/', 40 | }, 41 | ]; 42 | 43 | const teamMap = teamMembers.map((member) => ( 44 | 52 | )); 53 | return ( 54 |
55 |

Meet The Team!

56 |
57 | {teamMap} 58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /demo/client/styles.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Cabin&family=Lato:wght@300&family=League+Spartan:wght@500&display=swap'); 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | font-family: 'League Spartan', sans-serif; 7 | color: gray; 8 | } 9 | 10 | h2 { 11 | text-align: center; 12 | font-size: 72px; 13 | background: -webkit-linear-gradient(left, #333, #eee, #333); 14 | background: -o-linear-gradient(right, #333, #eee, #333); 15 | background: -moz-linear-gradient(right, #333, #eee, #333); 16 | background: linear-gradient(to right, #333, #eee, #333); 17 | -webkit-background-clip: text; 18 | -webkit-text-fill-color: transparent; 19 | margin: 20px; 20 | } 21 | 22 | /* Info Section */ 23 | .info-container { 24 | margin: auto; 25 | left: 50%; 26 | right: 50%; 27 | max-width: 1000px; 28 | display: flex; 29 | flex-direction: column; 30 | justify-content: center; 31 | padding: 0 10px; 32 | margin-bottom: 25px; 33 | font-size: 20px; 34 | border-bottom: 2px solid lightgray; 35 | } 36 | 37 | .npm-logo { 38 | width: 100px; 39 | height: auto; 40 | display: block; 41 | margin-left: auto; 42 | margin-right: auto; 43 | } 44 | 45 | .graphql-rest-image { 46 | max-width: 100vw; 47 | width: 100%; 48 | margin-left: auto; 49 | margin-right: auto; 50 | } 51 | .hacheql-image-container { 52 | text-align: center; 53 | max-width: 100vw; 54 | } 55 | .hacheql-image { 56 | width: 50%; 57 | height: 50%; 58 | } 59 | 60 | .both-image-container { 61 | text-align: center; 62 | max-width: 1000px; 63 | } 64 | 65 | .both-image { 66 | width: 50%; 67 | height: 50%; 68 | } 69 | 70 | cite { 71 | font-size: smaller; 72 | } 73 | /* Demo Section */ 74 | button { 75 | border: 1px solid darkgray; 76 | border-radius: 5px; 77 | padding: auto; 78 | color: black; 79 | padding: 15px 32px; 80 | text-align: center; 81 | text-decoration: none; 82 | display: inline-block; 83 | font-size: 16px; 84 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 85 | } 86 | 87 | header { 88 | border-bottom: 2px solid lightgray; 89 | margin-bottom: 20px; 90 | font-weight: normal; 91 | letter-spacing: 7.2px; 92 | } 93 | 94 | .demo { 95 | display: flex; 96 | flex-direction: column; 97 | justify-content: center; 98 | align-items: center; 99 | } 100 | 101 | .graph { 102 | background: black; 103 | border: 1px solid gray; 104 | border-radius: 5px; 105 | height: 25vh; 106 | width: 30vw; 107 | max-width: 300px; 108 | } 109 | 110 | .scroll-view { 111 | width: 50vw; 112 | max-width: 800px; 113 | } 114 | 115 | .dashboard { 116 | width: 30vw; 117 | max-width: 300px; 118 | border: 1px solid gray; 119 | border-radius: 5px; 120 | height:25vh; 121 | background-color: black; 122 | color: gold; 123 | display:flex; 124 | flex-direction: column; 125 | justify-content: flex-start; 126 | } 127 | 128 | .demo-display { 129 | display: flex; 130 | gap: 0px; 131 | } 132 | 133 | .query-buttons { 134 | margin-top: 20px; 135 | margin-left: auto; 136 | margin-right: auto; 137 | display: flex; 138 | justify-content: space-evenly; 139 | width: 80vw; 140 | max-width: 1100px; 141 | } 142 | 143 | button { 144 | width: 30%; 145 | text-align: center; 146 | padding-left: 0px; 147 | padding-right: 0px; 148 | max-height: 100px; 149 | max-width: 200px; 150 | } 151 | 152 | .dropdown { 153 | border: 1px solid darkgray; 154 | border-radius: 5px; 155 | padding-left: 0px; 156 | padding-right: 0px; 157 | color: black; 158 | font-size: 16px; 159 | width: 30%; 160 | max-height: 100px; 161 | max-width: 200px; 162 | } 163 | 164 | /* Team Section */ 165 | .team-container { 166 | margin-bottom: 50px; 167 | margin-top: 100px; 168 | border-top: 2px solid lightgray; 169 | } 170 | 171 | 172 | .profile { 173 | width: 100%; 174 | max-width: 275px; 175 | height: 40%; 176 | text-align: center; 177 | color: gray; 178 | } 179 | 180 | .profile-pic { 181 | height: 200px; 182 | border-radius: 50%; 183 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 184 | } 185 | 186 | .profile-name { 187 | font-size: large; 188 | color: black; 189 | } 190 | 191 | .team { 192 | display: flex; 193 | flex-direction: row; 194 | justify-content: center; 195 | flex-wrap: wrap; 196 | } 197 | 198 | .social-logo { 199 | width: 50px; 200 | max-width: 100px; 201 | } 202 | 203 | #logo_main { 204 | width: 100vw; 205 | max-width: 500px 206 | } 207 | -------------------------------------------------------------------------------- /demo/favicon.svg: -------------------------------------------------------------------------------- 1 | image/svg+xml 2 | 12 | 13 | -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | ![](demo/images/Logo.png) 2 | 3 | # Welcome to [HacheQL](https://www.hacheql.org/) · [![License badge](https://img.shields.io/badge/license-MIT-informational)](LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)]() 4 | HacheQL is a JavaScript library that brings HTTP caching to your GraphQL API. 5 | 6 | ## Features 7 | - Automatically creates and caches persisted GraphQL queries. 8 | - Integrates with any server running on Node.js. 9 | - Includes specialized support for servers written with Express.js. 10 | - Supports caching with Redis or in the server's local memory. 11 | 12 | Check out [our demo site](https://www.hacheql.org/) to see what HacheQL can do. 13 | 14 |
15 | 16 | ## Getting Started 17 | HacheQL sends GraphQL requests in a way that makes the response HTTP cacheable, so subsequent requests for the same data can be fulfilled from a cache in the browser or on a proxy server. 18 | 19 | HacheQL works by hashing the contents of GraphQL queries and caching key-value pairs of the form \: \ on the server side. 20 | 21 | Let's get the server set up first. 22 | 23 |
24 | 25 | ## Server-side HacheQL 26 | 27 | HacheQL works with any Node.js server, but it includes specialized support for servers written in Express.js. If your project uses Express, see the next section, titled 'Server-side HacheQL - with Express.' 28 | 29 | If you use vanilla Node or a different Node framework, you're in the right place. 30 | 31 |
Expand for instructions 32 |
33 | 34 | 1. Install HacheQL with npm. 35 | 36 | ``` 37 | npm install hacheql 38 | ``` 39 | 40 | 2. Import `nodeHacheQL` in files that handle GraphQL requests. 41 | 42 | ```javascript 43 | import { nodeHacheQL } from 'hacheql/server'; 44 | ``` 45 | 46 | 3. Call `nodeHacheQL` as the first step in handling GraphQL requests. 47 | ```javascript 48 | server.on('request', async (req, res) => { 49 | if (request.url === '/graphql') { 50 | try { 51 | const query = await nodeHacheQL(req, res, { redis: redisClient }); 52 | const data = await database.query(query); 53 | res.end(data); 54 | } catch (error) { 55 | /* error handling logic */ 56 | } 57 | } 58 | }); 59 | ``` 60 | > See the [Documentation](DOCUMENTATION.md#nodehacheql) for more detail on how to use this function. 61 | 62 | That's all for the server! See [Client-side HacheQL](README.md#client-side-hacheql) for the next steps. 63 |
64 | 65 |
66 | 67 | ## Server-side HacheQL - with Express 68 | If your project uses Express, this section is for you. If not, see the previous section, titled 'Server-side HacheQL.' 69 | 70 |
Expand for instructions 71 |
72 | 73 | 1. Install HacheQL with npm. 74 | 75 | ``` 76 | npm install hacheql 77 | ``` 78 | 79 | 2. Import `expressHacheQL` and `httpCache` in files that handle GraphQL requests. 80 | 81 | ```javascript 82 | import { expressHacheQL, httpCache } from 'hacheql/server'; 83 | ``` 84 | 85 | 3. Use `expressHacheQL` as the first piece of middleware in routes that handle GraphQL requests. 86 | 87 | If you want to cache using Redis, pass `expressHacheQL` an object with a property `redis` whose value is a reference to your Redis client. 88 | 89 | ```javascript 90 | app.use('/graphql', expressHacheQL({ redis: }), /* other middleware */); 91 | ``` 92 | 93 | If you aren't using Redis, don't pass any arguments to `expressHacheQL` and it will automatically use the server's memory for caching. 94 | 95 | ```javascript 96 | app.use('/graphql', expressHacheQL(), /* other middleware */); 97 | ``` 98 | 99 | 4. Use `httpCache` prior to sending a response. 100 | 101 | ```javascript 102 | app.use( 103 | '/graphql', 104 | expressHacheQL(), 105 | httpCache(), 106 | graphqlHTTP({ schema, graphiql: true,}), 107 | ); 108 | ``` 109 | 110 | 5. `expressHacheQL` relies on Express's built-in [express.json()](https://expressjs.com/en/api.html#express.json) method for parsing JSON-encoded request bodies. If you don't have it set up yet, add the following toward the top of your main server file: 111 | ```javascript 112 | app.use(express.json()) 113 | ``` 114 | 115 | That's all for the server! Let's set up the client. 116 |
117 | 118 |
119 | 120 | ## Client-side HacheQL 121 | HacheQL's client side is the same whether or not your server uses Express, and it's very simple to set up. 122 | > Note: It's possible to implement HacheQL on the client-side gradually. You can set it up for some GraphQL routes and not for others, and everything will still work. 123 | 124 |
Expand for instructions 125 |
126 | 127 | 1. Import `hacheQL` in files that send requests to a GraphQL API. 128 | 129 | ```javascript 130 | import { hacheQL } from 'hacheql'; 131 | ``` 132 | 133 | 2. HacheQL is designed to make it easy to switch over from the Fetch API. All you have to do is replace the word `fetch` with the word `hacheQL`. The arguments to the function remain exactly the same. 134 | 135 | For example, here's how you might send a GraphQL request using the Fetch API: 136 | 137 | ```javascript 138 | fetch('/graphql', { 139 | method: 'POST', 140 | headers: { 'Content-Type': 'application/graphql' }, 141 | body: '{ hero { name } }' 142 | }) 143 | .then(/* code */) 144 | ``` 145 | 146 | And here's what that same request looks like using HacheQL: 147 | 148 | ```javascript 149 | hacheQL('/graphql', { 150 | method: 'POST', 151 | headers: { 'Content-Type': 'application/graphql' }, 152 | body: '{ hero { name } }' 153 | }) 154 | .then(/* code */) 155 | ``` 156 | 157 | Simply replace `fetch` with `hacheQL` wherever the client-side code queries the GraphQL API, and you're done! You've set up HTTP caching for your GraphQL requests. 158 |
159 | 160 |
161 | 162 | ## Other Stuff 163 | 164 | Check out the [Documentation](DOCUMENTATION.md) for more sample usage, technical details, and ways to customize behavior. 165 | 166 | If you'd like to contribute, read our [Contributing Guide](CONTRIBUTING.md). 167 | 168 |
169 | 170 | ## License 171 | HacheQL is [MIT Licensed](LICENSE). -------------------------------------------------------------------------------- /demo/server/graphql/types.js: -------------------------------------------------------------------------------- 1 | import { 2 | Kind, 3 | GraphQLSchema, 4 | GraphQLObjectType, 5 | GraphQLList, 6 | GraphQLNonNull, 7 | GraphQLInt, 8 | GraphQLString, 9 | GraphQLScalarType, 10 | } 11 | from 'graphql'; 12 | 13 | import db from '../models/starWarsModel'; 14 | 15 | const bigIntType = new GraphQLScalarType({ 16 | name: 'BigInt', 17 | serialize: (val) => val, 18 | }); 19 | 20 | const dateType = new GraphQLScalarType({ 21 | name: 'Date', 22 | parseValue: (val) => (new Date(val)).toISOString(), 23 | serialize: (date) => date.toDateString(), 24 | parseLiteral: (ast) => { 25 | if (ast.kind === Kind.INT) { 26 | return (new Date(+ast.value)).toISOString(); 27 | } 28 | return null; 29 | }, 30 | }); 31 | 32 | const personType = new GraphQLObjectType({ 33 | name: 'Person', 34 | fields: { 35 | _id: { type: bigIntType }, 36 | name: { type: new GraphQLNonNull(GraphQLString) }, 37 | mass: { type: GraphQLString }, 38 | hair_color: { type: GraphQLString }, 39 | skin_color: { type: GraphQLString }, 40 | eye_color: { type: GraphQLString }, 41 | birth_year: { type: GraphQLString }, 42 | gender: { type: GraphQLString }, 43 | species_id: { type: bigIntType }, 44 | homeworld_id: { type: bigIntType }, 45 | }, 46 | }); 47 | 48 | const filmType = new GraphQLObjectType({ 49 | name: 'Film', 50 | fields: { 51 | _id: { type: bigIntType }, 52 | title: { type: new GraphQLNonNull(GraphQLString) }, 53 | episode_id: { type: new GraphQLNonNull(GraphQLInt) }, 54 | opening_crawl: { type: new GraphQLNonNull(GraphQLString) }, 55 | director: { type: new GraphQLNonNull(GraphQLString) }, 56 | producer: { type: new GraphQLNonNull(GraphQLString) }, 57 | release_date: { type: new GraphQLNonNull(dateType) }, 58 | }, 59 | }); 60 | 61 | const vesselType = new GraphQLObjectType({ 62 | name: 'Vessel', 63 | fields: { 64 | _id: { type: bigIntType }, 65 | name: { type: new GraphQLNonNull(GraphQLString) }, 66 | manufacturer: { type: GraphQLString }, 67 | model: { type: GraphQLString }, 68 | vessel_type: { type: new GraphQLNonNull(GraphQLString) }, 69 | vessel_class: { type: new GraphQLNonNull(GraphQLString) }, 70 | cost_in_credits: { type: bigIntType }, 71 | length: { type: GraphQLString }, 72 | max_atmosphering_speed: { type: GraphQLString }, 73 | crew: { type: GraphQLInt }, 74 | passengers: { type: GraphQLInt }, 75 | cargo_capacity: { type: GraphQLString }, 76 | consumables: { type: GraphQLString }, 77 | }, 78 | }); 79 | 80 | const speciesType = new GraphQLObjectType({ 81 | name: 'Species', 82 | fields: { 83 | _id: { type: bigIntType }, 84 | name: { type: new GraphQLNonNull(GraphQLString) }, 85 | classification: { type: GraphQLString }, 86 | average_height: { type: GraphQLString }, 87 | average_lifespan: { type: GraphQLString }, 88 | hair_colors: { type: GraphQLString }, 89 | skin_colors: { type: GraphQLString }, 90 | eye_colors: { type: GraphQLString }, 91 | language: { type: GraphQLString }, 92 | homeworld_id: { type: bigIntType }, 93 | }, 94 | }); 95 | 96 | const planetType = new GraphQLObjectType({ 97 | name: 'Planet', 98 | fields: { 99 | _id: { type: bigIntType }, 100 | name: { type: GraphQLString }, 101 | rotation_period: { type: GraphQLInt }, 102 | orbital_period: { type: GraphQLInt }, 103 | diameter: { type: GraphQLInt }, 104 | climate: { type: GraphQLString }, 105 | gravity: { type: GraphQLString }, 106 | terrain: { type: GraphQLString }, 107 | surface_water: { type: GraphQLString }, 108 | population: { type: bigIntType }, 109 | }, 110 | }); 111 | 112 | const queryType = new GraphQLObjectType({ 113 | name: 'Query', 114 | fields: { 115 | films: { 116 | type: new GraphQLList(filmType), 117 | resolve: () => db.query('SELECT * FROM films;').then((result) => result.rows).catch((e) => e), 118 | }, 119 | planets: { 120 | type: new GraphQLList(planetType), 121 | resolve: () => db.query('SELECT * FROM planets;').then((result) => result.rows).catch((e) => e), 122 | }, 123 | species: { 124 | type: new GraphQLList(speciesType), 125 | resolve: () => db.query('SELECT * FROM species;').then((result) => result.rows).catch((e) => e), 126 | }, 127 | people: { 128 | type: new GraphQLList(personType), 129 | resolve: () => db.query('SELECT * FROM people;').then((result) => result.rows).catch((e) => e), 130 | }, 131 | vessels: { 132 | type: new GraphQLList(vesselType), 133 | resolve: () => db.query('SELECT * FROM vessels;').then((result) => result.rows).catch((e) => e), 134 | }, 135 | }, 136 | }); 137 | 138 | const mutationType = new GraphQLObjectType({ 139 | name: 'Mutation', 140 | fields: { 141 | addFilm: { 142 | type: filmType, 143 | args: { 144 | _id: { type: new GraphQLNonNull(bigIntType) }, 145 | title: { type: new GraphQLNonNull(GraphQLString) }, 146 | episode_id: { type: new GraphQLNonNull(GraphQLInt) }, 147 | opening_crawl: { type: new GraphQLNonNull(GraphQLString) }, 148 | director: { type: new GraphQLNonNull(GraphQLString) }, 149 | producer: { type: new GraphQLNonNull(GraphQLString) }, 150 | release_date: { type: new GraphQLNonNull(dateType) }, 151 | }, 152 | resolve: (_, { 153 | _id, 154 | title, 155 | episode_id: episodeId, 156 | opening_crawl: openingCrawl, 157 | director, 158 | producer, 159 | release_date: releaseDate, 160 | }) => ( 161 | db.query('INSERT INTO films(_id, title, episode_id, opening_crawl, director, producer, release_date) VALUES($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (_id) DO UPDATE SET title=$2, episode_id=$3, opening_crawl=$4, director=$5, producer=$6, release_date=$7 RETURNING *;', [_id, title, episodeId, openingCrawl, director, producer, releaseDate]).then((result) => result.rows[0]).catch((e) => e) 162 | ), 163 | }, 164 | }, 165 | }); 166 | 167 | const schema = new GraphQLSchema({ 168 | query: queryType, 169 | mutation: mutationType, 170 | }); 171 | 172 | // Starship specs? 173 | 174 | export default schema; 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](demo/images/Logo.png) 2 | 3 | # Welcome to [HacheQL](https://www.hacheql.org/) · [![License badge](https://img.shields.io/badge/license-MIT-informational)](LICENSE) [![npm badge](https://img.shields.io/npm/v/hacheql)]() [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)]() 4 | HacheQL is a JavaScript library that brings HTTP caching to your GraphQL API. 5 | 6 | ## Features 7 | - Automatically creates and caches persisted GraphQL queries. 8 | - Integrates with any server running on Node.js. 9 | - Includes specialized support for servers written with Express.js. 10 | - Supports caching with Redis or in the server's local memory. 11 | 12 | Check out [our demo site](https://www.hacheql.org/) to see what HacheQL can do. 13 | 14 |
15 | 16 | ## Getting Started 17 | HacheQL sends GraphQL requests in a way that makes the response HTTP cacheable, so subsequent requests for the same data can be fulfilled from a cache in the browser or on a proxy server. 18 | 19 | HacheQL works by hashing the contents of GraphQL queries and caching key-value pairs of the form \: \ on the server side. 20 | 21 | Let's get the server set up first. 22 | 23 |
24 | 25 | ## Server-side HacheQL 26 | 27 | HacheQL works with any Node.js server, but it includes specialized support for servers written in Express.js. If your project uses Express, see the next section, titled 'Server-side HacheQL - with Express.' 28 | 29 | If you use vanilla Node or a different Node framework, you're in the right place. 30 | 31 |
Expand for instructions 32 |
33 | 34 | 1. Install HacheQL with npm. 35 | 36 | ``` 37 | npm install hacheql 38 | ``` 39 | 40 | 2. Import `nodeHacheQL` in files that handle GraphQL requests. 41 | 42 | ```javascript 43 | import { nodeHacheQL } from 'hacheql/server'; 44 | ``` 45 | 46 | 3. Call `nodeHacheQL` as the first step in handling GraphQL requests. 47 | ```javascript 48 | server.on('request', async (req, res) => { 49 | if (request.url === '/graphql') { 50 | try { 51 | const query = await nodeHacheQL(req, res, { redis: redisClient }); 52 | const data = await database.query(query); 53 | res.end(data); 54 | } catch (error) { 55 | /* error handling logic */ 56 | } 57 | } 58 | }); 59 | ``` 60 | > See the [Documentation](DOCUMENTATION.md#nodehacheql) for more detail on how to use this function. 61 | 62 | That's all for the server! See [Client-side HacheQL](README.md#client-side-hacheql) for the next steps. 63 |
64 | 65 |
66 | 67 | ## Server-side HacheQL - with Express 68 | If your project uses Express, this section is for you. If not, see the previous section, titled 'Server-side HacheQL.' 69 | 70 |
Expand for instructions 71 |
72 | 73 | 1. Install HacheQL with npm. 74 | 75 | ``` 76 | npm install hacheql 77 | ``` 78 | 79 | 2. Import `expressHacheQL` and `httpCache` in files that handle GraphQL requests. 80 | 81 | ```javascript 82 | import { expressHacheQL, httpCache } from 'hacheql/server'; 83 | ``` 84 | 85 | 3. Use `expressHacheQL` as the first piece of middleware in routes that handle GraphQL requests. 86 | 87 | If you want to cache using Redis, pass `expressHacheQL` an object with a property `redis` whose value is a reference to your Redis client. 88 | 89 | ```javascript 90 | app.use('/graphql', expressHacheQL({ redis: }), /* other middleware */); 91 | ``` 92 | 93 | If you aren't using Redis, don't pass any arguments to `expressHacheQL` and it will automatically use the server's memory for caching. 94 | 95 | ```javascript 96 | app.use('/graphql', expressHacheQL(), /* other middleware */); 97 | ``` 98 | 99 | 4. Use `httpCache` prior to sending a response. 100 | 101 | ```javascript 102 | app.use( 103 | '/graphql', 104 | expressHacheQL(), 105 | httpCache(), 106 | graphqlHTTP({ schema, graphiql: true,}), 107 | ); 108 | ``` 109 | 110 | 5. `expressHacheQL` relies on Express's built-in [express.json()](https://expressjs.com/en/api.html#express.json) method for parsing JSON-encoded request bodies. If you don't have it set up yet, add the following toward the top of your main server file: 111 | ```javascript 112 | app.use(express.json()) 113 | ``` 114 | 115 | That's all for the server! Let's set up the client. 116 |
117 | 118 |
119 | 120 | ## Client-side HacheQL 121 | HacheQL's client side is the same whether or not your server uses Express, and it's very simple to set up. 122 | > Note: It's possible to implement HacheQL on the client-side gradually. You can set it up for some GraphQL routes and not for others, and everything will still work. 123 | 124 |
Expand for instructions 125 |
126 | 127 | 1. Import `hacheQL` in files that send requests to a GraphQL API. 128 | 129 | ```javascript 130 | import { hacheQL } from 'hacheql'; 131 | ``` 132 | 133 | 2. HacheQL is designed to make it easy to switch over from the Fetch API. All you have to do is replace the word `fetch` with the word `hacheQL`. The arguments to the function remain exactly the same. 134 | 135 | For example, here's how you might send a GraphQL request using the Fetch API: 136 | 137 | ```javascript 138 | fetch('/graphql', { 139 | method: 'POST', 140 | headers: { 'Content-Type': 'application/graphql' }, 141 | body: '{ hero { name } }' 142 | }) 143 | .then(/* code */) 144 | ``` 145 | 146 | And here's what that same request looks like using HacheQL: 147 | 148 | ```javascript 149 | hacheQL('/graphql', { 150 | method: 'POST', 151 | headers: { 'Content-Type': 'application/graphql' }, 152 | body: '{ hero { name } }' 153 | }) 154 | .then(/* code */) 155 | ``` 156 | 157 | Simply replace `fetch` with `hacheQL` wherever the client-side code queries the GraphQL API, and you're done! You've set up HTTP caching for your GraphQL requests. 158 |
159 | 160 |
161 | 162 | ## Other Stuff 163 | 164 | Check out the [Documentation](DOCUMENTATION.md) for more sample usage, technical details, and ways to customize behavior. 165 | 166 | If you'd like to contribute, read our [Contributing Guide](CONTRIBUTING.md). 167 | 168 |
169 | 170 | ## License 171 | HacheQL is [MIT Licensed](LICENSE). -------------------------------------------------------------------------------- /library/tests/fetch.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { describe, jest } from '@jest/globals'; 3 | import sha1 from 'sha1'; 4 | import hacheQL from '../hacheql'; 5 | import { 6 | endpointURL, 7 | requestOptions, 8 | serverResponse200, 9 | serverResponse304, 10 | } from './mockReqRes'; 11 | import { 12 | getFetchRequestProfile, 13 | mockServer_HashNotFound, 14 | mockServer_HashFound, 15 | mockServer_NotModified, 16 | mockErrorGET, 17 | mockErrorPOST, 18 | } from './mockFunctions'; 19 | 20 | import { HASH_NOT_FOUND } from './HTTPStatusCodes'; 21 | 22 | /** 23 | * Function signature 24 | * hacheQL(endpoint, options) 25 | 26 | * This function signature is designed to mimic the fetch API. (In fact, the function uses the fetch API under the hood.) 27 | * https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters 28 | 29 | * @param {string} endpoint - The endpoint for the GraphQL requests. Analogous to the fetch API's 'resource' parameter. 30 | * @param {object} options - An object containing settings for the request; for example, the HTTP request method, headers, and request body. Analogous to the fetch API's 'init' parameter. All valid properties for the fetch API's 'init' object are valid properties for this function's options object. 31 | * @returns {promise} - A Promise that resolves to a Response object from the server, or rejects with an Error object. 32 | */ 33 | 34 | // TESTS ======================================================= 35 | describe('hacheQL() - client-side wrapper for fetch()', () => { 36 | describe('Should return a promise', () => { 37 | test('Should return a promise', async () => { 38 | expect.assertions(1); 39 | global.fetch = () => Promise.resolve(true); 40 | const immediateValue = hacheQL(endpointURL, requestOptions); 41 | expect(immediateValue).toBeInstanceOf(Promise); 42 | }); 43 | }); 44 | 45 | describe('Should default to sending a GET request with the following characteristics:', () => { 46 | let hacheQLFetchRequestProfile; 47 | 48 | beforeAll(async () => { 49 | // Mock the fetch API. 50 | global.fetch = getFetchRequestProfile; 51 | hacheQLFetchRequestProfile = await hacheQL(endpointURL, requestOptions); 52 | }); 53 | 54 | test('Should make a GET request to the passed-in endpoint.', async () => { 55 | expect.assertions(2); 56 | expect(getFetchRequestProfile).toBeCalledTimes(1); 57 | expect(hacheQLFetchRequestProfile.method).toBe('GET'); 58 | }); 59 | 60 | test('The * entire * request body should be hashed with SHA-1 and appended to the endpoint URL as a query parameter with a key of \'hash\'.', async () => { 61 | expect.assertions(1); 62 | const HASH = sha1(requestOptions.body); 63 | expect(hacheQLFetchRequestProfile.endpoint).toBe(`${endpointURL}/?hash=${HASH}`); 64 | }); 65 | 66 | test('The headers passed to the fetch API should match those passed to the hacheQL function.', async () => { 67 | expect.assertions(2); 68 | expect(hacheQLFetchRequestProfile.headers).toBe(requestOptions.headers); 69 | expect(Object.hasOwn(hacheQLFetchRequestProfile, 'body')).toBe(false); 70 | }); 71 | 72 | test('The body passed to the hacheQL function should NOT be included in the options object passed to the fetch API.', async () => { 73 | expect.assertions(1); 74 | expect(Object.hasOwn(hacheQLFetchRequestProfile, 'body')).toBe(false); 75 | }); 76 | 77 | test(`If it receives ${HASH_NOT_FOUND}, followup request options should be identical to the passed in request options`, async () => { 78 | global.fetch = mockServer_HashNotFound; 79 | await hacheQL(endpointURL, { ...requestOptions, method: 'DELETE' }); 80 | expect(mockServer_HashNotFound.mock.calls.length).toBe(2); 81 | expect(mockServer_HashNotFound.mock.calls[1][1].method).toBe('DELETE'); 82 | }); 83 | }); 84 | 85 | describe('Should take appropriate action based on different server responses.', () => { 86 | beforeEach(() => { 87 | jest.clearAllMocks(); 88 | }); 89 | 90 | test(`Should make a followup request if the initial GET request receives a ${HASH_NOT_FOUND} response.`, async () => { 91 | expect.assertions(2); 92 | global.fetch = mockServer_HashNotFound; 93 | const response = await hacheQL(endpointURL, requestOptions); 94 | expect(mockServer_HashNotFound).toBeCalledTimes(2); 95 | expect(response).toEqual(serverResponse200); 96 | }); 97 | 98 | test('Should return data from the server if the response has a status code of 200.', async () => { 99 | expect.assertions(2); 100 | global.fetch = mockServer_HashFound; 101 | const response = await hacheQL(endpointURL, requestOptions); 102 | expect(mockServer_HashFound).toBeCalledTimes(1); 103 | expect(response).toEqual(serverResponse200); 104 | }); 105 | 106 | test('Should return data from the server if the response has a status code of 304.', async () => { 107 | expect.assertions(1); 108 | global.fetch = mockServer_NotModified; 109 | const response = await hacheQL(endpointURL, requestOptions); 110 | expect(response).toEqual(serverResponse304); 111 | }); 112 | }); 113 | 114 | describe('Should handle errors gracefully', () => { 115 | async function hacheQLAttempt() { 116 | try { 117 | const response = await hacheQL(endpointURL, requestOptions); 118 | return response; 119 | } catch (error) { 120 | return error; 121 | } 122 | } 123 | 124 | test('Should catch errors during a GET request', async () => { 125 | expect.assertions(1); 126 | global.fetch = mockErrorGET; 127 | 128 | // The Jest documentation suggests writing this test like this: 129 | // https://jestjs.io/docs/asynchronous#promises 130 | // return hacheQL(endpointURL, requestOptions).catch((error) => expect(error.message).toBe('Ouch! GET me to a doctor!')); 131 | 132 | // But ESLint recommends doing it this way instead: 133 | // https://github.com/jest-community/eslint-plugin-jest/blob/v26.1.3/docs/rules/no-conditional-expect.md 134 | // (See both the rest of this 'test' block and the top of this 'describe' block.) 135 | 136 | // Also, note that 'errors thrown inside asynchronous functions will act like uncaught errors.' 137 | // That tripped me up for a while. 138 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch#gotchas_when_throwing_errors 139 | 140 | const result = await (hacheQLAttempt()); 141 | expect(result).toEqual(new Error('Ouch! GET me to a doctor!')); 142 | }); 143 | 144 | test('Should catch errors during a followup POST request', async () => { 145 | expect.assertions(1); 146 | global.fetch = mockErrorPOST; 147 | 148 | const result = await (hacheQLAttempt()); 149 | expect(result).toEqual(new Error('Yikes! This one\'s going to need a POSTmortem!')); 150 | }); 151 | }); 152 | 153 | describe('Should send uncacheable GraphQL strings as hashless posts.', () => { 154 | 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /demo/client/Containers/Demo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import hacheQL from 'hacheql'; 3 | import Graph from '../Components/Graph'; 4 | import Dashboard from '../Components/Dashboard'; 5 | import Scrollview from '../Components/Scrollview'; 6 | 7 | function Demo() { 8 | const [fetchTimes, setFetchTimes] = useState([0, 0]); 9 | const [queryResult, setQueryResult] = useState('Query Result Here'); 10 | const [queryString, setQueryString] = useState('Query String Here'); 11 | 12 | // GraphQL query types 13 | const querySelect = { 14 | films: 15 | `{ 16 | films { 17 | _id 18 | title 19 | episode_id 20 | director 21 | producer 22 | opening_crawl 23 | } 24 | }`, 25 | planets: 26 | `{ 27 | planets { 28 | _id 29 | name 30 | rotation_period 31 | orbital_period 32 | diameter 33 | climate 34 | gravity 35 | terrain 36 | surface_water 37 | population 38 | } 39 | }`, 40 | species: 41 | `{ 42 | species { 43 | _id 44 | name 45 | classification 46 | average_height 47 | average_lifespan 48 | hair_colors 49 | skin_colors 50 | eye_colors 51 | language 52 | homeworld_id 53 | } 54 | }`, 55 | people: 56 | `{ 57 | people { 58 | _id 59 | name 60 | mass 61 | skin_color 62 | eye_color 63 | birth_year 64 | gender 65 | species_id 66 | homeworld_id 67 | } 68 | }`, 69 | vessels: 70 | `{ 71 | vessels { 72 | _id 73 | name 74 | manufacturer 75 | model 76 | vessel_type 77 | vessel_class 78 | cost_in_credits 79 | length 80 | max_atmosphering_speed 81 | crew 82 | passengers 83 | cargo_capacity 84 | consumables 85 | } 86 | }`, 87 | }; 88 | 89 | let t0; let 90 | t1; 91 | const runQuery = () => { 92 | console.log('click'); 93 | // initiate timer 94 | t0 = performance.now(); 95 | hacheQL('/graphql', { 96 | method: 'POST', 97 | headers: { 98 | 'Content-Type': 'application/json', 99 | }, 100 | body: JSON.stringify({ 101 | query: queryString, 102 | }), 103 | }) 104 | .then((res) => res.json()) 105 | .then((data) => { 106 | // timer ends after fetch returns data 107 | t1 = performance.now(); 108 | const runTime = (t1 - t0); 109 | setFetchTimes([...fetchTimes, runTime]); 110 | setQueryResult(JSON.stringify(data)); 111 | }) 112 | .catch((err) => console.log('error on getAll in App.jsx: ', err)); 113 | }; 114 | 115 | const addOne = () => { 116 | setQueryString( 117 | 'mutation addEpisodeEight($_id: BigInt!, $title: String!, $episode_id: Int!, $opening_crawl: String!, $director: String!, $producer: String!, $release_date: Date!){\n addFilm(_id: $_id, title: $title, episode_id: $episode_id, opening_crawl: $opening_crawl, director: $director, producer: $producer, release_date: $release_date) {\n _id\n title\n episode_id\n director\n producer\n opening_crawl\n release_date\n }\n}","variables":{"_id":"8","title":"The Last Jedi","episode_id":8,"opening_crawl":"The FIRST ORDER reigns. Having decimated the peaceful Republic, Supreme Leader Snoke now deploys his merciles legions to seize military control of the galaxy. Only General Leia Organa\'s band of RESISTANCE fighters stand against the rising tyranny, certain that Jedi Master Luke Skywalker will return and restore a spark of hope to the fight. But the Resistance has been exposed. As the First Order speeds toward the rebel base, the brave heroes mount a desperate escape....","director":"JJ Abrams","producer":"JJ Abrams","release_date":"05 October 2011 14:48 UTC"},"operationName":"addEpisodeEight', 118 | ); 119 | fetch('/graphql', { 120 | method: 'POST', 121 | headers: { 122 | 'Content-Type': 'application/json', 123 | }, 124 | body: JSON.stringify({ 125 | query: 'mutation addEpisodeEight($_id: BigInt!, $title: String!, $episode_id: Int!, $opening_crawl: String!, $director: String!, $producer: String!, $release_date: Date!){\n addFilm(_id: $_id, title: $title, episode_id: $episode_id, opening_crawl: $opening_crawl, director: $director, producer: $producer, release_date: $release_date) {\n _id\n title\n episode_id\n director\n producer\n opening_crawl\n release_date\n }\n}', 126 | variables: { 127 | _id: '8', title: 'The Last Jedi', episode_id: 8, opening_crawl: "The FIRST ORDER reigns. Having decimated the peaceful Republic, Supreme Leader Snoke now deploys his merciles legions to seize military control of the galaxy. Only General Leia Organa's band of RESISTANCE fighters stand against the rising tyranny, certain that Jedi Master Luke Skywalker will return and restore a spark of hope to the fight. But the Resistance has been exposed. As the First Order speeds toward the rebel base, the brave heroes mount a desperate escape....", director: 'JJ Abrams', producer: 'JJ Abrams', release_date: '05 October 2011 14:48 UTC', 128 | }, 129 | operationName: 'addEpisodeEight', 130 | }), 131 | }) 132 | .then((res) => res.json()) 133 | .then((data) => { 134 | // timer ends after fetch returns data 135 | setQueryResult(JSON.stringify(data)); 136 | }) 137 | .catch((err) => console.log('error on addOne in App.jsx: ', err)); 138 | }; 139 | 140 | const handleChange = (event) => { 141 | console.log(event.target.value); 142 | setQueryString(event.target.value); 143 | setFetchTimes([0, 0]); 144 | }; 145 | 146 | return ( 147 |
148 |

Try out our Demo

149 |
150 |
    151 |
  • Select a type of query and run the query
  • 152 |
  • Note the performance improvement on subsequent requests
  • 153 |
  • Test out a simple mutation query, it passes through unaffected
  • 154 |
155 |
156 |
157 | 158 | 159 |
160 |
161 | 162 | 163 |
164 |
165 |
166 |
167 | 174 | 175 | 176 |
177 |
178 | ); 179 | } 180 | 181 | export default Demo; 182 | -------------------------------------------------------------------------------- /library/hacheql-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module hacheQL/server 3 | */ 4 | 5 | /** 6 | * @param {Object} [options={}] - An object with settings. 7 | * @param {Object} [options.redis] - To use Redis for caching, give the object a property with the key redis and the value: . 8 | * @param {Object} [cache={}] - An object to use as a cache. If not provided, defaults to an empty JavaScript object. If a Redis cache was specified, expressHacheQL uses that for caching. 9 | * @returns {function} - A function to be used as part of the middleware chain. After this piece of middleware runs, the GraphQL query can be accessed at req.body 10 | */ 11 | 12 | export function expressHacheQL({ redis } = {}, cache = {}) { 13 | // This function has two modes: one if a Redis cache is used, and the other if not. 14 | if (redis) { 15 | return async function redisHandler(req, res, next) { 16 | try { 17 | if (req.method === 'GET') { 18 | // Setting this value on res.locals tells the httpCache() function to set cache control headers on the response object. 19 | Object.defineProperty(res.locals, 'cacheable', { 20 | enumerable: false, 21 | writable: false, 22 | configurable: false, 23 | value: true, 24 | }); 25 | 26 | // If a GET request was sent with HacheQL, the URL will have a 'hash' property in the query string. 27 | // If the query string has no 'hash' property, simply call the next piece of middleware. 28 | if (Object.hasOwn(req.query, 'hash')) { 29 | // Try to retrieve a persisted query from the cache. 30 | const query = await redis.get(req.query.hash); 31 | if (!query) { 32 | // Status code 303 asks the client to make a followup HTTP request with the query included. 33 | return res.sendStatus(303); 34 | } 35 | 36 | delete req.query.hash; // After retrieving the persisted query we don't need the hash anymore. 37 | req.method = 'POST'; // Change the request method POST to account for situations where subsequent middleware functions expect a POST method. 38 | req.body = JSON.parse(query); 39 | } 40 | return next(); 41 | } 42 | // If a POST request was sent with HacheQL, cache the query and its associated hash. 43 | // If a POST request was not sent with HacheQL, simply call the next piece of middleware. 44 | if (req.method === 'POST') { 45 | // Check for a hash in the request. 46 | if (Object.hasOwn(req.query, 'hash')) { 47 | // if the hash is found, reformat the query to a string depending on its current data type and saving the hash along with its query string as a key-value pair in Redis cache 48 | const query = typeof req.body === 'object' ? JSON.stringify(req.body) : req.body; 49 | await redis.set(req.query.hash, query); 50 | } 51 | } 52 | return next(); 53 | } catch (e) { 54 | return next(e); 55 | } 56 | }; 57 | } 58 | // If a Redis cache is not used, return a function depending on current request method. 59 | return function cacheHandler(req, res, next) { 60 | try { 61 | // If the request was a GET, try to find the query string from cache object from the query hash 62 | if (req.method === 'GET') { 63 | Object.defineProperty(res.locals, 'cacheable', { 64 | enumerable: false, 65 | writable: false, 66 | configurable: false, 67 | value: true, 68 | }); 69 | if (Object.hasOwn(req.query, 'hash')) { 70 | const query = cache[req.query.hash]; 71 | // If there is no persisted query found return 303 status and make another request as a POST 72 | if (!query) { 73 | return res.sendStatus(303); 74 | } 75 | delete req.query.hash; 76 | req.method = 'POST'; 77 | req.body = query; 78 | } 79 | return next(); 80 | } 81 | // if the hash is found, save the hash along with its query string as a key-value pair in cache object 82 | if (req.method === 'POST') { 83 | if (Object.hasOwn(req.query, 'hash')) { 84 | cache[req.query.hash] = req.body; 85 | } 86 | } 87 | return next(); 88 | } catch (e) { 89 | return next(e); 90 | } 91 | }; 92 | } 93 | 94 | /** 95 | * @param {Object} req - A Node.js request object. 96 | * @param {string} key - A string index of the property to remove from the URL search parameter string. In our case, 'hash'. 97 | * @returns {Object} - An object containing the value of the property removed from the URL, along with the rest of the search params as a searchParams object (these remain in the URL). 98 | */ 99 | 100 | // This function gets the hashed GraphQL query from the request object's query string, then deletes that property from the query string. 101 | // If you're unfamiliar with the Node.js url module, see this section of the Node.js documentation: 102 | // https://nodejs.org/dist/latest-v16.x/docs/api/url.html#url-strings-and-url-objects 103 | 104 | function strip(req, key) { 105 | const url = new URL(req.url, `http://${req.headers.host}`); 106 | const { searchParams } = url; 107 | const hash = searchParams.get(key); 108 | url.searchParams.delete(key); 109 | req.url = `${url.pathname}${url.search}${url.hash}`; 110 | return { searchParams, hash }; 111 | } 112 | 113 | // Variable value tracks whether Redis connection has ever failed. 114 | let redisFailed = false; 115 | 116 | /** 117 | * 118 | * @param {Object} req - request object 119 | * @param {Object} res - response object 120 | * @param {Object} [opts] - An object providing the settings, which defaults to empty object. 121 | * @param {Object} [cache] - An object to use as cache, which defaults to empty object. 122 | * @param {function} [callback] - a callback which takes an error and/or data and is called when error/data is found. 123 | * @returns {Promise} - A Promise which resolves with the value of whatever the callback returns, or rejects with the reason of whatever the callback throws. 124 | * There are two ways access the data this function provides: 125 | * 1. You can pass it a callback. The callback is passed two arguments, (error, data), where data is a GraphQL query document. 126 | * 2. You can also use .then() chaining or async/await. 127 | */ 128 | 129 | function defaultCallback(err, data) { 130 | if (err) { 131 | throw err; 132 | } 133 | return data; 134 | } 135 | 136 | // Once-ify setting up the error event listener on the Redis client. <-- This comment is bad. Will fix. 137 | let errorListenerIsSetUp = false; 138 | 139 | export async function nodeHacheQL(req, res, opts = {}, cache = {}, callback = defaultCallback) { 140 | // Verify Redis connection has never failed, if truthy default to cache. 141 | if (redisFailed) { 142 | delete opts.redis; 143 | } 144 | const { redis } = opts; 145 | // Deconstruct hash and search parameters from reconstructed URL. 146 | const { searchParams, hash } = strip(req, 'hash'); 147 | try { 148 | // If Redis Client object exists, use it as our cache. 149 | if (redis) { 150 | if (!errorListenerIsSetUp) { 151 | redis.on('error', () => { 152 | redisFailed = true; 153 | }); 154 | errorListenerIsSetUp = true; 155 | } 156 | if (req.method === 'GET') { 157 | if (hash) { 158 | const query = await redis.get(hash); // What happens if there's a Redis error? How do we switch over to the server memory? 159 | if (!query) { 160 | // Status code 303 asks the client to make a followup HTTP request with the query included. 161 | res.statusCode = 303; 162 | res.send(); 163 | throw new URIError(); // Break 164 | } 165 | return callback(null, JSON.parse(query)); 166 | } 167 | // The searchParams.entries returns an iterator, which we then turn into a regular JS Object. 168 | // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams 169 | return callback(null, Object.fromEntries(searchParams.entries())); 170 | } 171 | if (req.method === 'POST') { 172 | // Construct the request body from the readable stream. 173 | const query = await (() => ( 174 | new Promise((resolve) => { 175 | const buffers = []; 176 | req.on('data', (chunk) => { 177 | buffers.push(chunk); 178 | }); 179 | req.on('end', () => { 180 | resolve(Buffer.concat(buffers).toString()); 181 | }); 182 | })))(); 183 | 184 | if (hash) { 185 | await redis.set(hash, query); 186 | } 187 | return callback(null, JSON.parse(query)); 188 | } 189 | return callback(); 190 | } 191 | // If Redis Client does NOT exist... 192 | if (req.method === 'GET') { 193 | if (hash) { 194 | if (!cache[hash]) { 195 | // Status code 303 asks the client to make a followup HTTP request with the query included. 196 | res.statusCode = 303; 197 | res.send(); 198 | throw new URIError(); // Break 199 | } 200 | return callback(null, cache[hash]); 201 | } 202 | // The searchParams.entries returns an iterator, which we then turn into a regular JS Object. 203 | // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams 204 | return callback(null, Object.fromEntries(searchParams.entries())); 205 | } 206 | if (req.method === 'POST') { 207 | // Construct the request body from the readable stream. 208 | const query = await (() => ( 209 | new Promise((resolve) => { 210 | const buffers = []; 211 | req.on('data', (chunk) => { 212 | buffers.push(chunk); 213 | }); 214 | req.on('end', () => { 215 | resolve(Buffer.concat(buffers).toString()); 216 | }); 217 | }) 218 | ))(); 219 | 220 | if (hash) { 221 | cache[hash] = query; 222 | } 223 | return callback(null, JSON.parse(query)); 224 | } 225 | return callback(); 226 | } catch (err) { 227 | if (!(err instanceof URIError)) { 228 | return callback(err); 229 | } 230 | return undefined; 231 | } 232 | } 233 | 234 | export function httpCache(customHeaders) { 235 | const defaultHeaders = { 236 | 'Cache-Control': 'max-age=5', 237 | }; 238 | 239 | const finalHeaders = { ...defaultHeaders, ...customHeaders }; 240 | 241 | return function setHeaders(req, res, next) { 242 | if (Object.hasOwn(res.locals, 'cacheable')) { 243 | res.set(finalHeaders); 244 | } 245 | return next(); 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /library/tests/middleware.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, expect, jest, test, 3 | } from '@jest/globals'; 4 | import httpMocks from 'node-mocks-http'; 5 | import { expressHacheQL, nodeHacheQL } from '../hacheql-server'; 6 | import { HASH_NOT_FOUND } from './HTTPStatusCodes'; 7 | 8 | // Function signature; 9 | // checkHash(request, response, next) 10 | // TODO: write test suite for final middleware, ensuring that url return to original 11 | 12 | describe('expressHacheQL - server-side function', () => { 13 | let cache; 14 | 15 | const mockReq = { 16 | method: 'GET', 17 | url: '/?hash=arbitraryh4sh', 18 | originalUrl: '/graphql?hash=arbitraryh4sh', 19 | headers: { 20 | accepts: 'application/json', 21 | 'Content-Type': 'application/json', 22 | }, 23 | query: { 24 | hash: 'arbitraryh4sh', 25 | }, 26 | }; 27 | 28 | const mockReqFollowupPOST = { 29 | method: 'POST', 30 | url: '/?hash=arbitraryh4sh', 31 | originalUrl: '/graphql?hash=arbitraryh4sh', 32 | headers: { 33 | accepts: 'application/json', 34 | 'Content-Type': 'application/json', 35 | }, 36 | body: { 37 | query: '{me{name}}', 38 | operationName: 'doTheThing', 39 | variables: { myVariable: 'someValue' }, 40 | }, 41 | query: { 42 | hash: 'arbitraryh4sh', 43 | }, 44 | }; 45 | 46 | let req; 47 | let res; 48 | let next; 49 | 50 | beforeEach(() => { 51 | jest.clearAllMocks(); 52 | cache = {}; 53 | req = httpMocks.createRequest(mockReq); 54 | res = httpMocks.createResponse(); 55 | next = jest.fn(() => { console.log('called next'); }); 56 | }); 57 | 58 | describe('Cache characteristics', () => { 59 | it.skip('The cache should be a FIFO heap, with the most recently accessed item moving to the top. It should be configurable to a certain size.', () => {}); 60 | }); 61 | 62 | describe('GET', () => { 63 | it(`Should send a ${HASH_NOT_FOUND} response and prevent the execution of any subsequent pieces of middleware if the requested hash is not present in the server's cache`, async () => { 64 | const configuredMiddleware = expressHacheQL({}, cache); 65 | configuredMiddleware(req, res, next); 66 | expect(res.statusCode).toBe(HASH_NOT_FOUND); 67 | expect(next).toBeCalledTimes(0); 68 | }); 69 | 70 | // This test assume's we're caching in local memory. 71 | describe(`For a GET request, if the requested hash is present in the cache: 72 | If on finishing the execution of our function the request method is a: 73 | POST: The associated GraphQL document should be stored at req.body 74 | GET: The associated GraphQL document should be stored at req.query`, () => { 75 | it('Caching in local memory', () => { 76 | // The requested hash is present in the cache. 77 | cache = { 78 | // arbitraryh4sh: '{"query":"{me{name}}","operationName":"doTheThing","variables":{"myVariable":"someValue"}}', 79 | arbitraryh4sh: { 80 | query: '{me{name}}', 81 | operationName: 'doTheThing', 82 | variables: { myVariable: 'someValue' }, 83 | }, 84 | otherhash: 'incorrect GraphQL document', 85 | }; 86 | 87 | const configuredMiddleware = expressHacheQL({}, cache); 88 | configuredMiddleware(req, res, next); 89 | 90 | if (req.method === 'GET') { 91 | expect(typeof req.query).toBe('object'); 92 | expect(req.query).toEqual(cache.arbitraryh4sh); 93 | } 94 | 95 | // JC - technically, this test is not being tested if our mock req object is a GET method right? Additionally, using our middleware, we wouldnt be checking cache under a POST request 96 | if (req.method === 'POST') { 97 | expect(typeof req.body).toBe('object'); 98 | expect(req.body).toEqual(cache.arbitraryh4sh); 99 | } 100 | }); 101 | // JC - Checking cache using an external cache (redis)? Would we check cache under POST method similar to line 92 102 | it('Caching in an external cache (Redis)', async () => { 103 | // The requested hash is present in the cache. 104 | const fakeRedisCache = { 105 | arbitraryh4sh: '{"query":"{me{name}}","operationName":"doTheThing","variables":{"myVariable":"someValue"}}', 106 | otherhash: 'incorrect GraphQL document', 107 | }; 108 | 109 | const fakeRedisClient = { 110 | get: jest.fn((key) => fakeRedisCache[key]), 111 | }; 112 | 113 | const configuredMiddleware = expressHacheQL({ redis: fakeRedisClient }); 114 | await configuredMiddleware(req, res, next); 115 | 116 | if (req.method === 'GET') { 117 | expect(typeof req.query).toBe('object'); 118 | expect(req.query).toEqual(JSON.parse(fakeRedisCache.arbitraryh4sh)); 119 | } 120 | 121 | if (req.method === 'POST') { 122 | expect(typeof req.body).toBe('object'); 123 | expect(req.body).toEqual(JSON.parse(fakeRedisCache.arbitraryh4sh)); 124 | } 125 | }); 126 | }); 127 | 128 | it('Should pass the request along to the next piece of middleware if there isn\'t a hash on the search params', () => { 129 | const newMockReq = { ...mockReq, query: {} }; 130 | const newMockRes = { 131 | randoProperty: { 132 | value: 'rando', 133 | }, 134 | }; 135 | 136 | const reqCopy = JSON.parse(JSON.stringify(newMockReq)); 137 | const resCopy = JSON.parse(JSON.stringify(newMockRes)); 138 | 139 | const configuredMiddleware = expressHacheQL({}, cache); 140 | configuredMiddleware(reqCopy, resCopy, next); 141 | 142 | expect(reqCopy).toEqual(newMockReq); 143 | expect(resCopy).toEqual(newMockRes); 144 | expect(next).toBeCalledTimes(1); 145 | 146 | // And what if there's no 'query' property on the request object at all? 147 | delete newMockReq.query; 148 | const reqCopyWithNoQuery = JSON.parse(JSON.stringify(newMockReq)); 149 | 150 | configuredMiddleware(reqCopyWithNoQuery, resCopy, next); 151 | 152 | expect(reqCopyWithNoQuery).toEqual(newMockReq); 153 | expect(resCopy).toEqual(newMockRes); 154 | expect(next).toBeCalledTimes(2); 155 | }); 156 | 157 | it.skip(`If there's an error in the Redis cache, it should error ${HASH_NOT_FOUND} and switch to the local cache.`, async () => { 158 | // This will simulate the Redis client being down. 159 | const fakeRedisClient = { 160 | get: jest.fn(() => Promise.reject(new Error('Error occurred while accessing the Redis cache.'))), 161 | }; 162 | 163 | // Configure our middleware function to use the Redis client. 164 | const configuredMiddleware = expressHacheQL({ redis: fakeRedisClient }, cache); 165 | // Simulate our function running as part of a middleware chain. 166 | await configuredMiddleware(req, res, next); 167 | 168 | // Expect to receive a HASH_NOT_FOUND error here. 169 | expect(res.statusCode).toBe(HASH_NOT_FOUND); 170 | 171 | // Then send a followup post. 172 | const followupRequest = httpMocks.createRequest(mockReqFollowupPOST); 173 | const followupResponse = httpMocks.createResponse(); 174 | await configuredMiddleware(followupRequest, followupResponse, next); 175 | 176 | // JC - should we be expecting the cache store to be in object per our last discussion? 177 | // Expect the key-value pair of hash-query to be saved in the local cache object. 178 | expect(cache.arbitraryh4sh).toBe(JSON.stringify({ 179 | query: '{me{name}}', 180 | operationName: 'doTheThing', 181 | variables: { myVariable: 'someValue' }, 182 | })); 183 | }); 184 | 185 | it.skip('If the redis cache ever errors, ALL subsequent caching should take place in the local memory (potentially update redis cache when back online)', () => {}); 186 | }); 187 | 188 | describe.skip('POST', () => { 189 | it('For a POST request, if there is a hash, it should add the hash and request body to the server\'s cache and invoke the next piece of middleware', () => {}); 190 | it('If there is no hash, it should invoke the next piece of middleware without storing anything in the cache.', () => {}); 191 | 192 | it('If there was ever an error reading or writing from the redis cache, the request should instead be stored in the local cache', () => {}); 193 | it('If the redis cache is erroring for the first time, switch to the local cache for the memory-life', () => {}); 194 | }); 195 | describe.skip('other HTTP methods', () => { 196 | it('If anything other than a POST or GET is found, we should invoke next (should we also log an error?)', () => {}); 197 | }); 198 | }); 199 | 200 | describe.skip('nodeHacheQL - server-side function', () => { 201 | describe('Cache characteristics', () => { 202 | test('The cache should be a FIFO heap, with the most recently accessed item moving to the top. It should be configurable to a certain size.', () => {}); 203 | }); 204 | describe('GET', () => { 205 | describe('Caching lifecycle', () => { 206 | test(`Should send an ${HASH_NOT_FOUND} response if the requested hash is not present in the server's cache`, () => {}); 207 | }); 208 | test(`For a GET request, if the requested hash is present in the cache: 209 | If on finishing the execution of our function the request method is a: 210 | POST: The associated query should be stored in req.body 211 | GET: The associated query should be stored as req.query`, () => {}); 212 | describe('Edge cases', () => { 213 | test('Should pass the request along to the next piece of middleware if there isn\'t a hash on the search params', () => {}); 214 | test(`If there's an error in the redis cache, it should error ${HASH_NOT_FOUND} and switch to the local cache.`, () => {}); 215 | test('If the redis cache ever errors, ALL subsequent caching should take place in the local memory (potentially update redis cache when back online)', () => {}); 216 | }); 217 | }); 218 | 219 | describe('POST', () => { 220 | test('For a POST request, if there is a hash, it should add the hash and request body to the server\'s cache and invoke the next piece of middleware', () => {}); 221 | test('If there is no hash, it should invoke the next piece of middleware without storing anything in the cache.', () => {}); 222 | 223 | test('If there was ever an error reading or writing from the redis cache, the request should instead be stored in the local cache', () => {}); 224 | test('If the redis cache is erroring for the first time, switch to the local cache for the memory-life', () => {}); 225 | }); 226 | describe('other HTTP methods', () => { 227 | test('If anything other than a POST or GET is found, we should invoke next (should we also log an error?)', () => {}); 228 | }); 229 | }); 230 | -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## Overview 4 | 5 | HacheQL works by hashing the contents of GraphQL queries and caching key-value pairs of the form \: \ on the server side. 6 | 7 | No matter how large or complex the original query is, the hashed version is short enough to be sent as a query parameter in a URL, while still being uniquely identifiable as related to the original query. As a result, HacheQL can send any GraphQL query as a GET request, which allows browsers and proxy servers to cache the response. 8 | 9 | Refer to the sections below for detailed information about specific HacheQL functions. 10 | 11 | ## hacheQL() 12 | 13 | Send a GraphQL request such that the response is HTTP cacheable. 14 | 15 |
Expand for details 16 | 17 | ### Syntax 18 | 19 | > Note: This function signature is designed to mimic [the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/fetch). 20 | 21 | ```javascript 22 | hacheQL(endpoint[, options]) 23 | ``` 24 | 25 | ### Parameters 26 | - `endpoint` \ 27 | - The URL endpoint for the GraphQL request. Analogous to the Fetch API's 'resource' parameter. 28 | - If the URL contains the GraphQL query in a query string (see the next bullet for an example), then the `options` argument may not be necessary. However, you won't be getting much benefit from HacheQL in that case. HacheQL's real utility comes in caching GraphQL requests made using the POST method (which allows for more complex queries). 29 | 30 | - An example of a GraphQL query contained in the URL's query string: 31 | ```javascript 32 | hacheQL('graphql?query=%7B%20hero%20%7B%20name%20%7D%20%7D').then(/* code */) 33 | ``` 34 | 35 | - `options` \ 36 | - An object containing settings for the request; for example, the HTTP request method, request headers, and request body. 37 | - Analogous to the fetch API's 'init' parameter. All valid properties for the fetch API's 'init' object are valid properties for this function's 'options' object. 38 | - See [this page from the GraphQL Foundation](https://graphql.org/learn/serving-over-http/#http-methods-headers-and-body) for more information on sending GraphQL requests over HTTP, especially with respect to setting headers. 39 | 40 | ### Return value 41 | \ · A Promise that resolves to a Response object from the server, or rejects with an Error object. 42 | 43 |
44 | 45 | ### Sample usage 46 | 47 | Just like `fetch()` from the Fetch API, `hacheQL()` works with both `.then` chaining and `async/await`. 48 | 49 | Using `.then` chaining: 50 | ```javascript 51 | hacheQL('/graphql', { 52 | method: 'POST', 53 | headers: { 'Content-Type': 'application/graphql' }, 54 | body: '{ hero { name } }' 55 | }) 56 | .then((response) => response.json()) 57 | .then((data) => console.log(data)) 58 | .catch((error) => /* error handling logic */); 59 | ``` 60 | 61 | Using `async/await`: 62 | ```javascript 63 | try { 64 | const response = await hacheQL('/graphql', { 65 | method: 'POST', 66 | headers: { 'Content-Type': 'application/graphql' }, 67 | body: '{ hero { name } }' 68 | }); 69 | 70 | const data = await response.json(); 71 | console.log(data); 72 | 73 | } catch (error) { 74 | /* error handling logic */ 75 | } 76 | ``` 77 | 78 | The previous examples sent the GraphQL query as a string (as indicated by the `application/graphql` Content-Type header), but it's also common to send a query as a JSON-encoded object (using the `application/json` Content-Type header). 79 | 80 | HacheQL is perfectly happy to handle that kind of request too. 81 | 82 | Using `.then` chaining: 83 | ```javascript 84 | hacheQL('/graphql', { 85 | method: 'POST', 86 | headers: { 'Content-Type': 'application/json' }, 87 | body: JSON.stringify({ 88 | query: `($episode: Episode) { 89 | hero(episode: $episode) { 90 | name 91 | friends { 92 | name 93 | } 94 | } 95 | }`, 96 | operationName: 'HeroNameAndFriends', 97 | variables: '{ "episode": "JEDI" }', 98 | }), 99 | }) 100 | .then((response) => response.json()) 101 | .then((data) => console.log(data)) 102 | .catch((error) => /* error handling logic */); 103 | ``` 104 | 105 | Using `async/await`: 106 | ```javascript 107 | try { 108 | const response = await hacheQL('/graphql', { 109 | method: 'POST', 110 | headers: { 'Content-Type': 'application/json' }, 111 | body: JSON.stringify({ 112 | query: `($episode: Episode) { 113 | hero(episode: $episode) { 114 | name 115 | friends { 116 | name 117 | } 118 | } 119 | }`, 120 | operationName: 'HeroNameAndFriends', 121 | variables: '{ "episode": "JEDI" }', 122 | }), 123 | }); 124 | 125 | const data = await response.json(); 126 | console.log(data); 127 | 128 | } catch (error) { 129 | /* error handling logic */ 130 | } 131 | ``` 132 | 133 | 134 | 135 | 136 |
137 | 138 | ## nodeHacheQL() 139 | 140 | Process incoming GraphQL requests on the server. 141 | > Note: If your project uses Express.js, we recommend using [expressHacheQL](#expresshacheql) instead. 142 | 143 |
Expand for details 144 | 145 | ### Behavior in detail 146 | 147 | Like many functions in Node.js, nodeHacheQL() runs asynchronously. It parses the Request object's readable data stream and either caches the incoming GraphQL query (if it's a new query) or retrieves the correct GraphQL query from the cache. 148 | 149 |
150 | 151 | ### Syntax 152 | ```javascript 153 | nodeHacheQL(req, res[, opts, cache, callback]) 154 | ``` 155 | 156 | ### Parameters 157 | - `req` \ 158 | - The HTTP Request object. 159 | - `res` \ 160 | - The HTTP Response object. 161 | - `opts` \ *(optional)* 162 | - Determines where GraphQL queries should be cached. 163 | - If not provided, nodeHacheQL caches GraphQL queries in the server's memory. 164 | - To cache with Redis, provide a reference to your Redis client as a property with the key `redis.` 165 | 166 | ```javascript 167 | nodeHacheQL(req, res, { redis: }) 168 | ``` 169 | - `cache` \ *(optional)* 170 | - If not provided, defaults to an empty object. 171 | - `callback` \ *(optional)* 172 | - If not provided, defaults to: 173 | ```javascript 174 | (err, query) => { 175 | if (err) { 176 | throw err; 177 | } 178 | return data; 179 | } 180 | ``` 181 | 182 | ### Return value 183 | \ · A Promise which resolves with the value of whatever the callback returns, or rejects with the reason of whatever the callback throws. 184 | 185 | There are two ways define what happens after this function finishes running: 186 | 1. You can pass it a callback. The callback is passed two arguments, (error, data), where data is a GraphQL query document. 187 | 2. You can also use .then() chaining or async/await. 188 | 189 |
190 | 191 | ### Sample usage 192 | 193 | Using async/await: 194 | ```javascript 195 | server.on('request', async (req, res) => { 196 | if (request.url === '/graphql') { 197 | try { 198 | const query = await nodeHacheQL(req, res, { redis: redisClient }); 199 | const data = await database.query(query); 200 | res.end(data); 201 | } catch (error) { 202 | /* error handling logic */ 203 | } 204 | } 205 | }); 206 | ``` 207 | 208 | Using a callback: 209 | ```javascript 210 | server.on('request', async (req, res) => { 211 | if (request.url === '/graphql') { 212 | nodeHacheQL(req, res, { redis: redisClient }, (err, query) => { 213 | database.query(query) 214 | .then((data) => res.end(data)) 215 | .catch((error) => /* error handling logic */); 216 | }); 217 | } 218 | }); 219 | 220 | ``` 221 | 222 | 223 | 224 |
225 | 226 | ## expressHacheQL() 227 | 228 | Process incoming GraphQL requests on the server. 229 | > This function is similar to [nodeHacheQL()](#nodehacheql), but it's built specifically for Express.js and takes advantage of Express's middleware pattern. 230 | 231 |
Expand for details 232 | 233 | ### Behavior in detail 234 | Invoking expressHacheQL returns a function to be used as part of the middleware chain. The middleware function caches new GraphQL queries and retrieves cached queries when they are needed. After the middleware function runs, the GraphQL query can be accessed at `req.body`. 235 | 236 | > Note: `expressHacheQL` relies on Express's built-in [express.json()](https://expressjs.com/en/api.html#express.json) method for parsing JSON-encoded request bodies. If you don't have it set up yet, add the following toward the top of your main server file: 237 | ```javascript 238 | app.use(express.json()) 239 | ``` 240 | 241 |
242 | 243 | ### Syntax 244 | ```javascript 245 | expressHacheQL([options, customCache]) 246 | ``` 247 | 248 | ### Parameters 249 | - `options` \ *(optional)* 250 | - An object with settings. If not provided, defaults to an empty Object. 251 | - Currently, there is only one setting available (see the next bullet), but more may be added in the future. 252 | - To use Redis for caching, give the object a property with the key `redis`. 253 | - `redis`: \ 254 | - `customCache` \ *(optional)* 255 | - An object to use as a cache. If not provided, defaults to an empty Object. 256 | - If a Redis cache was provided in the `options` object, that overrides anything passed in as the `customCache` argument. 257 | 258 | ### Return value 259 | \ · 260 | A function to be used as part of the middleware chain. After this piece of middleware runs, the GraphQL query can be accessed at `req.body`. 261 | 262 |
263 | 264 | ### Sample usage 265 | 266 | Use an invocation of `expressHacheQL` as the first piece of middleware in routes that handle GraphQL requests. 267 | 268 | If you don't pass any arguments to `expressHacheQL` it uses the server's memory for caching. 269 | 270 | ```javascript 271 | app.use('/graphql', expressHacheQL(), /* other middleware */); 272 | ``` 273 | 274 | If you want to cache using Redis, provide a reference to your Redis client as a property with the key `redis.` 275 | 276 | ```javascript 277 | app.use('/graphql', expressHacheQL({ redis: }), /* other middleware */); 278 | ``` 279 | 280 | If you want to cache in some other object, provide that object as a second argument to the function. 281 | 282 | ```javascript 283 | app.use('/graphql', expressHacheQL({}, ), /* other middleware */); 284 | ``` 285 | 286 | 287 | 288 |
289 | 290 | ## httpCache() 291 | 292 | Set cache-control headers on the HTTP Response object. 293 | 294 |
Expand for details 295 | 296 | ### Behavior in detail 297 | 298 | httpCache() automatically sets the header `'Cache-Control': 'max-age=5'` on all cacheable responses. However, you can define custom behavior if you'd like. 299 | 300 |
301 | 302 | ### Syntax 303 | > Note: Express automatically passes all three of these arguments to each piece of middleware. You do not need to pass them to httpCache() manually. 304 | 305 | ```javascript 306 | httpCache(customHeadersObject) 307 | ``` 308 | 309 | ### Parameters 310 | 311 | - `customHeadersObject` \ 312 | - An object that defines HTTP response headers to set. If not provided, defaults to: 313 | ```javascript 314 | { 'Cache-Control': 'max-age=5' } 315 | ``` 316 | - Common caching-related HTTP response headers include the [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header and the [Etag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) header. 317 | 318 | ### Return value 319 | A function to be used as part of the middlware chain. The middleware function sets HTTP headers on all cacheable response objects. 320 | 321 |
322 | 323 | ### Sample usage 324 | 325 | ```javascript 326 | app.use( 327 | '/graphql', 328 | expressHacheQL(), 329 | httpCache({ 330 | 'Cache-Control': 'max-age=10, must-revalidate'; 331 | }) 332 | graphqlHTTP({ 333 | schema, 334 | graphiql: false, 335 | }) 336 | ); 337 | ``` 338 | -------------------------------------------------------------------------------- /demo/server/mydb.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PostgreSQL database dump 3 | -- 4 | 5 | -- Dumped from database version 11.3 (Ubuntu 11.3-1.pgdg18.04+1) 6 | -- Dumped by pg_dump version 11.5 7 | 8 | SET statement_timeout = 0; 9 | SET lock_timeout = 0; 10 | SET idle_in_transaction_session_timeout = 0; 11 | SET client_encoding = 'UTF8'; 12 | SET standard_conforming_strings = on; 13 | SET check_function_bodies = false; 14 | SET client_min_messages = warning; 15 | SET row_security = off; 16 | 17 | -- 18 | -- Name: hqldb; Type: DATABASE; Schema: -; Owner: hqladmin 19 | -- 20 | 21 | 22 | \connect hqldb 23 | 24 | SET statement_timeout = 0; 25 | SET lock_timeout = 0; 26 | SET idle_in_transaction_session_timeout = 0; 27 | SET client_encoding = 'UTF8'; 28 | SET standard_conforming_strings = on; 29 | SET check_function_bodies = false; 30 | SET client_min_messages = warning; 31 | SET row_security = off; 32 | 33 | SET search_path = public, pg_catalog; 34 | 35 | SET default_tablespace = ''; 36 | 37 | SET default_with_oids = false; 38 | 39 | 40 | CREATE TABLE people ( 41 | "_id" serial NOT NULL, 42 | "name" varchar NOT NULL, 43 | "mass" varchar, 44 | "hair_color" varchar, 45 | "skin_color" varchar, 46 | "eye_color" varchar, 47 | "birth_year" varchar, 48 | "gender" varchar, 49 | "species_id" bigint, 50 | "homeworld_id" bigint, 51 | "height" integer, 52 | CONSTRAINT "people_pk" PRIMARY KEY ("_id") 53 | ); 54 | 55 | ALTER TABLE people OWNER to hqladmin; 56 | 57 | 58 | 59 | CREATE TABLE films ( 60 | "_id" serial NOT NULL, 61 | "title" varchar NOT NULL, 62 | "episode_id" integer NOT NULL, 63 | "opening_crawl" varchar NOT NULL, 64 | "director" varchar NOT NULL, 65 | "producer" varchar NOT NULL, 66 | "release_date" DATE NOT NULL, 67 | CONSTRAINT "films_pk" PRIMARY KEY ("_id") 68 | ); 69 | 70 | ALTER TABLE films OWNER to hqladmin; 71 | 72 | CREATE TABLE people_in_films ( 73 | "_id" serial NOT NULL, 74 | "person_id" bigint NOT NULL, 75 | "film_id" bigint NOT NULL, 76 | CONSTRAINT "people_in_films_pk" PRIMARY KEY ("_id") 77 | ); 78 | 79 | ALTER TABLE people_in_films OWNER to hqladmin; 80 | 81 | CREATE TABLE planets ( 82 | "_id" serial NOT NULL, 83 | "name" varchar, 84 | "rotation_period" integer, 85 | "orbital_period" integer, 86 | "diameter" integer, 87 | "climate" varchar, 88 | "gravity" varchar, 89 | "terrain" varchar, 90 | "surface_water" varchar, 91 | "population" bigint, 92 | CONSTRAINT "planets_pk" PRIMARY KEY ("_id") 93 | ); 94 | 95 | ALTER TABLE planets OWNER to hqladmin; 96 | 97 | CREATE TABLE species ( 98 | "_id" serial NOT NULL, 99 | "name" varchar NOT NULL, 100 | "classification" varchar, 101 | "average_height" varchar, 102 | "average_lifespan" varchar, 103 | "hair_colors" varchar, 104 | "skin_colors" varchar, 105 | "eye_colors" varchar, 106 | "language" varchar, 107 | "homeworld_id" bigint, 108 | CONSTRAINT "species_pk" PRIMARY KEY ("_id") 109 | ); 110 | 111 | ALTER TABLE species OWNER to hqladmin; 112 | 113 | CREATE TABLE vessels ( 114 | "_id" serial NOT NULL, 115 | "name" varchar NOT NULL, 116 | "manufacturer" varchar, 117 | "model" varchar, 118 | "vessel_type" varchar NOT NULL, 119 | "vessel_class" varchar NOT NULL, 120 | "cost_in_credits" bigint, 121 | "length" varchar, 122 | "max_atmosphering_speed" varchar, 123 | "crew" integer, 124 | "passengers" integer, 125 | "cargo_capacity" varchar, 126 | "consumables" varchar, 127 | CONSTRAINT "vessels_pk" PRIMARY KEY ("_id") 128 | ); 129 | 130 | ALTER TABLE vessels OWNER to hqladmin; 131 | 132 | CREATE TABLE species_in_films ( 133 | "_id" serial NOT NULL, 134 | "film_id" bigint NOT NULL, 135 | "species_id" bigint NOT NULL, 136 | CONSTRAINT "species_in_films_pk" PRIMARY KEY ("_id") 137 | ); 138 | 139 | ALTER TABLE species_in_films OWNER to hqladmin; 140 | 141 | CREATE TABLE planets_in_films ( 142 | "_id" serial NOT NULL, 143 | "film_id" bigint NOT NULL, 144 | "planet_id" bigint NOT NULL, 145 | CONSTRAINT "planets_in_films_pk" PRIMARY KEY ("_id") 146 | ); 147 | 148 | ALTER TABLE planets_in_films OWNER to hqladmin; 149 | 150 | CREATE TABLE pilots ( 151 | "_id" serial NOT NULL, 152 | "person_id" bigint NOT NULL, 153 | "vessel_id" bigint NOT NULL, 154 | CONSTRAINT "pilots_pk" PRIMARY KEY ("_id") 155 | ); 156 | 157 | ALTER TABLE pilots OWNER to hqladmin; 158 | 159 | CREATE TABLE vessels_in_films ( 160 | "_id" serial NOT NULL, 161 | "vessel_id" bigint NOT NULL, 162 | "film_id" bigint NOT NULL, 163 | CONSTRAINT "vessels_in_films_pk" PRIMARY KEY ("_id") 164 | ); 165 | 166 | ALTER TABLE vessels_in_films OWNER to hqladmin; 167 | 168 | CREATE TABLE starship_specs ( 169 | "_id" serial NOT NULL, 170 | "hyperdrive_rating" varchar, 171 | "MGLT" varchar, 172 | "vessel_id" bigint NOT NULL, 173 | CONSTRAINT "starship_specs_pk" PRIMARY KEY ("_id") 174 | ); 175 | 176 | ALTER TABLE starship_specs OWNER to hqladmin; 177 | 178 | 179 | ALTER TABLE people ADD CONSTRAINT "people_fk0" FOREIGN KEY ("species_id") REFERENCES species("_id"); 180 | ALTER TABLE people ADD CONSTRAINT "people_fk1" FOREIGN KEY ("homeworld_id") REFERENCES planets("_id"); 181 | 182 | 183 | ALTER TABLE people_in_films ADD CONSTRAINT "people_in_films_fk0" FOREIGN KEY ("person_id") REFERENCES people("_id"); 184 | ALTER TABLE people_in_films ADD CONSTRAINT "people_in_films_fk1" FOREIGN KEY ("film_id") REFERENCES films("_id"); 185 | 186 | 187 | ALTER TABLE species ADD CONSTRAINT "species_fk0" FOREIGN KEY ("homeworld_id") REFERENCES planets("_id"); 188 | 189 | 190 | ALTER TABLE species_in_films ADD CONSTRAINT "species_in_films_fk0" FOREIGN KEY ("film_id") REFERENCES films("_id"); 191 | ALTER TABLE species_in_films ADD CONSTRAINT "species_in_films_fk1" FOREIGN KEY ("species_id") REFERENCES species("_id"); 192 | 193 | ALTER TABLE planets_in_films ADD CONSTRAINT "planets_in_films_fk0" FOREIGN KEY ("film_id") REFERENCES films("_id"); 194 | ALTER TABLE planets_in_films ADD CONSTRAINT "planets_in_films_fk1" FOREIGN KEY ("planet_id") REFERENCES planets("_id"); 195 | 196 | ALTER TABLE pilots ADD CONSTRAINT "pilots_fk0" FOREIGN KEY ("person_id") REFERENCES people("_id"); 197 | ALTER TABLE pilots ADD CONSTRAINT "pilots_fk1" FOREIGN KEY ("vessel_id") REFERENCES vessels("_id"); 198 | 199 | ALTER TABLE vessels_in_films ADD CONSTRAINT "vessels_in_films_fk0" FOREIGN KEY ("vessel_id") REFERENCES vessels("_id"); 200 | ALTER TABLE vessels_in_films ADD CONSTRAINT "vessels_in_films_fk1" FOREIGN KEY ("film_id") REFERENCES films("_id"); 201 | 202 | ALTER TABLE starship_specs ADD CONSTRAINT "starship_specs_fk0" FOREIGN KEY ("vessel_id") REFERENCES vessels("_id"); 203 | 204 | 205 | 206 | -- 207 | -- TOC entry 4120 (class 0 OID 4163856) 208 | -- Dependencies: 225 209 | -- Data for Name: films; Type: TABLE DATA; Schema: Owner: - 210 | -- 211 | 212 | INSERT INTO films VALUES (1, 'A New Hope', 4, 'It is a period of civil war. 213 | Rebel spaceships, striking 214 | from a hidden base, have won 215 | their first victory against 216 | the evil Galactic Empire. 217 | 218 | During the battle, Rebel 219 | spies managed to steal secret 220 | plans to the Empire''s 221 | ultimate weapon, the DEATH 222 | STAR, an armored space 223 | station with enough power 224 | to destroy an entire planet. 225 | 226 | Pursued by the Empire''s 227 | sinister agents, Princess 228 | Leia races home aboard her 229 | starship, custodian of the 230 | stolen plans that can save her 231 | people and restore 232 | freedom to the galaxy....', 'George Lucas', 'Gary Kurtz, Rick McCallum', '1977-05-25'); 233 | INSERT INTO films VALUES (5, 'Attack of the Clones', 2, 'There is unrest in the Galactic 234 | Senate. Several thousand solar 235 | systems have declared their 236 | intentions to leave the Re 237 | 238 | This separatist movement, 239 | under the leadership of the 240 | mysterious Count Dooku, has 241 | made it difficult for the limited 242 | number of Jedi Knights to maintain 243 | peace and order in the galaxy. 244 | 245 | Senator Amidala, the former 246 | Queen of Naboo, is returning 247 | to the Galactic Senate to vote 248 | on the critical issue of creating 249 | an ARMY OF THE REPUBLIC 250 | to assist the overwhelmed 251 | Jedi....', 'George Lucas', 'Rick McCallum', '2002-05-16'); 252 | INSERT INTO films VALUES (4, 'The Phantom Menace', 1, 'Turmoil has engulfed the 253 | Galactic Re The taxation 254 | of trade routes to outlying star 255 | systems is in dispute. 256 | 257 | Hoping to resolve the matter 258 | with a blockade of deadly 259 | battleships, the greedy Trade 260 | Federation has stopped all 261 | shipping to the small planet 262 | of Naboo. 263 | 264 | While the Congress of the 265 | Reendlessly debates 266 | this alarming chain of events, 267 | the Supreme Chancellor has 268 | secretly dispatched two Jedi 269 | Knights, the guardians of 270 | peace and justice in the 271 | galaxy, to settle the conflict....', 'George Lucas', 'Rick McCallum', '1999-05-19'); 272 | INSERT INTO films VALUES (6, 'Revenge of the Sith', 3, 'War! The Reis crumbling 273 | under attacks by the ruthless 274 | Sith Lord, Count Dooku. 275 | There are heroes on both sides. 276 | Evil is everywhere. 277 | 278 | In a stunning move, the 279 | fiendish droid leader, General 280 | Grievous, has swept into the 281 | Recapital and kidnapped 282 | Chancellor Palpatine, leader of 283 | the Galactic Senate. 284 | 285 | As the Separatist Droid Army 286 | attempts to flee the besieged 287 | capital with their valuable 288 | hostage, two Jedi Knights lead a 289 | desperate mission to rescue the 290 | captive Chancellor....', 'George Lucas', 'Rick McCallum', '2005-05-19'); 291 | INSERT INTO films VALUES (3, 'Return of the Jedi', 6, 'Luke Skywalker has returned to 292 | his home planet of Tatooine in 293 | an attempt to rescue his 294 | friend Han Solo from the 295 | clutches of the vile gangster 296 | Jabba the Hutt. 297 | 298 | Little does Luke know that the 299 | GALACTIC EMPIRE has secretly 300 | begun construction on a new 301 | armored space station even 302 | more powerful than the first 303 | dreaded Death Star. 304 | 305 | When completed, this ultimate 306 | weapon will spell certain doom 307 | for the small band of rebels 308 | struggling to restore freedom 309 | to the galaxy...', 'Richard Marquand', 'Howard G. Kazanjian, George Lucas, Rick McCallum', '1983-05-25'); 310 | INSERT INTO films VALUES (2, 'The Empire Strikes Back', 5, 'It is a dark time for the 311 | Rebellion. Although the Death 312 | Star has been destroyed, 313 | Imperial troops have driven the 314 | Rebel forces from their hidden 315 | base and pursued them across 316 | the galaxy. 317 | 318 | Evading the dreaded Imperial 319 | Starfleet, a group of freedom 320 | fighters led by Luke Skywalker 321 | has established a new secret 322 | base on the remote ice world 323 | of Hoth. 324 | 325 | The evil lord Darth Vader, 326 | obsessed with finding young 327 | Skywalker, has dispatched 328 | thousands of remote probes into 329 | the far reaches of space....', 'Irvin Kershner', 'Gary Kurtz, Rick McCallum', '1980-05-17'); 330 | INSERT INTO films VALUES (7, 'The Force Awakens', 7, 'Luke Skywalker has vanished. 331 | In his absence, the sinister 332 | FIRST ORDER has risen from 333 | the ashes of the Empire 334 | and will not rest until 335 | Skywalker, the last Jedi, 336 | has been destroyed. 337 | 338 | With the support of the 339 | RE General Leia Organa 340 | leads a brave RESISTANCE. 341 | She is desperate to find her 342 | brother Luke and gain his 343 | help in restoring peace and 344 | justice to the galaxy. 345 | 346 | Leia has sent her most daring 347 | pilot on a secret mission 348 | to Jakku, where an old ally 349 | has discovered a clue to 350 | Luke''s whereabouts....', 'J. J. Abrams', 'Kathleen Kennedy, J. J. Abrams, Bryan Burk', '2015-12-11'); 351 | 352 | 353 | -- 354 | -- TOC entry 4124 (class 0 OID 4163875) 355 | -- Dependencies: 229 356 | -- Data for Name: planets; Type: TABLE DATA; Schema: Owner: - 357 | -- 358 | 359 | INSERT INTO planets VALUES (2, 'Alderaan', 24, 364, 12500, 'temperate', '1 standard', 'grasslands, mountains', '40', 2000000000); 360 | INSERT INTO planets VALUES (3, 'Yavin IV', 24, 4818, 10200, 'temperate, tropical', '1 standard', 'jungle, rainforests', '8', 1000); 361 | INSERT INTO planets VALUES (4, 'Hoth', 23, 549, 7200, 'frozen', '1.1 standard', 'tundra, ice caves, mountain ranges', '100', NULL); 362 | INSERT INTO planets VALUES (5, 'Dagobah', 23, 341, 8900, 'murky', 'N/A', 'swamp, jungles', '8', NULL); 363 | INSERT INTO planets VALUES (6, 'Bespin', 12, 5110, 118000, 'temperate', '1.5 (surface), 1 standard (Cloud City)', 'gas giant', '0', 6000000); 364 | INSERT INTO planets VALUES (7, 'Endor', 18, 402, 4900, 'temperate', '0.85 standard', 'forests, mountains, lakes', '8', 30000000); 365 | INSERT INTO planets VALUES (8, 'Naboo', 26, 312, 12120, 'temperate', '1 standard', 'grassy hills, swamps, forests, mountains', '12', 4500000000); 366 | INSERT INTO planets VALUES (9, 'Coruscant', 24, 368, 12240, 'temperate', '1 standard', 'cityscape, mountains', NULL, 1000000000000); 367 | INSERT INTO planets VALUES (10, 'Kamino', 27, 463, 19720, 'temperate', '1 standard', 'ocean', '100', 1000000000); 368 | INSERT INTO planets VALUES (11, 'Geonosis', 30, 256, 11370, 'temperate, arid', '0.9 standard', 'rock, desert, mountain, barren', '5', 100000000000); 369 | INSERT INTO planets VALUES (12, 'Utapau', 27, 351, 12900, 'temperate, arid, windy', '1 standard', 'scrublands, savanna, canyons, sinkholes', '0.9', 95000000); 370 | INSERT INTO planets VALUES (13, 'Mustafar', 36, 412, 4200, 'hot', '1 standard', 'volcanoes, lava rivers, mountains, caves', '0', 20000); 371 | INSERT INTO planets VALUES (14, 'Kashyyyk', 26, 381, 12765, 'tropical', '1 standard', 'jungle, forests, lakes, rivers', '60', 45000000); 372 | INSERT INTO planets VALUES (15, 'Polis Massa', 24, 590, 0, 'artificial temperate ', '0.56 standard', 'airless asteroid', '0', 1000000); 373 | INSERT INTO planets VALUES (16, 'Mygeeto', 12, 167, 10088, 'frigid', '1 standard', 'glaciers, mountains, ice canyons', NULL, 19000000); 374 | INSERT INTO planets VALUES (17, 'Felucia', 34, 231, 9100, 'hot, humid', '0.75 standard', 'fungus forests', NULL, 8500000); 375 | INSERT INTO planets VALUES (18, 'Cato Neimoidia', 25, 278, 0, 'temperate, moist', '1 standard', 'mountains, fields, forests, rock arches', NULL, 10000000); 376 | INSERT INTO planets VALUES (19, 'Saleucami', 26, 392, 14920, 'hot', NULL, 'caves, desert, mountains, volcanoes', NULL, 1400000000); 377 | INSERT INTO planets VALUES (20, 'Stewjon', NULL, NULL, 0, 'temperate', '1 standard', 'grass', NULL, NULL); 378 | INSERT INTO planets VALUES (21, 'Eriadu', 24, 360, 13490, 'polluted', '1 standard', 'cityscape', NULL, 22000000000); 379 | INSERT INTO planets VALUES (22, 'Corellia', 25, 329, 11000, 'temperate', '1 standard', 'plains, urban, hills, forests', '70', 3000000000); 380 | INSERT INTO planets VALUES (23, 'Rodia', 29, 305, 7549, 'hot', '1 standard', 'jungles, oceans, urban, swamps', '60', 1300000000); 381 | INSERT INTO planets VALUES (24, 'Nal Hutta', 87, 413, 12150, 'temperate', '1 standard', 'urban, oceans, swamps, bogs', NULL, 7000000000); 382 | INSERT INTO planets VALUES (25, 'Dantooine', 25, 378, 9830, 'temperate', '1 standard', 'oceans, savannas, mountains, grasslands', NULL, 1000); 383 | INSERT INTO planets VALUES (26, 'Bestine IV', 26, 680, 6400, 'temperate', NULL, 'rocky islands, oceans', '98', 62000000); 384 | INSERT INTO planets VALUES (27, 'Ord Mantell', 26, 334, 14050, 'temperate', '1 standard', 'plains, seas, mesas', '10', 4000000000); 385 | INSERT INTO planets VALUES (28, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, NULL); 386 | INSERT INTO planets VALUES (29, 'Trandosha', 25, 371, 0, 'arid', '0.62 standard', 'mountains, seas, grasslands, deserts', NULL, 42000000); 387 | INSERT INTO planets VALUES (30, 'Socorro', 20, 326, 0, 'arid', '1 standard', 'deserts, mountains', NULL, 300000000); 388 | INSERT INTO planets VALUES (31, 'Mon Cala', 21, 398, 11030, 'temperate', '1', 'oceans, reefs, islands', '100', 27000000000); 389 | INSERT INTO planets VALUES (32, 'Chandrila', 20, 368, 13500, 'temperate', '1', 'plains, forests', '40', 1200000000); 390 | INSERT INTO planets VALUES (33, 'Sullust', 20, 263, 12780, 'superheated', '1', 'mountains, volcanoes, rocky deserts', '5', 18500000000); 391 | INSERT INTO planets VALUES (34, 'Toydaria', 21, 184, 7900, 'temperate', '1', 'swamps, lakes', NULL, 11000000); 392 | INSERT INTO planets VALUES (35, 'Malastare', 26, 201, 18880, 'arid, temperate, tropical', '1.56', 'swamps, deserts, jungles, mountains', NULL, 2000000000); 393 | INSERT INTO planets VALUES (36, 'Dathomir', 24, 491, 10480, 'temperate', '0.9', 'forests, deserts, savannas', NULL, 5200); 394 | INSERT INTO planets VALUES (37, 'Ryloth', 30, 305, 10600, 'temperate, arid, subartic', '1', 'mountains, valleys, deserts, tundra', '5', 1500000000); 395 | INSERT INTO planets VALUES (38, 'Aleen Minor', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 396 | INSERT INTO planets VALUES (39, 'Vulpter', 22, 391, 14900, 'temperate, artic', '1', 'urban, barren', NULL, 421000000); 397 | INSERT INTO planets VALUES (40, 'Troiken', NULL, NULL, NULL, NULL, NULL, 'desert, tundra, rainforests, mountains', NULL, NULL); 398 | INSERT INTO planets VALUES (41, 'Tund', 48, 1770, 12190, NULL, NULL, 'barren, ash', NULL, 0); 399 | INSERT INTO planets VALUES (42, 'Haruun Kal', 25, 383, 10120, 'temperate', '0.98', 'toxic cloudsea, plateaus, volcanoes', NULL, 705300); 400 | INSERT INTO planets VALUES (43, 'Cerea', 27, 386, NULL, 'temperate', '1', 'verdant', '20', 450000000); 401 | INSERT INTO planets VALUES (44, 'Glee Anselm', 33, 206, 15600, 'tropical, temperate', '1', 'lakes, islands, swamps, seas', '80', 500000000); 402 | INSERT INTO planets VALUES (45, 'Iridonia', 29, 413, NULL, NULL, NULL, 'rocky canyons, acid pools', NULL, NULL); 403 | INSERT INTO planets VALUES (46, 'Tholoth', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 404 | INSERT INTO planets VALUES (47, 'Iktotch', 22, 481, NULL, 'arid, rocky, windy', '1', 'rocky', NULL, NULL); 405 | INSERT INTO planets VALUES (48, 'Quermia', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 406 | INSERT INTO planets VALUES (49, 'Dorin', 22, 409, 13400, 'temperate', '1', NULL, NULL, NULL); 407 | INSERT INTO planets VALUES (50, 'Champala', 27, 318, NULL, 'temperate', '1', 'oceans, rainforests, plateaus', NULL, 3500000000); 408 | INSERT INTO planets VALUES (51, 'Mirial', NULL, NULL, NULL, NULL, NULL, 'deserts', NULL, NULL); 409 | INSERT INTO planets VALUES (52, 'Serenno', NULL, NULL, NULL, NULL, NULL, 'rainforests, rivers, mountains', NULL, NULL); 410 | INSERT INTO planets VALUES (53, 'Concord Dawn', NULL, NULL, NULL, NULL, NULL, 'jungles, forests, deserts', NULL, NULL); 411 | INSERT INTO planets VALUES (54, 'Zolan', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 412 | INSERT INTO planets VALUES (55, 'Ojom', NULL, NULL, NULL, 'frigid', NULL, 'oceans, glaciers', '100', 500000000); 413 | INSERT INTO planets VALUES (56, 'Skako', 27, 384, NULL, 'temperate', '1', 'urban, vines', NULL, 500000000000); 414 | INSERT INTO planets VALUES (57, 'Muunilinst', 28, 412, 13800, 'temperate', '1', 'plains, forests, hills, mountains', '25', 5000000000); 415 | INSERT INTO planets VALUES (58, 'Shili', NULL, NULL, NULL, 'temperate', '1', 'cities, savannahs, seas, plains', NULL, NULL); 416 | INSERT INTO planets VALUES (59, 'Kalee', 23, 378, 13850, 'arid, temperate, tropical', '1', 'rainforests, cliffs, canyons, seas', NULL, 4000000000); 417 | INSERT INTO planets VALUES (60, 'Umbara', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 418 | INSERT INTO planets VALUES (1, 'Tatooine', 23, 304, 10465, 'arid', '1 standard', 'desert', '1', 200000); 419 | INSERT INTO planets VALUES (61, 'Jakku', NULL, NULL, NULL, NULL, NULL, 'deserts', NULL, NULL); 420 | 421 | -- 422 | -- TOC entry 4126 (class 0 OID 4163886) 423 | -- Dependencies: 231 424 | -- Data for Name: species; Type: TABLE DATA; Schema: Owner: - 425 | -- 426 | 427 | INSERT INTO species VALUES (5, 'Hutt', 'gastropod', '300', '1000', 'n/a', 'green, brown, tan', 'yellow, red', 'Huttese', 24); 428 | INSERT INTO species VALUES (6, 'Yoda''s species', 'mammal', '66', '900', 'brown, white', 'green, yellow', 'brown, green, yellow', 'Galactic basic', 28); 429 | INSERT INTO species VALUES (7, 'Trandoshan', 'reptile', '200', 'unknown', 'none', 'brown, green', 'yellow, orange', 'Dosh', 29); 430 | INSERT INTO species VALUES (8, 'Mon Calamari', 'amphibian', '160', 'unknown', 'none', 'red, blue, brown, magenta', 'yellow', 'Mon Calamarian', 31); 431 | INSERT INTO species VALUES (9, 'Ewok', 'mammal', '100', 'unknown', 'white, brown, black', 'brown', 'orange, brown', 'Ewokese', 7); 432 | INSERT INTO species VALUES (10, 'Sullustan', 'mammal', '180', 'unknown', 'none', 'pale', 'black', 'Sullutese', 33); 433 | INSERT INTO species VALUES (11, 'Neimodian', 'unknown', '180', 'unknown', 'none', 'grey, green', 'red, pink', 'Neimoidia', 18); 434 | INSERT INTO species VALUES (12, 'Gungan', 'amphibian', '190', 'unknown', 'none', 'brown, green', 'orange', 'Gungan basic', 8); 435 | INSERT INTO species VALUES (13, 'Toydarian', 'mammal', '120', '91', 'none', 'blue, green, grey', 'yellow', 'Toydarian', 34); 436 | INSERT INTO species VALUES (14, 'Dug', 'mammal', '100', 'unknown', 'none', 'brown, purple, grey, red', 'yellow, blue', 'Dugese', 35); 437 | INSERT INTO species VALUES (15, 'Twi''lek', 'mammals', '200', 'unknown', 'none', 'orange, yellow, blue, green, pink, purple, tan', 'blue, brown, orange, pink', 'Twi''leki', 37); 438 | INSERT INTO species VALUES (16, 'Aleena', 'reptile', '80', '79', 'none', 'blue, gray', 'unknown', 'Aleena', 38); 439 | INSERT INTO species VALUES (17, 'Vulptereen', 'unknown', '100', 'unknown', 'none', 'grey', 'yellow', 'vulpterish', 39); 440 | INSERT INTO species VALUES (18, 'Xexto', 'unknown', '125', 'unknown', 'none', 'grey, yellow, purple', 'black', 'Xextese', 40); 441 | INSERT INTO species VALUES (19, 'Toong', 'unknown', '200', 'unknown', 'none', 'grey, green, yellow', 'orange', 'Tundan', 41); 442 | INSERT INTO species VALUES (20, 'Cerean', 'mammal', '200', 'unknown', 'red, blond, black, white', 'pale pink', 'hazel', 'Cerean', 43); 443 | INSERT INTO species VALUES (21, 'Nautolan', 'amphibian', '180', '70', 'none', 'green, blue, brown, red', 'black', 'Nautila', 44); 444 | INSERT INTO species VALUES (22, 'Zabrak', 'mammal', '180', 'unknown', 'black', 'pale, brown, red, orange, yellow', 'brown, orange', 'Zabraki', 45); 445 | INSERT INTO species VALUES (23, 'Tholothian', 'mammal', 'unknown', 'unknown', 'unknown', 'dark', 'blue, indigo', 'unknown', 46); 446 | INSERT INTO species VALUES (24, 'Iktotchi', 'unknown', '180', 'unknown', 'none', 'pink', 'orange', 'Iktotchese', 47); 447 | INSERT INTO species VALUES (25, 'Quermian', 'mammal', '240', '86', 'none', 'white', 'yellow', 'Quermian', 48); 448 | INSERT INTO species VALUES (26, 'Kel Dor', 'unknown', '180', '70', 'none', 'peach, orange, red', 'black, silver', 'Kel Dor', 49); 449 | INSERT INTO species VALUES (27, 'Chagrian', 'amphibian', '190', 'unknown', 'none', 'blue', 'blue', 'Chagria', 50); 450 | INSERT INTO species VALUES (28, 'Geonosian', 'insectoid', '178', 'unknown', 'none', 'green, brown', 'green, hazel', 'Geonosian', 11); 451 | INSERT INTO species VALUES (29, 'Mirialan', 'mammal', '180', 'unknown', 'black, brown', 'yellow, green', 'blue, green, red, yellow, brown, orange', 'Mirialan', 51); 452 | INSERT INTO species VALUES (30, 'Clawdite', 'reptilian', '180', '70', 'none', 'green, yellow', 'yellow', 'Clawdite', 54); 453 | INSERT INTO species VALUES (31, 'Besalisk', 'amphibian', '178', '75', 'none', 'brown', 'yellow', 'besalisk', 55); 454 | INSERT INTO species VALUES (32, 'Kaminoan', 'amphibian', '220', '80', 'none', 'grey, blue', 'black', 'Kaminoan', 10); 455 | INSERT INTO species VALUES (33, 'Skakoan', 'mammal', 'unknown', 'unknown', 'none', 'grey, green', 'unknown', 'Skakoan', 56); 456 | INSERT INTO species VALUES (34, 'Muun', 'mammal', '190', '100', 'none', 'grey, white', 'black', 'Muun', 57); 457 | INSERT INTO species VALUES (35, 'Togruta', 'mammal', '180', '94', 'none', 'red, white, orange, yellow, green, blue', 'red, orange, yellow, green, blue, black', 'Togruti', 58); 458 | INSERT INTO species VALUES (36, 'Kaleesh', 'reptile', '170', '80', 'none', 'brown, orange, tan', 'yellow', 'Kaleesh', 59); 459 | INSERT INTO species VALUES (37, 'Pau''an', 'mammal', '190', '700', 'none', 'grey', 'black', 'Utapese', 12); 460 | INSERT INTO species VALUES (3, 'Wookiee', 'mammal', '210', '400', 'black, brown', 'gray', 'blue, green, yellow, brown, golden, red', 'Shyriiwook', 14); 461 | INSERT INTO species VALUES (2, 'Droid', 'artificial', 'n/a', 'indefinite', 'n/a', 'n/a', 'n/a', 'n/a', NULL); 462 | INSERT INTO species VALUES (1, 'Human', 'mammal', '180', '120', 'blonde, brown, black, red', 'caucasian, black, asian, hispanic', 'brown, blue, green, hazel, grey, amber', 'Galactic Basic', 9); 463 | INSERT INTO species VALUES (4, 'Rodian', 'sentient', '170', 'unknown', 'n/a', 'green, blue', 'black', 'Galactic Basic', 23); 464 | 465 | 466 | -- 467 | -- TOC entry 4118 (class 0 OID 4163845) 468 | -- Dependencies: 223 469 | -- Data for Name: people; Type: TABLE DATA; Schema: Owner: - 470 | -- 471 | 472 | INSERT INTO people VALUES (1, 'Luke Skywalker', '77', 'blond', 'fair', 'blue', '19BBY', 'male', 1, 1, 172); 473 | INSERT INTO people VALUES (2, 'C-3PO', '75', 'n/a', 'gold', 'yellow', '112BBY', 'n/a', 2, 1, 167); 474 | INSERT INTO people VALUES (3, 'R2-D2', '32', 'n/a', 'white, blue', 'red', '33BBY', 'n/a', 2, 8, 96); 475 | INSERT INTO people VALUES (4, 'Darth Vader', '136', 'none', 'white', 'yellow', '41.9BBY', 'male', 1, 1, 202); 476 | INSERT INTO people VALUES (5, 'Leia Organa', '49', 'brown', 'light', 'brown', '19BBY', 'female', 1, 2, 150); 477 | INSERT INTO people VALUES (6, 'Owen Lars', '120', 'brown, grey', 'light', 'blue', '52BBY', 'male', 1, 1, 178); 478 | INSERT INTO people VALUES (7, 'Beru Whitesun lars', '75', 'brown', 'light', 'blue', '47BBY', 'female', 1, 1, 165); 479 | INSERT INTO people VALUES (8, 'R5-D4', '32', 'n/a', 'white, red', 'red', NULL, 'n/a', 2, 1, 97); 480 | INSERT INTO people VALUES (9, 'Biggs Darklighter', '84', 'black', 'light', 'brown', '24BBY', 'male', 1, 1, 183); 481 | INSERT INTO people VALUES (10, 'Obi-Wan Kenobi', '77', 'auburn, white', 'fair', 'blue-gray', '57BBY', 'male', 1, 20, 182); 482 | INSERT INTO people VALUES (11, 'Anakin Skywalker', '84', 'blond', 'fair', 'blue', '41.9BBY', 'male', 1, 1, 188); 483 | INSERT INTO people VALUES (12, 'Wilhuff Tarkin', NULL, 'auburn, grey', 'fair', 'blue', '64BBY', 'male', 1, 21, 180); 484 | INSERT INTO people VALUES (13, 'Chewbacca', '112', 'brown', NULL, 'blue', '200BBY', 'male', 3, 14, 228); 485 | INSERT INTO people VALUES (14, 'Han Solo', '80', 'brown', 'fair', 'brown', '29BBY', 'male', 1, 22, 180); 486 | INSERT INTO people VALUES (15, 'Greedo', '74', 'n/a', 'green', 'black', '44BBY', 'male', 4, 23, 173); 487 | INSERT INTO people VALUES (16, 'Jabba Desilijic Tiure', '1,358', 'n/a', 'green-tan, brown', 'orange', '600BBY', 'hermaphrodite', 5, 24, 175); 488 | INSERT INTO people VALUES (18, 'Wedge Antilles', '77', 'brown', 'fair', 'hazel', '21BBY', 'male', 1, 22, 170); 489 | INSERT INTO people VALUES (19, 'Jek Tono Porkins', '110', 'brown', 'fair', 'blue', NULL, 'male', 1, 26, 180); 490 | INSERT INTO people VALUES (20, 'Yoda', '17', 'white', 'green', 'brown', '896BBY', 'male', 6, 28, 66); 491 | INSERT INTO people VALUES (21, 'Palpatine', '75', 'grey', 'pale', 'yellow', '82BBY', 'male', 1, 8, 170); 492 | INSERT INTO people VALUES (22, 'Boba Fett', '78.2', 'black', 'fair', 'brown', '31.5BBY', 'male', 1, 10, 183); 493 | INSERT INTO people VALUES (23, 'IG-88', '140', 'none', 'metal', 'red', '15BBY', 'none', 2, 28, 200); 494 | INSERT INTO people VALUES (24, 'Bossk', '113', 'none', 'green', 'red', '53BBY', 'male', 7, 29, 190); 495 | INSERT INTO people VALUES (25, 'Lando Calrissian', '79', 'black', 'dark', 'brown', '31BBY', 'male', 1, 30, 177); 496 | INSERT INTO people VALUES (26, 'Lobot', '79', 'none', 'light', 'blue', '37BBY', 'male', 1, 6, 175); 497 | INSERT INTO people VALUES (27, 'Ackbar', '83', 'none', 'brown mottle', 'orange', '41BBY', 'male', 8, 31, 180); 498 | INSERT INTO people VALUES (28, 'Mon Mothma', NULL, 'auburn', 'fair', 'blue', '48BBY', 'female', 1, 32, 150); 499 | INSERT INTO people VALUES (29, 'Arvel Crynyd', NULL, 'brown', 'fair', 'brown', NULL, 'male', 1, 28, NULL); 500 | INSERT INTO people VALUES (30, 'Wicket Systri Warrick', '20', 'brown', 'brown', 'brown', '8BBY', 'male', 9, 7, 88); 501 | INSERT INTO people VALUES (31, 'Nien Nunb', '68', 'none', 'grey', 'black', NULL, 'male', 10, 33, 160); 502 | INSERT INTO people VALUES (32, 'Qui-Gon Jinn', '89', 'brown', 'fair', 'blue', '92BBY', 'male', 1, 28, 193); 503 | INSERT INTO people VALUES (33, 'Nute Gunray', '90', 'none', 'mottled green', 'red', NULL, 'male', 11, 18, 191); 504 | INSERT INTO people VALUES (34, 'Finis Valorum', NULL, 'blond', 'fair', 'blue', '91BBY', 'male', 1, 9, 170); 505 | INSERT INTO people VALUES (36, 'Jar Jar Binks', '66', 'none', 'orange', 'orange', '52BBY', 'male', 12, 8, 196); 506 | INSERT INTO people VALUES (37, 'Roos Tarpals', '82', 'none', 'grey', 'orange', NULL, 'male', 12, 8, 224); 507 | INSERT INTO people VALUES (38, 'Rugor Nass', NULL, 'none', 'green', 'orange', NULL, 'male', 12, 8, 206); 508 | INSERT INTO people VALUES (39, 'Ric Olié', NULL, 'brown', 'fair', 'blue', NULL, 'male', NULL, 8, 183); 509 | INSERT INTO people VALUES (40, 'Watto', NULL, 'black', 'blue, grey', 'yellow', NULL, 'male', 13, 34, 137); 510 | INSERT INTO people VALUES (41, 'Sebulba', '40', 'none', 'grey, red', 'orange', NULL, 'male', 14, 35, 112); 511 | INSERT INTO people VALUES (42, 'Quarsh Panaka', NULL, 'black', 'dark', 'brown', '62BBY', 'male', NULL, 8, 183); 512 | INSERT INTO people VALUES (43, 'Shmi Skywalker', NULL, 'black', 'fair', 'brown', '72BBY', 'female', 1, 1, 163); 513 | INSERT INTO people VALUES (44, 'Darth Maul', '80', 'none', 'red', 'yellow', '54BBY', 'male', 22, 36, 175); 514 | INSERT INTO people VALUES (45, 'Bib Fortuna', NULL, 'none', 'pale', 'pink', NULL, 'male', 15, 37, 180); 515 | INSERT INTO people VALUES (46, 'Ayla Secura', '55', 'none', 'blue', 'hazel', '48BBY', 'female', 15, 37, 178); 516 | INSERT INTO people VALUES (48, 'Dud Bolt', '45', 'none', 'blue, grey', 'yellow', NULL, 'male', 17, 39, 94); 517 | INSERT INTO people VALUES (49, 'Gasgano', NULL, 'none', 'white, blue', 'black', NULL, 'male', 18, 40, 122); 518 | INSERT INTO people VALUES (50, 'Ben Quadinaros', '65', 'none', 'grey, green, yellow', 'orange', NULL, 'male', 19, 41, 163); 519 | INSERT INTO people VALUES (51, 'Mace Windu', '84', 'none', 'dark', 'brown', '72BBY', 'male', 1, 42, 188); 520 | INSERT INTO people VALUES (52, 'Ki-Adi-Mundi', '82', 'white', 'pale', 'yellow', '92BBY', 'male', 20, 43, 198); 521 | INSERT INTO people VALUES (53, 'Kit Fisto', '87', 'none', 'green', 'black', NULL, 'male', 21, 44, 196); 522 | INSERT INTO people VALUES (54, 'Eeth Koth', NULL, 'black', 'brown', 'brown', NULL, 'male', 22, 45, 171); 523 | INSERT INTO people VALUES (55, 'Adi Gallia', '50', 'none', 'dark', 'blue', NULL, 'female', 23, 9, 184); 524 | INSERT INTO people VALUES (56, 'Saesee Tiin', NULL, 'none', 'pale', 'orange', NULL, 'male', 24, 47, 188); 525 | INSERT INTO people VALUES (57, 'Yarael Poof', NULL, 'none', 'white', 'yellow', NULL, 'male', 25, 48, 264); 526 | INSERT INTO people VALUES (58, 'Plo Koon', '80', 'none', 'orange', 'black', '22BBY', 'male', 26, 49, 188); 527 | INSERT INTO people VALUES (59, 'Mas Amedda', NULL, 'none', 'blue', 'blue', NULL, 'male', 27, 50, 196); 528 | INSERT INTO people VALUES (60, 'Gregar Typho', '85', 'black', 'dark', 'brown', NULL, 'male', 1, 8, 185); 529 | INSERT INTO people VALUES (61, 'Cordé', NULL, 'brown', 'light', 'brown', NULL, 'female', 1, 8, 157); 530 | INSERT INTO people VALUES (62, 'Cliegg Lars', NULL, 'brown', 'fair', 'blue', '82BBY', 'male', 1, 1, 183); 531 | INSERT INTO people VALUES (63, 'Poggle the Lesser', '80', 'none', 'green', 'yellow', NULL, 'male', 28, 11, 183); 532 | INSERT INTO people VALUES (64, 'Luminara Unduli', '56.2', 'black', 'yellow', 'blue', '58BBY', 'female', 29, 51, 170); 533 | INSERT INTO people VALUES (65, 'Barriss Offee', '50', 'black', 'yellow', 'blue', '40BBY', 'female', 29, 51, 166); 534 | INSERT INTO people VALUES (66, 'Dormé', NULL, 'brown', 'light', 'brown', NULL, 'female', 1, 8, 165); 535 | INSERT INTO people VALUES (67, 'Dooku', '80', 'white', 'fair', 'brown', '102BBY', 'male', 1, 52, 193); 536 | INSERT INTO people VALUES (68, 'Bail Prestor Organa', NULL, 'black', 'tan', 'brown', '67BBY', 'male', 1, 2, 191); 537 | INSERT INTO people VALUES (69, 'Jango Fett', '79', 'black', 'tan', 'brown', '66BBY', 'male', 1, 53, 183); 538 | INSERT INTO people VALUES (70, 'Zam Wesell', '55', 'blonde', 'fair, green, yellow', 'yellow', NULL, 'female', 30, 54, 168); 539 | INSERT INTO people VALUES (71, 'Dexter Jettster', '102', 'none', 'brown', 'yellow', NULL, 'male', 31, 55, 198); 540 | INSERT INTO people VALUES (72, 'Lama Su', '88', 'none', 'grey', 'black', NULL, 'male', 32, 10, 229); 541 | INSERT INTO people VALUES (73, 'Taun We', NULL, 'none', 'grey', 'black', NULL, 'female', 32, 10, 213); 542 | INSERT INTO people VALUES (74, 'Jocasta Nu', NULL, 'white', 'fair', 'blue', NULL, 'female', 1, 9, 167); 543 | INSERT INTO people VALUES (47, 'Ratts Tyerell', '15', 'none', 'grey, blue', NULL, NULL, 'male', 16, 38, 79); 544 | INSERT INTO people VALUES (75, 'R4-P17', NULL, 'none', 'silver, red', 'red, blue', NULL, 'female', NULL, 28, 96); 545 | INSERT INTO people VALUES (76, 'Wat Tambor', '48', 'none', 'green, grey', NULL, NULL, 'male', 33, 56, 193); 546 | INSERT INTO people VALUES (77, 'San Hill', NULL, 'none', 'grey', 'gold', NULL, 'male', 34, 57, 191); 547 | INSERT INTO people VALUES (78, 'Shaak Ti', '57', 'none', 'red, blue, white', 'black', NULL, 'female', 35, 58, 178); 548 | INSERT INTO people VALUES (79, 'Grievous', '159', 'none', 'brown, white', 'green, yellow', NULL, 'male', 36, 59, 216); 549 | INSERT INTO people VALUES (80, 'Tarfful', '136', 'brown', 'brown', 'blue', NULL, 'male', 3, 14, 234); 550 | INSERT INTO people VALUES (81, 'Raymus Antilles', '79', 'brown', 'light', 'brown', NULL, 'male', 1, 2, 188); 551 | INSERT INTO people VALUES (82, 'Sly Moore', '48', 'none', 'pale', 'white', NULL, 'female', NULL, 60, 178); 552 | INSERT INTO people VALUES (83, 'Tion Medon', '80', 'none', 'grey', 'black', NULL, 'male', 37, 12, 206); 553 | INSERT INTO people VALUES (84, 'Finn', NULL, 'black', 'dark', 'dark', NULL, 'male', 1, 28, NULL); 554 | INSERT INTO people VALUES (85, 'Rey', NULL, 'brown', 'light', 'hazel', NULL, 'female', 1, 28, NULL); 555 | INSERT INTO people VALUES (86, 'Poe Dameron', NULL, 'brown', 'light', 'brown', NULL, 'male', 1, 28, NULL); 556 | INSERT INTO people VALUES (87, 'BB8', NULL, 'none', 'none', 'black', NULL, 'none', 2, 28, NULL); 557 | INSERT INTO people VALUES (88, 'Captain Phasma', NULL, NULL, NULL, NULL, NULL, 'female', NULL, 28, NULL); 558 | INSERT INTO people VALUES (35, 'Padmé Amidala', '45', 'brown', 'light', 'brown', '46BBY', 'female', 1, 8, 165); 559 | 560 | -- 561 | -- TOC entry 4128 (class 0 OID 4163897) 562 | -- Dependencies: 233 563 | -- Data for Name: vessels; Type: TABLE DATA; Schema: Owner: - 564 | -- 565 | 566 | INSERT INTO vessels VALUES (4, 'Sand Crawler', 'Corellia Mining Corporation', 'Digger Crawler', 'vehicle', 'wheeled', 150000, '36.8', '30', 46, 30, '50000', '2 months'); 567 | INSERT INTO vessels VALUES (6, 'T-16 skyhopper', 'Incom Corporation', 'T-16 skyhopper', 'vehicle', 'repulsorcraft', 14500, '10.4', '1200', 1, 1, '50', '0'); 568 | INSERT INTO vessels VALUES (7, 'X-34 landspeeder', 'SoroSuub Corporation', 'X-34 landspeeder', 'vehicle', 'repulsorcraft', 10550, '3.4', '250', 1, 1, '5', NULL); 569 | INSERT INTO vessels VALUES (8, 'TIE/LN starfighter', 'Sienar Fleet Systems', 'Twin Ion Engine/Ln Starfighter', 'vehicle', 'starfighter', NULL, '6.4', '1200', 1, 0, '65', '2 days'); 570 | INSERT INTO vessels VALUES (14, 'Snowspeeder', 'Incom corporation', 't-47 airspeeder', 'vehicle', 'airspeeder', NULL, '4.5', '650', 2, 0, '10', 'none'); 571 | INSERT INTO vessels VALUES (16, 'TIE bomber', 'Sienar Fleet Systems', 'TIE/sa bomber', 'vehicle', 'space/planetary bomber', NULL, '7.8', '850', 1, 0, 'none', '2 days'); 572 | INSERT INTO vessels VALUES (18, 'AT-AT', 'Kuat Drive Yards, Imperial Department of Military Research', 'All Terrain Armored Transport', 'vehicle', 'assault walker', NULL, '20', '60', 5, 40, '1000', NULL); 573 | INSERT INTO vessels VALUES (19, 'AT-ST', 'Kuat Drive Yards, Imperial Department of Military Research', 'All Terrain Scout Transport', 'vehicle', 'walker', NULL, '2', '90', 2, 0, '200', 'none'); 574 | INSERT INTO vessels VALUES (20, 'Storm IV Twin-Pod cloud car', 'Bespin Motors', 'Storm IV Twin-Pod', 'vehicle', 'repulsorcraft', 75000, '7', '1500', 2, 0, '10', '1 day'); 575 | INSERT INTO vessels VALUES (24, 'Sail barge', 'Ubrikkian Industries Custom Vehicle Division', 'Modified Luxury Sail Barge', 'vehicle', 'sail barge', 285000, '30', '100', 26, 500, '2000000', 'Live food tanks'); 576 | INSERT INTO vessels VALUES (25, 'Bantha-II cargo skiff', 'Ubrikkian Industries', 'Bantha-II', 'vehicle', 'repulsorcraft cargo skiff', 8000, '9.5', '250', 5, 16, '135000', '1 day'); 577 | INSERT INTO vessels VALUES (26, 'TIE/IN interceptor', 'Sienar Fleet Systems', 'Twin Ion Engine Interceptor', 'vehicle', 'starfighter', NULL, '9.6', '1250', 1, 0, '75', '2 days'); 578 | INSERT INTO vessels VALUES (30, 'Imperial Speeder Bike', 'Aratech Repulsor Company', '74-Z speeder bike', 'vehicle', 'speeder', 8000, '3', '360', 1, 1, '4', '1 day'); 579 | INSERT INTO vessels VALUES (33, 'Vulture Droid', 'Haor Chall Engineering, Baktoid Armor Workshop', 'Vulture-class droid starfighter', 'vehicle', 'starfighter', NULL, '3.5', '1200', 0, 0, '0', 'none'); 580 | INSERT INTO vessels VALUES (34, 'Multi-Troop Transport', 'Baktoid Armor Workshop', 'Multi-Troop Transport', 'vehicle', 'repulsorcraft', 138000, '31', '35', 4, 112, '12000', NULL); 581 | INSERT INTO vessels VALUES (35, 'Armored Assault Tank', 'Baktoid Armor Workshop', 'Armoured Assault Tank', 'vehicle', 'repulsorcraft', NULL, '9.75', '55', 4, 6, NULL, NULL); 582 | INSERT INTO vessels VALUES (36, 'Single Trooper Aerial Platform', 'Baktoid Armor Workshop', 'Single Trooper Aerial Platform', 'vehicle', 'repulsorcraft', 2500, '2', '400', 1, 0, 'none', 'none'); 583 | INSERT INTO vessels VALUES (37, 'C-9979 landing craft', 'Haor Chall Engineering', 'C-9979 landing craft', 'vehicle', 'landing craft', 200000, '210', '587', 140, 284, '1800000', '1 day'); 584 | INSERT INTO vessels VALUES (38, 'Tribubble bongo', 'Otoh Gunga Bongameken Cooperative', 'Tribubble bongo', 'vehicle', 'submarine', NULL, '15', '85', 1, 2, '1600', NULL); 585 | INSERT INTO vessels VALUES (42, 'Sith speeder', 'Razalon', 'FC-20 speeder bike', 'vehicle', 'speeder', 4000, '1.5', '180', 1, 0, '2', NULL); 586 | INSERT INTO vessels VALUES (44, 'Zephyr-G swoop bike', 'Mobquet Swoops and Speeders', 'Zephyr-G swoop bike', 'vehicle', 'repulsorcraft', 5750, '3.68', '350', 1, 1, '200', 'none'); 587 | INSERT INTO vessels VALUES (45, 'Koro-2 Exodrive airspeeder', 'Desler Gizh Outworld Mobility Corporation', 'Koro-2 Exodrive airspeeder', 'vehicle', 'airspeeder', NULL, '6.6', '800', 1, 1, '80', NULL); 588 | INSERT INTO vessels VALUES (46, 'XJ-6 airspeeder', 'Narglatch AirTech prefabricated kit', 'XJ-6 airspeeder', 'vehicle', 'airspeeder', NULL, '6.23', '720', 1, 1, NULL, NULL); 589 | INSERT INTO vessels VALUES (50, 'LAAT/i', 'Rothana Heavy Engineering', 'Low Altitude Assault Transport/infrantry', 'vehicle', 'gunship', NULL, '17.4', '620', 6, 30, '170', NULL); 590 | INSERT INTO vessels VALUES (51, 'LAAT/c', 'Rothana Heavy Engineering', 'Low Altitude Assault Transport/carrier', 'vehicle', 'gunship', NULL, '28.82', '620', 1, 0, '40000', NULL); 591 | INSERT INTO vessels VALUES (60, 'Tsmeu-6 personal wheel bike', 'Z-Gomot Ternbuell Guppat Corporation', 'Tsmeu-6 personal wheel bike', 'vehicle', 'wheeled walker', 15000, '3.5', '330', 1, 1, '10', 'none'); 592 | INSERT INTO vessels VALUES (62, 'Emergency Firespeeder', NULL, 'Fire suppression speeder', 'vehicle', 'fire suppression ship', NULL, NULL, NULL, 2, NULL, NULL, NULL); 593 | INSERT INTO vessels VALUES (67, 'Droid tri-fighter', 'Colla Designs, Phlac-Arphocc Automata Industries', 'tri-fighter', 'vehicle', 'droid starfighter', 20000, '5.4', '1180', 1, 0, '0', 'none'); 594 | INSERT INTO vessels VALUES (69, 'Oevvaor jet catamaran', 'Appazanna Engineering Works', 'Oevvaor jet catamaran', 'vehicle', 'airspeeder', 12125, '15.1', '420', 2, 2, '50', '3 days'); 595 | INSERT INTO vessels VALUES (70, 'Raddaugh Gnasp fluttercraft', 'Appazanna Engineering Works', 'Raddaugh Gnasp fluttercraft', 'vehicle', 'air speeder', 14750, '7', '310', 2, 0, '20', 'none'); 596 | INSERT INTO vessels VALUES (71, 'Clone turbo tank', 'Kuat Drive Yards', 'HAVw A6 Juggernaut', 'vehicle', 'wheeled walker', 350000, '49.4', '160', 20, 300, '30000', '20 days'); 597 | INSERT INTO vessels VALUES (72, 'Corporate Alliance tank droid', 'Techno Union', 'NR-N99 Persuader-class droid enforcer', 'vehicle', 'droid tank', 49000, '10.96', '100', 0, 4, 'none', 'none'); 598 | INSERT INTO vessels VALUES (73, 'Droid gunship', 'Baktoid Fleet Ordnance, Haor Chall Engineering', 'HMP droid gunship', 'vehicle', 'airspeeder', 60000, '12.3', '820', 0, 0, '0', 'none'); 599 | INSERT INTO vessels VALUES (76, 'AT-RT', 'Kuat Drive Yards', 'All Terrain Recon Transport', 'vehicle', 'walker', 40000, '3.2', '90', 1, 0, '20', '1 day'); 600 | INSERT INTO vessels VALUES (53, 'AT-TE', 'Rothana Heavy Engineering, Kuat Drive Yards', 'All Terrain Tactical Enforcer', 'vehicle', 'walker', NULL, '13.2', '60', 6, 36, '10000', '21 days'); 601 | INSERT INTO vessels VALUES (54, 'SPHA', 'Rothana Heavy Engineering', 'Self-Propelled Heavy Artillery', 'vehicle', 'walker', NULL, '140', '35', 25, 30, '500', '7 days'); 602 | INSERT INTO vessels VALUES (55, 'Flitknot speeder', 'Huppla Pasa Tisc Shipwrights Collective', 'Flitknot speeder', 'vehicle', 'speeder', 8000, '2', '634', 1, 0, NULL, NULL); 603 | INSERT INTO vessels VALUES (56, 'Neimoidian shuttle', 'Haor Chall Engineering', 'Sheathipede-class transport shuttle', 'vehicle', 'transport', NULL, '20', '880', 2, 6, '1000', '7 days'); 604 | INSERT INTO vessels VALUES (57, 'Geonosian starfighter', 'Huppla Pasa Tisc Shipwrights Collective', 'Nantex-class territorial defense', 'vehicle', 'starfighter', NULL, '9.8', '20000', 1, 0, NULL, NULL); 605 | INSERT INTO vessels VALUES (15, 'Executor', 'Kuat Drive Yards, Fondor Shipyards', 'Executor-class star dreadnought', 'starship', 'Star dreadnought', 1143350000, '19000', 'n/a', 279144, 38000, '250000000', '6 years'); 606 | INSERT INTO vessels VALUES (5, 'Sentinel-class landing craft', 'Sienar Fleet Systems, Cyngus Spaceworks', 'Sentinel-class landing craft', 'starship', 'landing craft', 240000, '38', '1000', 5, 75, '180000', '1 month'); 607 | INSERT INTO vessels VALUES (9, 'Death Star', 'Imperial Department of Military Research, Sienar Fleet Systems', 'DS-1 Orbital Battle Station', 'starship', 'Deep Space Mobile Battlestation', 1000000000000, '120000', 'n/a', 342953, 843342, '1000000000000', '3 years'); 608 | INSERT INTO vessels VALUES (10, 'Millennium Falcon', 'Corellian Engineering Corporation', 'YT-1300 light freighter', 'starship', 'Light freighter', 100000, '34.37', '1050', 4, 6, '100000', '2 months'); 609 | INSERT INTO vessels VALUES (11, 'Y-wing', 'Koensayr Manufacturing', 'BTL Y-wing', 'starship', 'assault starfighter', 134999, '14', '1000km', 2, 0, '110', '1 week'); 610 | INSERT INTO vessels VALUES (12, 'X-wing', 'Incom Corporation', 'T-65 X-wing', 'starship', 'Starfighter', 149999, '12.5', '1050', 1, 0, '110', '1 week'); 611 | INSERT INTO vessels VALUES (13, 'TIE Advanced x1', 'Sienar Fleet Systems', 'Twin Ion Engine Advanced x1', 'starship', 'Starfighter', NULL, '9.2', '1200', 1, 0, '150', '5 days'); 612 | INSERT INTO vessels VALUES (21, 'Slave 1', 'Kuat Systems Engineering', 'Firespray-31-class patrol and attack', 'starship', 'Patrol craft', NULL, '21.5', '1000', 1, 6, '70000', '1 month'); 613 | INSERT INTO vessels VALUES (22, 'Imperial shuttle', 'Sienar Fleet Systems', 'Lambda-class T-4a shuttle', 'starship', 'Armed government transport', 240000, '20', '850', 6, 20, '80000', '2 months'); 614 | INSERT INTO vessels VALUES (23, 'EF76 Nebulon-B escort frigate', 'Kuat Drive Yards', 'EF76 Nebulon-B escort frigate', 'starship', 'Escort ship', 8500000, '300', '800', 854, 75, '6000000', '2 years'); 615 | INSERT INTO vessels VALUES (27, 'Calamari Cruiser', 'Mon Calamari shipyards', 'MC80 Liberty type Star Cruiser', 'starship', 'Star Cruiser', 104000000, '1200', 'n/a', 5400, 1200, NULL, '2 years'); 616 | INSERT INTO vessels VALUES (28, 'A-wing', 'Alliance Underground Engineering, Incom Corporation', 'RZ-1 A-wing Interceptor', 'starship', 'Starfighter', 175000, '9.6', '1300', 1, 0, '40', '1 week'); 617 | INSERT INTO vessels VALUES (29, 'B-wing', 'Slayn & Korpil', 'A/SF-01 B-wing starfighter', 'starship', 'Assault Starfighter', 220000, '16.9', '950', 1, 0, '45', '1 week'); 618 | INSERT INTO vessels VALUES (31, 'ReCruiser', 'Corellian Engineering Corporation', 'Consular-class cruiser', 'starship', 'Space cruiser', NULL, '115', '900', 9, 16, NULL, NULL); 619 | INSERT INTO vessels VALUES (39, 'Naboo fighter', 'Theed Palace Space Vessel Engineering Corps', 'N-1 starfighter', 'starship', 'Starfighter', 200000, '11', '1100', 1, 0, '65', '7 days'); 620 | INSERT INTO vessels VALUES (40, 'Naboo Royal Starship', 'Theed Palace Space Vessel Engineering Corps, Nubia Star Drives', 'J-type 327 Nubian royal starship', 'starship', 'yacht', NULL, '76', '920', 8, NULL, NULL, NULL); 621 | INSERT INTO vessels VALUES (41, 'Scimitar', 'ReSienar Systems', 'Star Courier', 'starship', 'Space Transport', 55000000, '26.5', '1180', 1, 6, '2500000', '30 days'); 622 | INSERT INTO vessels VALUES (43, 'J-type diplomatic barge', 'Theed Palace Space Vessel Engineering Corps, Nubia Star Drives', 'J-type diplomatic barge', 'starship', 'Diplomatic barge', 2000000, '39', '2000', 5, 10, NULL, '1 year'); 623 | INSERT INTO vessels VALUES (47, 'AA-9 Coruscant freighter', 'Botajef Shipyards', 'Botajef AA-9 Freighter-Liner', 'starship', 'freighter', NULL, '390', NULL, NULL, 30000, NULL, NULL); 624 | INSERT INTO vessels VALUES (48, 'Jedi starfighter', 'Kuat Systems Engineering', 'Delta-7 Aethersprite-class interceptor', 'starship', 'Starfighter', 180000, '8', '1150', 1, 0, '60', '7 days'); 625 | INSERT INTO vessels VALUES (49, 'H-type Nubian yacht', 'Theed Palace Space Vessel Engineering Corps', 'H-type Nubian yacht', 'starship', 'yacht', NULL, '47.9', '8000', 4, NULL, NULL, NULL); 626 | INSERT INTO vessels VALUES (3, 'Star Destroyer', 'Kuat Drive Yards', 'Imperial I-class Star Destroyer', 'starship', 'Star Destroyer', 150000000, '1,600', '975', 47060, 0, '36000000', '2 years'); 627 | INSERT INTO vessels VALUES (59, 'Trade Federation cruiser', 'Rendili StarDrive, Free Dac Volunteers Engineering corps.', 'Providence-class carrier/destroyer', 'starship', 'capital ship', 125000000, '1088', '1050', 600, 48247, '50000000', '4 years'); 628 | INSERT INTO vessels VALUES (61, 'Theta-class T-2c shuttle', 'Cygnus Spaceworks', 'Theta-class T-2c shuttle', 'starship', 'transport', 1000000, '18.5', '2000', 5, 16, '50000', '56 days'); 629 | INSERT INTO vessels VALUES (77, 'T-70 X-wing fighter', 'Incom', 'T-70 X-wing fighter', 'starship', 'fighter', NULL, NULL, NULL, 1, NULL, NULL, NULL); 630 | INSERT INTO vessels VALUES (17, 'Rebel transport', 'Gallofree Yards, Inc.', 'GR-75 medium transport', 'starship', 'Medium transport', NULL, '90', '650', 6, 90, '19000000', '6 months'); 631 | INSERT INTO vessels VALUES (32, 'Droid control ship', 'Hoersch-Kessel Drive, Inc.', 'Lucrehulk-class Droid Control Ship', 'starship', 'Droid control ship', NULL, '3170', 'n/a', 175, 139000, '4000000000', '500 days'); 632 | INSERT INTO vessels VALUES (52, 'ReAssault ship', 'Rothana Heavy Engineering', 'Acclamator I-class assault ship', 'starship', 'assault ship', NULL, '752', NULL, 700, 16000, '11250000', '2 years'); 633 | INSERT INTO vessels VALUES (58, 'Solar Sailer', 'Huppla Pasa Tisc Shipwrights Collective', 'Punworcca 116-class interstellar sloop', 'starship', 'yacht', 35700, '15.2', '1600', 3, 11, '240', '7 days'); 634 | INSERT INTO vessels VALUES (63, 'Reattack cruiser', 'Kuat Drive Yards, Allanteen Six shipyards', 'Senator-class Star Destroyer', 'starship', 'star destroyer', 59000000, '1137', '975', 7400, 2000, '20000000', '2 years'); 635 | INSERT INTO vessels VALUES (64, 'Naboo star skiff', 'Theed Palace Space Vessel Engineering Corps/Nubia Star Drives, Incorporated', 'J-type star skiff', 'starship', 'yacht', NULL, '29.2', '1050', 3, 3, NULL, NULL); 636 | INSERT INTO vessels VALUES (65, 'Jedi Interceptor', 'Kuat Systems Engineering', 'Eta-2 Actis-class light interceptor', 'starship', 'starfighter', 320000, '5.47', '1500', 1, 0, '60', '2 days'); 637 | INSERT INTO vessels VALUES (66, 'arc-170', 'Incom Corporation, Subpro Corporation', 'Aggressive Reconnaissance-170 starfighte', 'starship', 'starfighter', 155000, '14.5', '1000', 3, 0, '110', '5 days'); 638 | INSERT INTO vessels VALUES (74, 'Belbullab-22 starfighter', 'Feethan Ottraw Scalable Assemblies', 'Belbullab-22 starfighter', 'starship', 'starfighter', 168000, '6.71', '1100', 1, 0, '140', '7 days'); 639 | INSERT INTO vessels VALUES (75, 'V-wing', 'Kuat Systems Engineering', 'Alpha-3 Nimbus-class V-wing starfighter', 'starship', 'starfighter', 102500, '7.9', '1050', 1, 0, '60', '15 hours'); 640 | INSERT INTO vessels VALUES (2, 'CR90 corvette', 'Corellian Engineering Corporation', 'CR90 corvette', 'starship', 'corvette', 3500000, '150', '950', 165, 600, '3000000', '1 year'); 641 | INSERT INTO vessels VALUES (68, 'Banking clan frigate', 'Hoersch-Kessel Drive, Inc, Gwori Revolutionary Industries', 'Munificent-class star frigate', 'starship', 'cruiser', 57000000, '825', NULL, 200, NULL, '40000000', '2 years'); 642 | 643 | 644 | 645 | -- 646 | -- TOC entry 4138 (class 0 OID 4163940) 647 | -- Dependencies: 243 648 | -- Data for Name: starship_specs; Type: TABLE DATA; Schema: Owner: - 649 | -- 650 | 651 | INSERT INTO starship_specs VALUES (1, '2.0', '40', 15); 652 | INSERT INTO starship_specs VALUES (2, '1.0', '70', 5); 653 | INSERT INTO starship_specs VALUES (3, '4.0', '10', 9); 654 | INSERT INTO starship_specs VALUES (4, '0.5', '75', 10); 655 | INSERT INTO starship_specs VALUES (5, '1.0', '80', 11); 656 | INSERT INTO starship_specs VALUES (6, '1.0', '100', 12); 657 | INSERT INTO starship_specs VALUES (7, '1.0', '105', 13); 658 | INSERT INTO starship_specs VALUES (8, '3.0', '70', 21); 659 | INSERT INTO starship_specs VALUES (9, '1.0', '50', 22); 660 | INSERT INTO starship_specs VALUES (10, '2.0', '40', 23); 661 | INSERT INTO starship_specs VALUES (11, '1.0', '60', 27); 662 | INSERT INTO starship_specs VALUES (12, '1.0', '120', 28); 663 | INSERT INTO starship_specs VALUES (13, '2.0', '91', 29); 664 | INSERT INTO starship_specs VALUES (14, '2.0', NULL, 31); 665 | INSERT INTO starship_specs VALUES (15, '1.0', NULL, 39); 666 | INSERT INTO starship_specs VALUES (16, '1.8', NULL, 40); 667 | INSERT INTO starship_specs VALUES (17, '1.5', NULL, 41); 668 | INSERT INTO starship_specs VALUES (18, '0.7', NULL, 43); 669 | INSERT INTO starship_specs VALUES (19, NULL, NULL, 47); 670 | INSERT INTO starship_specs VALUES (20, '1.0', NULL, 48); 671 | INSERT INTO starship_specs VALUES (21, '0.9', NULL, 49); 672 | INSERT INTO starship_specs VALUES (22, '2.0', '60', 3); 673 | INSERT INTO starship_specs VALUES (23, '1.5', NULL, 59); 674 | INSERT INTO starship_specs VALUES (24, '1.0', NULL, 61); 675 | INSERT INTO starship_specs VALUES (25, NULL, NULL, 77); 676 | INSERT INTO starship_specs VALUES (26, '4.0', '20', 17); 677 | INSERT INTO starship_specs VALUES (27, '2.0', NULL, 32); 678 | INSERT INTO starship_specs VALUES (28, '0.6', NULL, 52); 679 | INSERT INTO starship_specs VALUES (29, '1.5', NULL, 58); 680 | INSERT INTO starship_specs VALUES (30, '1.0', NULL, 63); 681 | INSERT INTO starship_specs VALUES (31, '0.5', NULL, 64); 682 | INSERT INTO starship_specs VALUES (32, '1.0', NULL, 65); 683 | INSERT INTO starship_specs VALUES (33, '1.0', '100', 66); 684 | INSERT INTO starship_specs VALUES (34, '6', NULL, 74); 685 | INSERT INTO starship_specs VALUES (35, '1.0', NULL, 75); 686 | INSERT INTO starship_specs VALUES (36, '2.0', '60', 2); 687 | INSERT INTO starship_specs VALUES (37, '1.0', NULL, 68); 688 | 689 | 690 | -- 691 | -- TOC entry 4122 (class 0 OID 4163867) 692 | -- Dependencies: 227 693 | -- Data for Name: people_in_films; Type: TABLE DATA; Schema: Owner: - 694 | -- 695 | 696 | INSERT INTO people_in_films VALUES (1, 1, 1); 697 | INSERT INTO people_in_films VALUES (2, 2, 1); 698 | INSERT INTO people_in_films VALUES (3, 3, 1); 699 | INSERT INTO people_in_films VALUES (4, 4, 1); 700 | INSERT INTO people_in_films VALUES (5, 5, 1); 701 | INSERT INTO people_in_films VALUES (6, 6, 1); 702 | INSERT INTO people_in_films VALUES (7, 7, 1); 703 | INSERT INTO people_in_films VALUES (8, 8, 1); 704 | INSERT INTO people_in_films VALUES (9, 9, 1); 705 | INSERT INTO people_in_films VALUES (10, 10, 1); 706 | INSERT INTO people_in_films VALUES (11, 12, 1); 707 | INSERT INTO people_in_films VALUES (12, 13, 1); 708 | INSERT INTO people_in_films VALUES (13, 14, 1); 709 | INSERT INTO people_in_films VALUES (14, 15, 1); 710 | INSERT INTO people_in_films VALUES (15, 16, 1); 711 | INSERT INTO people_in_films VALUES (16, 18, 1); 712 | INSERT INTO people_in_films VALUES (17, 19, 1); 713 | INSERT INTO people_in_films VALUES (18, 81, 1); 714 | INSERT INTO people_in_films VALUES (19, 2, 5); 715 | INSERT INTO people_in_films VALUES (20, 3, 5); 716 | INSERT INTO people_in_films VALUES (21, 6, 5); 717 | INSERT INTO people_in_films VALUES (22, 7, 5); 718 | INSERT INTO people_in_films VALUES (23, 10, 5); 719 | INSERT INTO people_in_films VALUES (24, 11, 5); 720 | INSERT INTO people_in_films VALUES (25, 20, 5); 721 | INSERT INTO people_in_films VALUES (26, 21, 5); 722 | INSERT INTO people_in_films VALUES (27, 22, 5); 723 | INSERT INTO people_in_films VALUES (28, 33, 5); 724 | INSERT INTO people_in_films VALUES (29, 36, 5); 725 | INSERT INTO people_in_films VALUES (30, 40, 5); 726 | INSERT INTO people_in_films VALUES (31, 43, 5); 727 | INSERT INTO people_in_films VALUES (32, 46, 5); 728 | INSERT INTO people_in_films VALUES (33, 51, 5); 729 | INSERT INTO people_in_films VALUES (34, 52, 5); 730 | INSERT INTO people_in_films VALUES (35, 53, 5); 731 | INSERT INTO people_in_films VALUES (36, 58, 5); 732 | INSERT INTO people_in_films VALUES (37, 59, 5); 733 | INSERT INTO people_in_films VALUES (38, 60, 5); 734 | INSERT INTO people_in_films VALUES (39, 61, 5); 735 | INSERT INTO people_in_films VALUES (40, 62, 5); 736 | INSERT INTO people_in_films VALUES (41, 63, 5); 737 | INSERT INTO people_in_films VALUES (42, 64, 5); 738 | INSERT INTO people_in_films VALUES (43, 65, 5); 739 | INSERT INTO people_in_films VALUES (44, 66, 5); 740 | INSERT INTO people_in_films VALUES (45, 67, 5); 741 | INSERT INTO people_in_films VALUES (46, 68, 5); 742 | INSERT INTO people_in_films VALUES (47, 69, 5); 743 | INSERT INTO people_in_films VALUES (48, 70, 5); 744 | INSERT INTO people_in_films VALUES (49, 71, 5); 745 | INSERT INTO people_in_films VALUES (50, 72, 5); 746 | INSERT INTO people_in_films VALUES (51, 73, 5); 747 | INSERT INTO people_in_films VALUES (52, 74, 5); 748 | INSERT INTO people_in_films VALUES (53, 75, 5); 749 | INSERT INTO people_in_films VALUES (54, 76, 5); 750 | INSERT INTO people_in_films VALUES (55, 77, 5); 751 | INSERT INTO people_in_films VALUES (56, 78, 5); 752 | INSERT INTO people_in_films VALUES (57, 82, 5); 753 | INSERT INTO people_in_films VALUES (58, 35, 5); 754 | INSERT INTO people_in_films VALUES (59, 2, 4); 755 | INSERT INTO people_in_films VALUES (60, 3, 4); 756 | INSERT INTO people_in_films VALUES (61, 10, 4); 757 | INSERT INTO people_in_films VALUES (62, 11, 4); 758 | INSERT INTO people_in_films VALUES (63, 16, 4); 759 | INSERT INTO people_in_films VALUES (64, 20, 4); 760 | INSERT INTO people_in_films VALUES (65, 21, 4); 761 | INSERT INTO people_in_films VALUES (66, 32, 4); 762 | INSERT INTO people_in_films VALUES (67, 33, 4); 763 | INSERT INTO people_in_films VALUES (68, 34, 4); 764 | INSERT INTO people_in_films VALUES (69, 36, 4); 765 | INSERT INTO people_in_films VALUES (70, 37, 4); 766 | INSERT INTO people_in_films VALUES (71, 38, 4); 767 | INSERT INTO people_in_films VALUES (72, 39, 4); 768 | INSERT INTO people_in_films VALUES (73, 40, 4); 769 | INSERT INTO people_in_films VALUES (74, 41, 4); 770 | INSERT INTO people_in_films VALUES (75, 42, 4); 771 | INSERT INTO people_in_films VALUES (76, 43, 4); 772 | INSERT INTO people_in_films VALUES (77, 44, 4); 773 | INSERT INTO people_in_films VALUES (78, 46, 4); 774 | INSERT INTO people_in_films VALUES (79, 48, 4); 775 | INSERT INTO people_in_films VALUES (80, 49, 4); 776 | INSERT INTO people_in_films VALUES (81, 50, 4); 777 | INSERT INTO people_in_films VALUES (82, 51, 4); 778 | INSERT INTO people_in_films VALUES (83, 52, 4); 779 | INSERT INTO people_in_films VALUES (84, 53, 4); 780 | INSERT INTO people_in_films VALUES (85, 54, 4); 781 | INSERT INTO people_in_films VALUES (86, 55, 4); 782 | INSERT INTO people_in_films VALUES (87, 56, 4); 783 | INSERT INTO people_in_films VALUES (88, 57, 4); 784 | INSERT INTO people_in_films VALUES (89, 58, 4); 785 | INSERT INTO people_in_films VALUES (90, 59, 4); 786 | INSERT INTO people_in_films VALUES (91, 47, 4); 787 | INSERT INTO people_in_films VALUES (92, 35, 4); 788 | INSERT INTO people_in_films VALUES (93, 1, 6); 789 | INSERT INTO people_in_films VALUES (94, 2, 6); 790 | INSERT INTO people_in_films VALUES (95, 3, 6); 791 | INSERT INTO people_in_films VALUES (96, 4, 6); 792 | INSERT INTO people_in_films VALUES (97, 5, 6); 793 | INSERT INTO people_in_films VALUES (98, 6, 6); 794 | INSERT INTO people_in_films VALUES (99, 7, 6); 795 | INSERT INTO people_in_films VALUES (100, 10, 6); 796 | INSERT INTO people_in_films VALUES (101, 11, 6); 797 | INSERT INTO people_in_films VALUES (102, 12, 6); 798 | INSERT INTO people_in_films VALUES (103, 13, 6); 799 | INSERT INTO people_in_films VALUES (104, 20, 6); 800 | INSERT INTO people_in_films VALUES (105, 21, 6); 801 | INSERT INTO people_in_films VALUES (106, 33, 6); 802 | INSERT INTO people_in_films VALUES (107, 46, 6); 803 | INSERT INTO people_in_films VALUES (108, 51, 6); 804 | INSERT INTO people_in_films VALUES (109, 52, 6); 805 | INSERT INTO people_in_films VALUES (110, 53, 6); 806 | INSERT INTO people_in_films VALUES (111, 54, 6); 807 | INSERT INTO people_in_films VALUES (112, 55, 6); 808 | INSERT INTO people_in_films VALUES (113, 56, 6); 809 | INSERT INTO people_in_films VALUES (114, 58, 6); 810 | INSERT INTO people_in_films VALUES (115, 63, 6); 811 | INSERT INTO people_in_films VALUES (116, 64, 6); 812 | INSERT INTO people_in_films VALUES (117, 67, 6); 813 | INSERT INTO people_in_films VALUES (118, 68, 6); 814 | INSERT INTO people_in_films VALUES (119, 75, 6); 815 | INSERT INTO people_in_films VALUES (120, 78, 6); 816 | INSERT INTO people_in_films VALUES (121, 79, 6); 817 | INSERT INTO people_in_films VALUES (122, 80, 6); 818 | INSERT INTO people_in_films VALUES (123, 81, 6); 819 | INSERT INTO people_in_films VALUES (124, 82, 6); 820 | INSERT INTO people_in_films VALUES (125, 83, 6); 821 | INSERT INTO people_in_films VALUES (126, 35, 6); 822 | INSERT INTO people_in_films VALUES (127, 1, 3); 823 | INSERT INTO people_in_films VALUES (128, 2, 3); 824 | INSERT INTO people_in_films VALUES (129, 3, 3); 825 | INSERT INTO people_in_films VALUES (130, 4, 3); 826 | INSERT INTO people_in_films VALUES (131, 5, 3); 827 | INSERT INTO people_in_films VALUES (132, 10, 3); 828 | INSERT INTO people_in_films VALUES (133, 13, 3); 829 | INSERT INTO people_in_films VALUES (134, 14, 3); 830 | INSERT INTO people_in_films VALUES (135, 16, 3); 831 | INSERT INTO people_in_films VALUES (136, 18, 3); 832 | INSERT INTO people_in_films VALUES (137, 20, 3); 833 | INSERT INTO people_in_films VALUES (138, 21, 3); 834 | INSERT INTO people_in_films VALUES (139, 22, 3); 835 | INSERT INTO people_in_films VALUES (140, 25, 3); 836 | INSERT INTO people_in_films VALUES (141, 27, 3); 837 | INSERT INTO people_in_films VALUES (142, 28, 3); 838 | INSERT INTO people_in_films VALUES (143, 29, 3); 839 | INSERT INTO people_in_films VALUES (144, 30, 3); 840 | INSERT INTO people_in_films VALUES (145, 31, 3); 841 | INSERT INTO people_in_films VALUES (146, 45, 3); 842 | INSERT INTO people_in_films VALUES (147, 1, 2); 843 | INSERT INTO people_in_films VALUES (148, 2, 2); 844 | INSERT INTO people_in_films VALUES (149, 3, 2); 845 | INSERT INTO people_in_films VALUES (150, 4, 2); 846 | INSERT INTO people_in_films VALUES (151, 5, 2); 847 | INSERT INTO people_in_films VALUES (152, 10, 2); 848 | INSERT INTO people_in_films VALUES (153, 13, 2); 849 | INSERT INTO people_in_films VALUES (154, 14, 2); 850 | INSERT INTO people_in_films VALUES (155, 18, 2); 851 | INSERT INTO people_in_films VALUES (156, 20, 2); 852 | INSERT INTO people_in_films VALUES (157, 21, 2); 853 | INSERT INTO people_in_films VALUES (158, 22, 2); 854 | INSERT INTO people_in_films VALUES (159, 23, 2); 855 | INSERT INTO people_in_films VALUES (160, 24, 2); 856 | INSERT INTO people_in_films VALUES (161, 25, 2); 857 | INSERT INTO people_in_films VALUES (162, 26, 2); 858 | INSERT INTO people_in_films VALUES (163, 1, 7); 859 | INSERT INTO people_in_films VALUES (164, 3, 7); 860 | INSERT INTO people_in_films VALUES (165, 5, 7); 861 | INSERT INTO people_in_films VALUES (166, 13, 7); 862 | INSERT INTO people_in_films VALUES (167, 14, 7); 863 | INSERT INTO people_in_films VALUES (168, 27, 7); 864 | INSERT INTO people_in_films VALUES (169, 84, 7); 865 | INSERT INTO people_in_films VALUES (170, 85, 7); 866 | INSERT INTO people_in_films VALUES (171, 86, 7); 867 | INSERT INTO people_in_films VALUES (172, 87, 7); 868 | INSERT INTO people_in_films VALUES (173, 88, 7); 869 | 870 | 871 | -- 872 | -- TOC entry 4134 (class 0 OID 4163924) 873 | -- Dependencies: 239 874 | -- Data for Name: pilots; Type: TABLE DATA; Schema: Owner: - 875 | -- 876 | 877 | INSERT INTO pilots VALUES (1, 1, 14); 878 | INSERT INTO pilots VALUES (2, 18, 14); 879 | INSERT INTO pilots VALUES (3, 13, 19); 880 | INSERT INTO pilots VALUES (4, 1, 30); 881 | INSERT INTO pilots VALUES (5, 5, 30); 882 | INSERT INTO pilots VALUES (6, 10, 38); 883 | INSERT INTO pilots VALUES (7, 32, 38); 884 | INSERT INTO pilots VALUES (8, 44, 42); 885 | INSERT INTO pilots VALUES (9, 11, 44); 886 | INSERT INTO pilots VALUES (10, 70, 45); 887 | INSERT INTO pilots VALUES (11, 11, 46); 888 | INSERT INTO pilots VALUES (12, 79, 60); 889 | INSERT INTO pilots VALUES (13, 67, 55); 890 | INSERT INTO pilots VALUES (14, 13, 10); 891 | INSERT INTO pilots VALUES (15, 14, 10); 892 | INSERT INTO pilots VALUES (16, 25, 10); 893 | INSERT INTO pilots VALUES (17, 31, 10); 894 | INSERT INTO pilots VALUES (18, 1, 12); 895 | INSERT INTO pilots VALUES (19, 9, 12); 896 | INSERT INTO pilots VALUES (20, 18, 12); 897 | INSERT INTO pilots VALUES (21, 19, 12); 898 | INSERT INTO pilots VALUES (22, 4, 13); 899 | INSERT INTO pilots VALUES (23, 22, 21); 900 | INSERT INTO pilots VALUES (24, 1, 22); 901 | INSERT INTO pilots VALUES (25, 13, 22); 902 | INSERT INTO pilots VALUES (26, 14, 22); 903 | INSERT INTO pilots VALUES (27, 29, 28); 904 | INSERT INTO pilots VALUES (28, 11, 39); 905 | INSERT INTO pilots VALUES (29, 60, 39); 906 | INSERT INTO pilots VALUES (30, 35, 39); 907 | INSERT INTO pilots VALUES (31, 39, 40); 908 | INSERT INTO pilots VALUES (32, 44, 41); 909 | INSERT INTO pilots VALUES (33, 10, 48); 910 | INSERT INTO pilots VALUES (34, 58, 48); 911 | INSERT INTO pilots VALUES (35, 35, 49); 912 | INSERT INTO pilots VALUES (36, 10, 59); 913 | INSERT INTO pilots VALUES (37, 11, 59); 914 | INSERT INTO pilots VALUES (38, 86, 77); 915 | INSERT INTO pilots VALUES (39, 10, 64); 916 | INSERT INTO pilots VALUES (40, 35, 64); 917 | INSERT INTO pilots VALUES (41, 10, 65); 918 | INSERT INTO pilots VALUES (42, 11, 65); 919 | INSERT INTO pilots VALUES (43, 10, 74); 920 | INSERT INTO pilots VALUES (44, 79, 74); 921 | 922 | 923 | 924 | 925 | -- 926 | -- TOC entry 4132 (class 0 OID 4163916) 927 | -- Dependencies: 237 928 | -- Data for Name: planets_in_films; Type: TABLE DATA; Schema: Owner: - 929 | -- 930 | 931 | INSERT INTO planets_in_films VALUES (1, 1, 2); 932 | INSERT INTO planets_in_films VALUES (2, 1, 3); 933 | INSERT INTO planets_in_films VALUES (3, 1, 1); 934 | INSERT INTO planets_in_films VALUES (4, 5, 8); 935 | INSERT INTO planets_in_films VALUES (5, 5, 9); 936 | INSERT INTO planets_in_films VALUES (6, 5, 10); 937 | INSERT INTO planets_in_films VALUES (7, 5, 11); 938 | INSERT INTO planets_in_films VALUES (8, 5, 1); 939 | INSERT INTO planets_in_films VALUES (9, 4, 8); 940 | INSERT INTO planets_in_films VALUES (10, 4, 9); 941 | INSERT INTO planets_in_films VALUES (11, 4, 1); 942 | INSERT INTO planets_in_films VALUES (12, 6, 2); 943 | INSERT INTO planets_in_films VALUES (13, 6, 5); 944 | INSERT INTO planets_in_films VALUES (14, 6, 8); 945 | INSERT INTO planets_in_films VALUES (15, 6, 9); 946 | INSERT INTO planets_in_films VALUES (16, 6, 12); 947 | INSERT INTO planets_in_films VALUES (17, 6, 13); 948 | INSERT INTO planets_in_films VALUES (18, 6, 14); 949 | INSERT INTO planets_in_films VALUES (19, 6, 15); 950 | INSERT INTO planets_in_films VALUES (20, 6, 16); 951 | INSERT INTO planets_in_films VALUES (21, 6, 17); 952 | INSERT INTO planets_in_films VALUES (22, 6, 18); 953 | INSERT INTO planets_in_films VALUES (23, 6, 19); 954 | INSERT INTO planets_in_films VALUES (24, 6, 1); 955 | INSERT INTO planets_in_films VALUES (25, 3, 5); 956 | INSERT INTO planets_in_films VALUES (26, 3, 7); 957 | INSERT INTO planets_in_films VALUES (27, 3, 8); 958 | INSERT INTO planets_in_films VALUES (28, 3, 9); 959 | INSERT INTO planets_in_films VALUES (29, 3, 1); 960 | INSERT INTO planets_in_films VALUES (30, 2, 4); 961 | INSERT INTO planets_in_films VALUES (31, 2, 5); 962 | INSERT INTO planets_in_films VALUES (32, 2, 6); 963 | INSERT INTO planets_in_films VALUES (33, 2, 27); 964 | INSERT INTO planets_in_films VALUES (34, 7, 61); 965 | 966 | 967 | 968 | 969 | -- 970 | -- TOC entry 4130 (class 0 OID 4163908) 971 | -- Dependencies: 235 972 | -- Data for Name: species_in_films; Type: TABLE DATA; Schema: Owner: - 973 | -- 974 | 975 | INSERT INTO species_in_films VALUES (1, 1, 5); 976 | INSERT INTO species_in_films VALUES (2, 1, 3); 977 | INSERT INTO species_in_films VALUES (3, 1, 2); 978 | INSERT INTO species_in_films VALUES (4, 1, 1); 979 | INSERT INTO species_in_films VALUES (5, 1, 4); 980 | INSERT INTO species_in_films VALUES (6, 5, 32); 981 | INSERT INTO species_in_films VALUES (7, 5, 33); 982 | INSERT INTO species_in_films VALUES (8, 5, 2); 983 | INSERT INTO species_in_films VALUES (9, 5, 35); 984 | INSERT INTO species_in_films VALUES (10, 5, 6); 985 | INSERT INTO species_in_films VALUES (11, 5, 1); 986 | INSERT INTO species_in_films VALUES (12, 5, 12); 987 | INSERT INTO species_in_films VALUES (13, 5, 34); 988 | INSERT INTO species_in_films VALUES (14, 5, 13); 989 | INSERT INTO species_in_films VALUES (15, 5, 15); 990 | INSERT INTO species_in_films VALUES (16, 5, 28); 991 | INSERT INTO species_in_films VALUES (17, 5, 29); 992 | INSERT INTO species_in_films VALUES (18, 5, 30); 993 | INSERT INTO species_in_films VALUES (19, 5, 31); 994 | INSERT INTO species_in_films VALUES (20, 4, 1); 995 | INSERT INTO species_in_films VALUES (21, 4, 2); 996 | INSERT INTO species_in_films VALUES (22, 4, 6); 997 | INSERT INTO species_in_films VALUES (23, 4, 11); 998 | INSERT INTO species_in_films VALUES (24, 4, 12); 999 | INSERT INTO species_in_films VALUES (25, 4, 13); 1000 | INSERT INTO species_in_films VALUES (26, 4, 14); 1001 | INSERT INTO species_in_films VALUES (27, 4, 15); 1002 | INSERT INTO species_in_films VALUES (28, 4, 16); 1003 | INSERT INTO species_in_films VALUES (29, 4, 17); 1004 | INSERT INTO species_in_films VALUES (30, 4, 18); 1005 | INSERT INTO species_in_films VALUES (31, 4, 19); 1006 | INSERT INTO species_in_films VALUES (32, 4, 20); 1007 | INSERT INTO species_in_films VALUES (33, 4, 21); 1008 | INSERT INTO species_in_films VALUES (34, 4, 22); 1009 | INSERT INTO species_in_films VALUES (35, 4, 23); 1010 | INSERT INTO species_in_films VALUES (36, 4, 24); 1011 | INSERT INTO species_in_films VALUES (37, 4, 25); 1012 | INSERT INTO species_in_films VALUES (38, 4, 26); 1013 | INSERT INTO species_in_films VALUES (39, 4, 27); 1014 | INSERT INTO species_in_films VALUES (40, 6, 19); 1015 | INSERT INTO species_in_films VALUES (41, 6, 33); 1016 | INSERT INTO species_in_films VALUES (42, 6, 2); 1017 | INSERT INTO species_in_films VALUES (43, 6, 3); 1018 | INSERT INTO species_in_films VALUES (44, 6, 36); 1019 | INSERT INTO species_in_films VALUES (45, 6, 37); 1020 | INSERT INTO species_in_films VALUES (46, 6, 6); 1021 | INSERT INTO species_in_films VALUES (47, 6, 1); 1022 | INSERT INTO species_in_films VALUES (48, 6, 34); 1023 | INSERT INTO species_in_films VALUES (49, 6, 15); 1024 | INSERT INTO species_in_films VALUES (50, 6, 35); 1025 | INSERT INTO species_in_films VALUES (51, 6, 20); 1026 | INSERT INTO species_in_films VALUES (52, 6, 23); 1027 | INSERT INTO species_in_films VALUES (53, 6, 24); 1028 | INSERT INTO species_in_films VALUES (54, 6, 25); 1029 | INSERT INTO species_in_films VALUES (55, 6, 26); 1030 | INSERT INTO species_in_films VALUES (56, 6, 27); 1031 | INSERT INTO species_in_films VALUES (57, 6, 28); 1032 | INSERT INTO species_in_films VALUES (58, 6, 29); 1033 | INSERT INTO species_in_films VALUES (59, 6, 30); 1034 | INSERT INTO species_in_films VALUES (60, 3, 1); 1035 | INSERT INTO species_in_films VALUES (61, 3, 2); 1036 | INSERT INTO species_in_films VALUES (62, 3, 3); 1037 | INSERT INTO species_in_films VALUES (63, 3, 5); 1038 | INSERT INTO species_in_films VALUES (64, 3, 6); 1039 | INSERT INTO species_in_films VALUES (65, 3, 8); 1040 | INSERT INTO species_in_films VALUES (66, 3, 9); 1041 | INSERT INTO species_in_films VALUES (67, 3, 10); 1042 | INSERT INTO species_in_films VALUES (68, 3, 15); 1043 | INSERT INTO species_in_films VALUES (69, 2, 6); 1044 | INSERT INTO species_in_films VALUES (70, 2, 7); 1045 | INSERT INTO species_in_films VALUES (71, 2, 3); 1046 | INSERT INTO species_in_films VALUES (72, 2, 2); 1047 | INSERT INTO species_in_films VALUES (73, 2, 1); 1048 | INSERT INTO species_in_films VALUES (74, 7, 3); 1049 | INSERT INTO species_in_films VALUES (75, 7, 2); 1050 | INSERT INTO species_in_films VALUES (76, 7, 1); 1051 | 1052 | 1053 | select pg_catalog.setval('people__id_seq', 89, false); 1054 | select pg_catalog.setval('planets__id_seq', 62, false); 1055 | select pg_catalog.setval('vessels__id_seq', 78, false); 1056 | select pg_catalog.setval('species__id_seq', 38, false); 1057 | select pg_catalog.setval('films__id_seq', 8, false); 1058 | select pg_catalog.setval('people_in_films__id_seq', 174, false); 1059 | select pg_catalog.setval('planets_in_films__id_seq', 35, false); 1060 | select pg_catalog.setval('species_in_films__id_seq', 77, false); 1061 | select pg_catalog.setval('pilots__id_seq', 45, false); 1062 | select pg_catalog.setval('starship_specs__id_seq', 39, false); 1063 | 1064 | REVOKE ALL ON SCHEMA public FROM PUBLIC; 1065 | REVOKE ALL ON SCHEMA public FROM hqladmin; 1066 | GRANT ALL ON SCHEMA public TO hqladmin; 1067 | GRANT ALL ON SCHEMA public TO PUBLIC; 1068 | 1069 | -- Completed on 2019-09-11 17:02:50 PDT 1070 | 1071 | -- 1072 | -- PostgreSQL database dump complete 1073 | -- 1074 | 1075 | --------------------------------------------------------------------------------