├── .babelrc.json ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .huskyrc.json ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc.json ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src ├── index.js ├── index.test.js ├── isUUID.js └── isUUID.test.js └── yarn.lock /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "10" 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": ["babel-plugin-add-module-exports"] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{js,json}] 10 | indent_size = 2 11 | indent_style = space 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true, 4 | "node": true 5 | }, 6 | "extends": ["airbnb-base", "plugin:prettier/recommended"] 7 | } 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Build directory 64 | lib/ 65 | -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,json,md,yml}": "prettier --write", 3 | "*.js": "eslint --cache --fix" 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "always", 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | - 12 7 | 8 | cache: yarn 9 | 10 | script: yarn test:coverage 11 | 12 | after_success: 13 | - bash <(curl -s https://codecov.io/bash) 14 | 15 | branches: 16 | only: 17 | - master 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to 7 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## [Unreleased] 10 | 11 | ## [0.2.0] - 2018-07-13 12 | 13 | ### Changed 14 | 15 | - Calling `.default` after `require('graphql-type-uuid')` is no longer needed 16 | (nor supported). 17 | 18 | ## 0.1.0 - 2018-07-12 19 | 20 | Initial version. 21 | 22 | [unreleased]: https://github.com/olistic/graphql-type-uuid/compare/v0.2.0...HEAD 23 | [0.2.0]: https://github.com/olistic/graphql-type-uuid/compare/v0.1.0...v0.2.0 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Matías Olivera 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-type-uuid [![npm][npm-badge]][npm] 2 | 3 | UUID scalar type for [GraphQL.js](https://github.com/graphql/graphql-js). 4 | 5 | [![Travis][build-badge]][build] [![Codecov][codecov-badge]][codecov] 6 | 7 | ## Usage 8 | 9 | This package exports a UUID scalar GraphQL.js type: 10 | 11 | ```js 12 | import GraphQLUUID from 'graphql-type-uuid'; 13 | ``` 14 | 15 | This type can also be imported as follows using CommonJS: 16 | 17 | ```js 18 | const GraphQLUUID = require('graphql-type-uuid'); 19 | ``` 20 | 21 | GraphQLUUID can represent any UUID version specified in 22 | [RFC 4122](https://tools.ietf.org/html/rfc4122). 23 | 24 | ### Programmatically-constructed schemas 25 | 26 | You can use this in a programmatically-constructed schema as with any other 27 | scalar type: 28 | 29 | ```js 30 | import { GraphQLObjectType } from 'graphql'; 31 | import GraphQLUUID from 'graphql-type-uuid'; 32 | 33 | export default new GraphQLObjectType({ 34 | name: 'MyType', 35 | fields: { 36 | myField: { type: GraphQLUUID }, 37 | }, 38 | }); 39 | ``` 40 | 41 | ### SDL with [graphql-tools](https://github.com/apollographql/graphql-tools) 42 | 43 | When using the SDL with graphql-tools, define `GraphQLUUID` as the resolver for 44 | the corresponding scalar type in your schema: 45 | 46 | ```js 47 | import { makeExecutableSchema } from 'graphql-tools'; 48 | import GraphQLUUID from 'graphql-type-uuid'; 49 | 50 | const typeDefs = ` 51 | scalar UUID 52 | 53 | type MyType { 54 | myField: UUID 55 | } 56 | `; 57 | 58 | const resolvers = { 59 | UUID: GraphQLUUID, 60 | }; 61 | 62 | export default makeExecutableSchema({ typeDefs, resolvers }); 63 | ``` 64 | 65 | ## Related 66 | 67 | If you happen to be looking for a JSON scalar GraphQL.js type, please check 68 | [graphql-type-json](https://github.com/taion/graphql-type-json), in which this 69 | project is heavily inspired. 70 | 71 | [npm-badge]: https://img.shields.io/npm/v/graphql-type-uuid.svg 72 | [npm]: https://www.npmjs.com/package/graphql-type-uuid 73 | [build-badge]: 74 | https://img.shields.io/travis/olistic/graphql-type-uuid/master.svg 75 | [build]: https://travis-ci.org/olistic/graphql-type-uuid 76 | [codecov-badge]: 77 | https://img.shields.io/codecov/c/github/olistic/graphql-type-uuid/master.svg 78 | [codecov]: https://codecov.io/gh/olistic/graphql-type-uuid 79 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: ['src/**'], 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-type-uuid", 3 | "version": "0.2.0", 4 | "description": "UUID scalar type for GraphQL.js", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "repository": "https://github.com/olistic/graphql-type-uuid.git", 8 | "keywords": [ 9 | "graphql", 10 | "uuid", 11 | "rfc-4122" 12 | ], 13 | "main": "lib/index.js", 14 | "files": [ 15 | "lib" 16 | ], 17 | "engines": { 18 | "node": ">=10" 19 | }, 20 | "scripts": { 21 | "build": "yarn clean:build && babel src --out-dir lib", 22 | "clean": "yarn clean:build & yarn clean:coverage & yarn clean:modules", 23 | "clean:build": "rimraf lib", 24 | "clean:coverage": "rimraf coverage", 25 | "clean:modules": "rimraf node_modules", 26 | "lint": "eslint --cache src", 27 | "lint:fix": "yarn lint --fix", 28 | "pretest": "yarn lint", 29 | "test": "jest", 30 | "test:coverage": "yarn test --coverage", 31 | "test:watch": "yarn test --watch" 32 | }, 33 | "peerDependencies": { 34 | "graphql": ">=0.8.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/cli": "^7.8.4", 38 | "@babel/core": "^7.9.0", 39 | "@babel/preset-env": "^7.9.5", 40 | "babel-plugin-add-module-exports": "^1.0.2", 41 | "eslint": "^6.8.0", 42 | "eslint-config-airbnb-base": "^14.1.0", 43 | "eslint-config-prettier": "^6.10.1", 44 | "eslint-plugin-import": "^2.20.2", 45 | "eslint-plugin-prettier": "^3.1.2", 46 | "graphql": "^15.0.0", 47 | "husky": "^4.2.5", 48 | "jest": "^25.3.0", 49 | "lint-staged": "^10.1.3", 50 | "prettier": "2.0.4", 51 | "rimraf": "^3.0.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType } from 'graphql'; 2 | import { Kind } from 'graphql/language'; 3 | 4 | import isUUID from './isUUID'; 5 | 6 | const GraphQLUUID = new GraphQLScalarType({ 7 | name: 'UUID', 8 | description: 9 | 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://tools.ietf.org/html/rfc4122).', 10 | serialize: (value) => { 11 | if (!isUUID(value)) { 12 | throw new TypeError(`UUID cannot represent non-UUID value: ${value}`); 13 | } 14 | 15 | return value.toLowerCase(); 16 | }, 17 | parseValue: (value) => { 18 | if (!isUUID(value)) { 19 | throw new TypeError(`UUID cannot represent non-UUID value: ${value}`); 20 | } 21 | 22 | return value.toLowerCase(); 23 | }, 24 | parseLiteral: (ast) => { 25 | if (ast.kind === Kind.STRING) { 26 | if (isUUID(ast.value)) { 27 | return ast.value; 28 | } 29 | } 30 | 31 | return undefined; 32 | }, 33 | }); 34 | 35 | export default GraphQLUUID; 36 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import { graphql, GraphQLObjectType, GraphQLSchema } from 'graphql'; 2 | 3 | import GraphQLUUID from '.'; 4 | 5 | describe('GraphQLUUID', () => { 6 | let schema; 7 | 8 | beforeEach(() => { 9 | schema = new GraphQLSchema({ 10 | query: new GraphQLObjectType({ 11 | name: 'Query', 12 | fields: { 13 | value: { 14 | type: GraphQLUUID, 15 | args: { 16 | arg: { 17 | type: GraphQLUUID, 18 | }, 19 | }, 20 | resolve: (parent, { arg }) => arg, 21 | }, 22 | }, 23 | }), 24 | }); 25 | }); 26 | 27 | describe('serialize', () => { 28 | test('supports serialization', () => { 29 | expect( 30 | GraphQLUUID.serialize('16fd2706-8baf-433b-82eb-8c7fada847da'), 31 | ).toEqual('16fd2706-8baf-433b-82eb-8c7fada847da'); 32 | }); 33 | 34 | test('converts to lower case during serialization', () => { 35 | expect( 36 | GraphQLUUID.serialize('16FD2706-8BAF-433B-82EB-8C7FADA847DA'), 37 | ).toEqual('16fd2706-8baf-433b-82eb-8c7fada847da'); 38 | }); 39 | 40 | test('rejects invalid values', () => { 41 | expect(() => { 42 | GraphQLUUID.serialize('INVALID'); 43 | }).toThrow( 44 | new TypeError('UUID cannot represent non-UUID value: INVALID'), 45 | ); 46 | }); 47 | }); 48 | 49 | describe('parseValue', () => { 50 | test('supports parsing values', async () => { 51 | const { 52 | data: { value }, 53 | } = await graphql( 54 | schema, 55 | 'query ($arg: UUID!) { value(arg: $arg) }', 56 | null, 57 | null, 58 | { arg: '16fd2706-8baf-433b-82eb-8c7fada847da' }, 59 | ); 60 | expect(value).toEqual('16fd2706-8baf-433b-82eb-8c7fada847da'); 61 | }); 62 | 63 | test('rejects invalid values', async () => { 64 | const { errors } = await graphql( 65 | schema, 66 | 'query ($arg: UUID!) { value(arg: $arg) }', 67 | null, 68 | null, 69 | { arg: 'INVALID' }, 70 | ); 71 | expect(errors.length).toBe(1); 72 | }); 73 | }); 74 | 75 | describe('parseLiteral', () => { 76 | test('supports parsing literals', async () => { 77 | const { 78 | data: { value }, 79 | } = await graphql( 80 | schema, 81 | '{ value(arg: "16fd2706-8baf-433b-82eb-8c7fada847da") }', 82 | ); 83 | expect(value).toEqual('16fd2706-8baf-433b-82eb-8c7fada847da'); 84 | }); 85 | 86 | test('rejects non-UUID string literals', async () => { 87 | const { errors } = await graphql(schema, '{ value(arg: "not a UUID") }'); 88 | expect(errors.length).toBe(1); 89 | }); 90 | 91 | test('rejects invalid literals', async () => { 92 | const { errors } = await graphql(schema, '{ value(arg: INVALID) }'); 93 | expect(errors.length).toBe(1); 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /src/isUUID.js: -------------------------------------------------------------------------------- 1 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 2 | const nilUUID = '00000000-0000-0000-0000-000000000000'; 3 | 4 | function isUUID(value) { 5 | return uuidRegex.test(value) || nilUUID === value; 6 | } 7 | 8 | export default isUUID; 9 | -------------------------------------------------------------------------------- /src/isUUID.test.js: -------------------------------------------------------------------------------- 1 | import isUUID from './isUUID'; 2 | 3 | describe('isUUID', () => { 4 | test('supports lowercase version 1 UUID', () => { 5 | expect(isUUID('a8098c1a-f86e-11da-bd1a-00112444be1e')).toBe(true); 6 | }); 7 | 8 | test('supports uppercase version 1 UUID', () => { 9 | expect(isUUID('A8098C1A-F86E-11DA-BD1A-00112444BE1E')).toBe(true); 10 | }); 11 | 12 | test('supports lowercase version 2 UUID', () => { 13 | expect(isUUID('a8098c1a-f86e-21da-bd1a-00112444be1e')).toBe(true); 14 | }); 15 | 16 | test('supports uppercase version 2 UUID', () => { 17 | expect(isUUID('A8098C1A-F86E-21DA-BD1A-00112444BE1E')).toBe(true); 18 | }); 19 | 20 | test('supports lowercase version 3 UUID', () => { 21 | expect(isUUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')).toBe(true); 22 | }); 23 | 24 | test('supports uppercase version 3 UUID', () => { 25 | expect(isUUID('6FA459EA-EE8A-3CA4-894E-DB77E160355E')).toBe(true); 26 | }); 27 | 28 | test('supports lowercase version 4 UUID', () => { 29 | expect(isUUID('16fd2706-8baf-433b-82eb-8c7fada847da')).toBe(true); 30 | }); 31 | 32 | test('supports uppercase version 4 UUID', () => { 33 | expect(isUUID('16FD2706-8BAF-433B-82EB-8C7FADA847DA')).toBe(true); 34 | }); 35 | 36 | test('supports lowercase version 5 UUID', () => { 37 | expect(isUUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')).toBe(true); 38 | }); 39 | 40 | test('supports uppercase version 5 UUID', () => { 41 | expect(isUUID('886313E1-3B8A-5372-9B90-0C9AEE199E5D')).toBe(true); 42 | }); 43 | 44 | test('supports nil UUID', () => { 45 | expect(isUUID('00000000-0000-0000-0000-000000000000')).toBe(true); 46 | }); 47 | 48 | test('rejects invalid values', () => { 49 | expect(isUUID('')).toBe(false); 50 | expect(isUUID('INVALID')).toBe(false); 51 | expect(isUUID('abcdefgh-ijkl-mnop-qrst-uvwxyzabcdef')).toBe(false); 52 | expect(isUUID('02c44232-4ac6-6eda-9ad2-735096bbaf34')).toBe(false); 53 | expect(isUUID('886313e1-3b8a-0372-9b90-0c9aee199e5d')).toBe(false); 54 | expect(isUUID('886313e13b8a53729b900c9aee199e5d')).toBe(false); 55 | }); 56 | }); 57 | --------------------------------------------------------------------------------