├── netlify.toml ├── codecov.yml ├── static ├── favicon.ico └── share-card.png ├── .lintstagedrc ├── .npmignore ├── sandbox.config.json ├── .prettierrc ├── jest.config.js ├── .fiddly.config.json ├── .travis.yml ├── .gitignore ├── example ├── resolvers.ts ├── .babelrc.js ├── index.ts ├── types.ts └── defaultQuery.ts ├── src ├── utils.ts ├── __TEST__ │ └── index.spec.ts ├── index.ts ├── numbers.ts ├── dates.ts ├── phone.ts ├── currencies.ts ├── strings.ts ├── scalars.ts ├── limits.ts └── units.ts ├── tsconfig.json ├── .babelrc.js ├── styles └── styles.css ├── LICENSE.md ├── package.json ├── .eslintrc.js └── README.md /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | Command = "yarn build:docs" 3 | Publish = "public" 4 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | parsers: 3 | javascript: 4 | enable_partials: yes -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saeris/graphql-directives/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /static/share-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saeris/graphql-directives/HEAD/static/share-card.png -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{js,jsx}": [ 3 | "prettier --write", 4 | "eslint --fix", 5 | "git add" 6 | ] 7 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.iml 4 | .*.haste_cache.* 5 | .DS_Store 6 | .idea 7 | .babelrc 8 | .eslintrc 9 | npm-debug.log 10 | lib -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": false, 4 | "view": "browser", 5 | "template": "apollo" 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false 10 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: `graphql-directives`, 3 | coverageDirectory: `./.coverage/`, 4 | collectCoverage: true, 5 | transform: { 6 | "^.+\\.(js|ts)x?$": `babel-jest` 7 | }, 8 | verbose: true 9 | } 10 | -------------------------------------------------------------------------------- /.fiddly.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "🧭 GraphQL Directives", 3 | "description": "A collection of custom GraphQL Schema Directives for use with Apollo Server", 4 | "noHeader": true, 5 | "homepage:": "http://saeris.io", 6 | "repo": "https://github.com/saeris/graphql-directives", 7 | "favicon": "static/favicon.ico", 8 | "styles": "styles/styles.css" 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | dist: trusty 7 | 8 | sudo: false 9 | 10 | cache: 11 | yarn: true 12 | directories: 13 | - node_modules 14 | 15 | before_install: 16 | - curl -o- -L https://yarnpkg.com/install.sh | bash 17 | - export PATH="$HOME/.yarn/bin:$PATH" 18 | 19 | before_script: 20 | - yarn test:coverage 21 | 22 | script: 23 | - yarn build 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Directories 6 | .serverless 7 | .webpack 8 | .netlify 9 | /.coverage 10 | /dist 11 | /public 12 | /pkg 13 | /build 14 | /typings 15 | /.awcache 16 | /.ssh 17 | /electron.js* 18 | 19 | # Files 20 | .DS_STORE 21 | npm-debug.log* 22 | yarn-error.log* 23 | .env 24 | serverless.env.yml 25 | .nyc_output 26 | coverage.lcov 27 | engine.json 28 | -------------------------------------------------------------------------------- /example/resolvers.ts: -------------------------------------------------------------------------------- 1 | export const resolvers = { 2 | Query: { 3 | getPerson: () => ({ 4 | birthDate: new Date(), 5 | age: 21, 6 | balance: 11075.25, 7 | height: 70.5, 8 | weight: 165, 9 | trajectory: 75, 10 | roomDimensions: 25.75, 11 | bagSize: 2772, 12 | coffeeConsumed: 21.125, 13 | hourlyRate: 1150, 14 | phoneNumber: `+17895551234`, 15 | powerSupply: 800, 16 | diskSpace: 1024 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLDirective, GraphQLSchema, printSchema } from "graphql" 2 | import gql from "graphql-tag" 3 | 4 | export const directiveToString = ( 5 | directive: GraphQLDirective | GraphQLDirective[] 6 | ): string => 7 | printSchema( 8 | new GraphQLSchema({ 9 | directives: Array.isArray(directive) ? directive : [directive] 10 | }) 11 | ) 12 | 13 | export const directiveToDocumentNode = ( 14 | directive: GraphQLDirective | GraphQLDirective[] 15 | ) => gql(directiveToString(directive)) 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "jsx": "preserve", 6 | "lib": ["esnext"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "removeComments": false, 13 | "preserveConstEnums": true, 14 | "moduleResolution": "node", 15 | "sourceMap": true 16 | }, 17 | "include": ["./src/**/*"], 18 | "exclude": ["node_modules", "**/*.spec.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /example/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require(`@babel/plugin-proposal-class-properties`), 4 | require(`@babel/plugin-proposal-object-rest-spread`) 5 | ], 6 | presets: [ 7 | require(`@babel/preset-typescript`), 8 | [ 9 | require(`@babel/preset-env`), 10 | { targets: { node: true }, useBuiltIns: `usage`, corejs: 3 } 11 | ] 12 | ], 13 | env: { 14 | test: { 15 | sourceMaps: `inline`, 16 | presets: [ 17 | [ 18 | require(`@babel/preset-env`), 19 | { 20 | targets: { node: true }, 21 | modules: `commonjs`, 22 | useBuiltIns: `usage`, 23 | corejs: 3 24 | } 25 | ] 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require(`@babel/plugin-proposal-class-properties`), 4 | require(`@babel/plugin-proposal-object-rest-spread`), 5 | require(`@babel/plugin-proposal-optional-chaining`) 6 | ], 7 | presets: [ 8 | require(`@babel/preset-typescript`), 9 | [ 10 | require(`@babel/preset-env`), 11 | { 12 | targets: { node: true }, 13 | modules: false, 14 | useBuiltIns: `usage`, 15 | corejs: 3 16 | } 17 | ] 18 | ], 19 | env: { 20 | test: { 21 | sourceMaps: `inline`, 22 | presets: [ 23 | [ 24 | require(`@babel/preset-env`), 25 | { 26 | targets: { node: true }, 27 | modules: `commonjs`, 28 | useBuiltIns: `usage`, 29 | corejs: 3 30 | } 31 | ] 32 | ] 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /styles/styles.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Titillium+Web:wght@700&display=swap"); 2 | 3 | body { 4 | background: #fefefe; 5 | } 6 | 7 | h1, 8 | h2, 9 | h3, 10 | h4, 11 | h5, 12 | h6 { 13 | grid-column: content; 14 | font-family: "Titillium Web", sans-serif; 15 | } 16 | 17 | #fiddly .container { 18 | display: grid; 19 | grid-template-columns: 20 | [full-start] 21 | minmax(20px, 1fr) 22 | [content-start] 23 | minmax(0, 80ch) 24 | [content-end] 25 | minmax(20px, 1fr) 26 | [full-end]; 27 | width: 100%; 28 | max-width: initial; 29 | margin: 0; 30 | } 31 | 32 | p, 33 | ul, 34 | ol, 35 | pre, 36 | hr, 37 | table, 38 | blockquote, 39 | details { 40 | grid-column: content; 41 | } 42 | 43 | figure { 44 | grid-column: full; 45 | } 46 | 47 | iframe { 48 | grid-column: full; 49 | width: 100%; 50 | height: 800px; 51 | } 52 | -------------------------------------------------------------------------------- /example/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from "apollo-server" 2 | import { typeDefs } from "./types" 3 | import { resolvers } from "./resolvers" 4 | import { defaultQuery } from "./defaultQuery" 5 | import schemaDirectives from "../pkg" 6 | 7 | const host = process.env.HOSTNAME 8 | 9 | const endpoint = host 10 | ? `https://${host.replace(`sse-sandbox-`, ``)}.sse.codesandbox.io` 11 | : `localhost:9000` 12 | 13 | const directives = Object.values(schemaDirectives).map((directive) => 14 | directive.toDocumentNode() 15 | ) 16 | 17 | const server = new ApolloServer({ 18 | typeDefs: [...typeDefs, ...directives], 19 | resolvers, 20 | schemaDirectives, 21 | playground: { 22 | tabs: [ 23 | { 24 | endpoint, 25 | query: defaultQuery 26 | } 27 | ] 28 | } 29 | }) 30 | 31 | /* eslint-disable */ 32 | server.listen({ host: `localhost`, port: 9000 }).then(({ url }) => { 33 | console.log(`🚀 Server ready at ${url}`) 34 | }) 35 | -------------------------------------------------------------------------------- /example/types.ts: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-server" 2 | 3 | const Query = gql` 4 | type Query { 5 | getPerson: Person 6 | } 7 | ` 8 | 9 | const Person = gql` 10 | type Person { 11 | birthDate: String @formatDate 12 | age: Int @convertTime(originalUnit: years) 13 | balance: Float @formatNumber 14 | height: Float @convertLength(originalUnit: inches) 15 | weight: Int @convertMass(originalUnit: poundmass) 16 | trajectory: Float @convertAngle(originalUnit: deg) 17 | roomDimensions: Float @convertSurfaceArea(originalUnit: sqft) 18 | bagSize: Float @convertVolume(originalUnit: cuin) 19 | coffeeConsumed: Float @convertLiquidVolume(originalUnit: fluidounce) 20 | hourlyRate: Int @formatCurrency 21 | phoneNumber: String @formatPhoneNumber 22 | powerSupply: Int @convertElectroMagneticForce(originalUnit: watts) 23 | diskSpace: Int @convertBinary(originalUnit: bytes) 24 | } 25 | ` 26 | 27 | export const typeDefs = [Query, Person] 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Drake Costa drake@saeris.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/__TEST__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { GraphQLDirective } from "graphql" 3 | import { default as Directives } from "../" 4 | 5 | describe(`directive exports`, () => { 6 | it(`each have directive declarations`, () => { 7 | for (const directive of Object.values(Directives)) { 8 | expect(directive.getDirectiveDeclaration()).toBeInstanceOf( 9 | GraphQLDirective 10 | ) 11 | } 12 | }) 13 | 14 | it(`each converts directive declarations to strings`, () => { 15 | for (const directive of Object.values(Directives)) { 16 | expect(typeof directive.toString()).toBe(`string`) 17 | } 18 | }) 19 | 20 | it(`each converts directive declarations to Document Nodes`, () => { 21 | for (const directive of Object.values(Directives)) { 22 | expect(directive.toDocumentNode().kind).toBe(`Document`) 23 | } 24 | }) 25 | 26 | it(`each are instances of SchemaDirectiveVisitor`, () => { 27 | for (const directive of Object.values(Directives)) { 28 | expect(directive.prototype instanceof SchemaDirectiveVisitor).toBe(true) 29 | } 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Currencies from "./currencies" 2 | import * as Dates from "./dates" 3 | // import * as Limits from "./limits" 4 | import * as Numbers from "./numbers" 5 | import * as Phone from "./phone" 6 | import * as Strings from "./strings" 7 | import * as Units from "./units" 8 | 9 | // Export all durectives as default 10 | const schemaDirectives = { 11 | ...Currencies, 12 | ...Dates, 13 | // ...Limits, 14 | ...Numbers, 15 | ...Phone, 16 | ...Strings, 17 | ...Units 18 | } 19 | 20 | export default schemaDirectives 21 | 22 | // Export namespaced groups of directives 23 | export { 24 | Currencies, 25 | Dates, 26 | // Limits, 27 | Numbers, 28 | Phone, 29 | Strings, 30 | Units 31 | } 32 | 33 | // Export each directive individually 34 | export { formatCurrency } from "./currencies" 35 | export { formatDate } from "./dates" 36 | // export { maxLength, minLength, greaterThan, lessThan } from "./limits" 37 | export { formatNumber } from "./numbers" 38 | export { formatPhoneNumber } from "./phone" 39 | export { 40 | camelCase, 41 | capitalize, 42 | deburr, 43 | kebabCase, 44 | lowerCase, 45 | lowerFirst, 46 | snakeCase, 47 | toLower, 48 | toUpper, 49 | trim, 50 | upperCase, 51 | upperFirst 52 | } from "./strings" 53 | export { 54 | convertLength, 55 | convertSurfaceArea, 56 | convertVolume, 57 | convertLiquidVolume, 58 | convertAngle, 59 | convertTime, 60 | convertMass, 61 | convertTemperature, 62 | convertForce, 63 | convertEnergy, 64 | convertPower, 65 | convertPressure, 66 | convertElectroMagneticForce, 67 | convertBinary 68 | } from "./units" 69 | -------------------------------------------------------------------------------- /src/numbers.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { 3 | defaultFieldResolver, 4 | GraphQLDirective, 5 | DirectiveLocation, 6 | GraphQLString, 7 | GraphQLField, 8 | GraphQLArgument 9 | } from "graphql" 10 | import numeral from "numeral" 11 | import { directiveToString, directiveToDocumentNode } from "./utils" 12 | 13 | export class formatNumber extends SchemaDirectiveVisitor { 14 | static getDirectiveDeclaration() { 15 | return new GraphQLDirective({ 16 | name: `formatNumber`, 17 | locations: [DirectiveLocation.FIELD_DEFINITION], 18 | args: { 19 | defaultFormat: { 20 | type: GraphQLString, 21 | defaultValue: `0,0.0000` 22 | } 23 | } 24 | }) 25 | } 26 | 27 | static toString() { 28 | return directiveToString(this.getDirectiveDeclaration()) 29 | } 30 | 31 | static toDocumentNode() { 32 | return directiveToDocumentNode(this.getDirectiveDeclaration()) 33 | } 34 | 35 | visitFieldDefinition(field: GraphQLField) { 36 | const { resolve = defaultFieldResolver } = field 37 | const { defaultFormat } = this.args 38 | 39 | field.args.push({ name: `format`, type: GraphQLString } as GraphQLArgument) 40 | 41 | field.resolve = async function ( 42 | source, 43 | { format, ...args }, 44 | context, 45 | info 46 | ) { 47 | const result = await resolve.call(this, source, args, context, info) 48 | const transform = (input: any) => 49 | numeral(input).format(format || defaultFormat) 50 | return Array.isArray(result) ? result.map(transform) : transform(result) 51 | } 52 | 53 | field.type = GraphQLString 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/dates.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { 3 | defaultFieldResolver, 4 | GraphQLDirective, 5 | DirectiveLocation, 6 | GraphQLString, 7 | GraphQLField, 8 | GraphQLArgument 9 | } from "graphql" 10 | import { default as formatter } from "date-fns/format" 11 | import { directiveToString, directiveToDocumentNode } from "./utils" 12 | 13 | export class formatDate extends SchemaDirectiveVisitor { 14 | static getDirectiveDeclaration() { 15 | return new GraphQLDirective({ 16 | name: `formatDate`, 17 | locations: [DirectiveLocation.FIELD_DEFINITION], 18 | args: { 19 | defaultFormat: { 20 | type: GraphQLString, 21 | defaultValue: `mmmm d, yyyy` 22 | } 23 | } 24 | }) 25 | } 26 | 27 | static toString() { 28 | return directiveToString(this.getDirectiveDeclaration()) 29 | } 30 | 31 | static toDocumentNode() { 32 | return directiveToDocumentNode(this.getDirectiveDeclaration()) 33 | } 34 | 35 | visitFieldDefinition(field: GraphQLField) { 36 | const { resolve = defaultFieldResolver } = field 37 | const { defaultFormat } = this.args 38 | 39 | field.args.push({ name: `format`, type: GraphQLString } as GraphQLArgument) 40 | 41 | field.resolve = async function ( 42 | source, 43 | { format, ...args }, 44 | context, 45 | info 46 | ) { 47 | const result = await resolve.call(this, source, args, context, info) 48 | const transform = (input: number | Date) => 49 | formatter(input, format || defaultFormat) 50 | return Array.isArray(result) ? result.map(transform) : transform(result) 51 | } 52 | 53 | field.type = GraphQLString 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/defaultQuery.ts: -------------------------------------------------------------------------------- 1 | export const defaultQuery = ` 2 | query getPerson { 3 | getPerson { 4 | # birthDate: String @formatDate 5 | defaultBirthDate: birthDate 6 | formattedBirthDate:birthDate(format: "H:mm:ssa - mmmm d, yyyy") 7 | 8 | # age: Int @convertTime(originalUnit: years) 9 | defaultAge: age 10 | convertedAge: age(convertTo: days) 11 | 12 | # balance: Float @formatNumber 13 | defaultBalance: balance 14 | formattedBalance:balance(format: "0.0a") 15 | 16 | # height: Float @convertLength(originalUnit: inches) 17 | defaultHeight: height 18 | convertedHeight: height(convertTo: feet) 19 | 20 | # weight: Float @convertMass(originalUnit: poundmass) 21 | defaultWeight: weight 22 | convertedWeight: weight(convertTo: gram) 23 | 24 | # trajectory: Float @convertAngle(originalUnit: deg) 25 | defaultTrajectory: trajectory 26 | convertedTrajectory: trajectory(convertTo: rad) 27 | 28 | # roomDimensions: Float @convertSurfaceArea(originalUnit: sqft) 29 | defaultRoomDimensions: roomDimensions 30 | convertedRoomDimensions: roomDimensions(convertTo: m2) 31 | 32 | # bagSize: Float @convertVolume(originalUnit: cuin) 33 | defaultBagSize: bagSize 34 | convertedBagSize: bagSize(convertTo: litre) 35 | 36 | # coffeeConsumed: Float @convertLiquidVolume(originalUnit: fluidounce) 37 | defaultCoffeeConsumed: coffeeConsumed 38 | convertedCoffeeConsumed: coffeeConsumed(convertTo: cup) 39 | 40 | # hourlyRate: Int @formatCurrency 41 | defaultHourlyRate: hourlyRate 42 | formattedHourlyRate: hourlyRate(format: "USD0,0.0" currency: "EUR") 43 | 44 | # phoneNumber: String @formatPhoneNumber 45 | defaultPhoneNumer: phoneNumber 46 | formattedPhoneNumber: phoneNumber(format: National) 47 | 48 | # diskSpace: Int @converBinary 49 | defaultDiskSpace: diskSpace 50 | formattedDiskSpace: diskSpace(convertTo: bits) 51 | } 52 | } 53 | ` 54 | -------------------------------------------------------------------------------- /src/phone.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { 3 | defaultFieldResolver, 4 | GraphQLDirective, 5 | DirectiveLocation, 6 | GraphQLEnumType, 7 | GraphQLString, 8 | GraphQLField, 9 | GraphQLArgument 10 | } from "graphql" 11 | import { parsePhoneNumber, NumberFormat } from "libphonenumber-js" 12 | import { directiveToString, directiveToDocumentNode } from "./utils" 13 | 14 | const PhoneFormats = new GraphQLEnumType({ 15 | name: `PhoneFormats`, 16 | values: { 17 | National: {}, 18 | International: {}, 19 | E164: {}, 20 | RFC3966: {} 21 | } 22 | }) 23 | 24 | export class formatPhoneNumber extends SchemaDirectiveVisitor { 25 | static getDirectiveDeclaration() { 26 | return new GraphQLDirective({ 27 | name: `formatPhoneNumber`, 28 | locations: [DirectiveLocation.FIELD_DEFINITION], 29 | args: { 30 | defaultFormat: { 31 | type: PhoneFormats, 32 | defaultValue: `International` 33 | } 34 | } 35 | }) 36 | } 37 | 38 | static toString() { 39 | return directiveToString(this.getDirectiveDeclaration()) 40 | } 41 | 42 | static toDocumentNode() { 43 | return directiveToDocumentNode(this.getDirectiveDeclaration()) 44 | } 45 | 46 | visitFieldDefinition(field: GraphQLField) { 47 | const { resolve = defaultFieldResolver } = field 48 | const { defaultFormat } = this.args 49 | const getFormat = (raw: string) => 50 | raw === `E164` ? `E.164` : raw.toUpperCase() 51 | 52 | field.args.push({ 53 | name: `format`, 54 | type: PhoneFormats 55 | } as GraphQLArgument) 56 | 57 | field.resolve = async function ( 58 | source, 59 | { format, ...args }, 60 | context, 61 | info 62 | ) { 63 | const result = await resolve.call(this, source, args, context, info) 64 | const transform = (input: string) => 65 | parsePhoneNumber(input).format( 66 | getFormat(format || defaultFormat) as NumberFormat 67 | ) 68 | return Array.isArray(result) ? result.map(transform) : transform(result) 69 | } 70 | 71 | field.type = GraphQLString 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/currencies.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { 3 | defaultFieldResolver, 4 | GraphQLDirective, 5 | DirectiveLocation, 6 | GraphQLString, 7 | GraphQLEnumType, 8 | GraphQLField, 9 | GraphQLArgument 10 | } from "graphql" 11 | import dinero from "dinero.js" 12 | import { directiveToString, directiveToDocumentNode } from "./utils" 13 | 14 | const RoundingMode = new GraphQLEnumType({ 15 | name: `RoundingMode`, 16 | values: { 17 | HALF_ODD: {}, 18 | HALF_EVEN: {}, 19 | HALF_UP: {}, 20 | HALF_DOWN: {}, 21 | HALF_TOWARD_ZERO: {}, 22 | HALF_AWAY_FROM_ZERO: {} 23 | } 24 | }) 25 | 26 | export class formatCurrency extends SchemaDirectiveVisitor { 27 | static getDirectiveDeclaration() { 28 | return new GraphQLDirective({ 29 | name: `formatCurrency`, 30 | locations: [DirectiveLocation.FIELD_DEFINITION], 31 | args: { 32 | defaultFormat: { 33 | type: GraphQLString, 34 | defaultValue: `$0,0.00` 35 | }, 36 | defaultRoundingMode: { 37 | type: RoundingMode, 38 | defaultValue: `HALF_AWAY_FROM_ZERO` 39 | } 40 | } 41 | }) 42 | } 43 | 44 | static toString() { 45 | return directiveToString(this.getDirectiveDeclaration()) 46 | } 47 | 48 | static toDocumentNode() { 49 | return directiveToDocumentNode(this.getDirectiveDeclaration()) 50 | } 51 | 52 | visitFieldDefinition(field: GraphQLField) { 53 | const { resolve = defaultFieldResolver } = field 54 | const { defaultFormat, defaultRoundingMode } = this.args 55 | 56 | field.args.push({ name: `format`, type: GraphQLString } as GraphQLArgument) 57 | field.args.push({ 58 | name: `currency`, 59 | type: GraphQLString 60 | } as GraphQLArgument) 61 | field.args.push({ 62 | name: `roundingMode`, 63 | type: RoundingMode 64 | } as GraphQLArgument) 65 | 66 | field.resolve = async function ( 67 | source, 68 | { format, currency, roundingMode, ...args }, 69 | context, 70 | info 71 | ) { 72 | const result = await resolve.call(this, source, args, context, info) 73 | const transform = (input: number) => { 74 | const config: dinero.Options = { amount: input } 75 | if (currency) config.currency = currency 76 | return dinero(config).toFormat( 77 | format || defaultFormat, 78 | roundingMode || defaultRoundingMode 79 | ) 80 | } 81 | return Array.isArray(result) ? result.map(transform) : transform(result) 82 | } 83 | 84 | field.type = GraphQLString 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/strings.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { 3 | defaultFieldResolver, 4 | GraphQLDirective, 5 | DirectiveLocation, 6 | GraphQLString, 7 | GraphQLField 8 | } from "graphql" 9 | import _camelCase from "lodash/camelCase" 10 | import _capitalize from "lodash/capitalize" 11 | import _deburr from "lodash/deburr" 12 | import _kebabCase from "lodash/kebabCase" 13 | import _lowerCase from "lodash/lowerCase" 14 | import _lowerFirst from "lodash/lowerFirst" 15 | import _snakeCase from "lodash/snakeCase" 16 | import _toLower from "lodash/toLower" 17 | import _toUpper from "lodash/toUpper" 18 | import _trim from "lodash/trim" 19 | import _upperCase from "lodash/upperCase" 20 | import _upperFirst from "lodash/upperFirst" 21 | import { directiveToString, directiveToDocumentNode } from "./utils" 22 | 23 | const methods = { 24 | camelCase: _camelCase, 25 | capitalize: _capitalize, 26 | deburr: _deburr, 27 | kebabCase: _kebabCase, 28 | lowerCase: _lowerCase, 29 | lowerFirst: _lowerFirst, 30 | snakeCase: _snakeCase, 31 | toLower: _toLower, 32 | toUpper: _toUpper, 33 | trim: _trim, 34 | upperCase: _upperCase, 35 | upperFirst: _upperFirst 36 | } 37 | 38 | class CreateStringDirective extends SchemaDirectiveVisitor { 39 | static getDirectiveDeclaration() { 40 | return new GraphQLDirective({ 41 | name: this.name, 42 | locations: [DirectiveLocation.FIELD_DEFINITION] 43 | }) 44 | } 45 | 46 | static toString() { 47 | return directiveToString(this.getDirectiveDeclaration()) 48 | } 49 | 50 | static toDocumentNode() { 51 | return directiveToDocumentNode(this.getDirectiveDeclaration()) 52 | } 53 | 54 | visitFieldDefinition(field: GraphQLField) { 55 | const { resolve = defaultFieldResolver } = field 56 | field.resolve = async function (...args) { 57 | const result = await resolve.apply(this, args) 58 | const transform = (input: string) => 59 | typeof input === `string` 60 | ? methods[this.name as keyof typeof methods](input) 61 | : input 62 | return Array.isArray(result) ? result.map(transform) : transform(result) 63 | } 64 | 65 | field.type = GraphQLString 66 | } 67 | } 68 | 69 | export class camelCase extends CreateStringDirective {} 70 | export class capitalize extends CreateStringDirective {} 71 | export class deburr extends CreateStringDirective {} 72 | export class kebabCase extends CreateStringDirective {} 73 | export class lowerCase extends CreateStringDirective {} 74 | export class lowerFirst extends CreateStringDirective {} 75 | export class snakeCase extends CreateStringDirective {} 76 | export class toLower extends CreateStringDirective {} 77 | export class toUpper extends CreateStringDirective {} 78 | export class trim extends CreateStringDirective {} 79 | export class upperCase extends CreateStringDirective {} 80 | export class upperFirst extends CreateStringDirective {} 81 | -------------------------------------------------------------------------------- /src/scalars.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType, GraphQLScalarTypeConfig } from "graphql" 2 | 3 | export class MaxLength extends GraphQLScalarType { 4 | constructor( 5 | type: Readonly>, 6 | limit: number 7 | ) { 8 | super({ 9 | name: `MaxLength${limit}`, 10 | 11 | serialize(value) { 12 | const serialized = type?.serialize?.(value) 13 | if (typeof value === `string` && value.length <= limit) { 14 | return serialized 15 | } 16 | if (typeof value === `number` && !isNaN(value) && value <= limit) { 17 | return serialized 18 | } 19 | throw new TypeError(`Value exceeds limit: ${limit}`) 20 | }, 21 | 22 | parseValue(value) { 23 | return type.parseValue?.(value) 24 | }, 25 | 26 | parseLiteral(ast) { 27 | return type.parseLiteral?.(ast, {}) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | export class MinLength extends GraphQLScalarType { 34 | constructor( 35 | type: Readonly>, 36 | limit: number 37 | ) { 38 | super({ 39 | name: `MinLength${limit}`, 40 | 41 | serialize(value) { 42 | const serialized = type?.serialize?.(value) 43 | if (typeof value === `string` && value.length >= limit) { 44 | return serialized 45 | } 46 | if (typeof value === `number` && !isNaN(value) && value >= limit) { 47 | return serialized 48 | } 49 | throw new TypeError(`Value beneath limit: ${limit}`) 50 | }, 51 | 52 | parseValue(value) { 53 | return type.parseValue?.(value) 54 | }, 55 | 56 | parseLiteral(ast) { 57 | return type.parseLiteral?.(ast, {}) 58 | } 59 | }) 60 | } 61 | } 62 | 63 | export class GreaterThan extends GraphQLScalarType { 64 | constructor( 65 | type: Readonly>, 66 | limit: number 67 | ) { 68 | super({ 69 | name: `GreaterThan${limit}`, 70 | 71 | serialize(value) { 72 | const serialized = type?.serialize?.(value) 73 | if (typeof value === `number` && !isNaN(value) && value > limit) { 74 | return serialized 75 | } 76 | throw new TypeError(`Value beneath limit: ${limit}`) 77 | }, 78 | 79 | parseValue(value) { 80 | return type.parseValue?.(value) 81 | }, 82 | 83 | parseLiteral(ast) { 84 | return type.parseLiteral?.(ast, {}) 85 | } 86 | }) 87 | } 88 | } 89 | 90 | export class LessThan extends GraphQLScalarType { 91 | constructor( 92 | type: Readonly>, 93 | limit: number 94 | ) { 95 | super({ 96 | name: `LessThan${limit}`, 97 | 98 | serialize(value) { 99 | const serialized = type?.serialize?.(value) 100 | if (typeof value === `number` && !isNaN(value) && value < limit) { 101 | return serialized 102 | } 103 | throw new TypeError(`Value exceeds limit: ${limit}`) 104 | }, 105 | 106 | parseValue(value) { 107 | return type.parseValue?.(value) 108 | }, 109 | 110 | parseLiteral(ast) { 111 | return type.parseLiteral?.(ast, {}) 112 | } 113 | }) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/limits.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { 3 | GraphQLNonNull, 4 | GraphQLScalarType, 5 | GraphQLInputField, 6 | GraphQLField 7 | } from "graphql" 8 | import { MaxLength, MinLength, GreaterThan, LessThan } from "./scalars" 9 | 10 | export class maxLength extends SchemaDirectiveVisitor { 11 | visitInputFieldDefinition(field: GraphQLInputField) { 12 | this.wrapType(field) 13 | } 14 | 15 | visitFieldDefinition(field: GraphQLField) { 16 | this.wrapType(field) 17 | } 18 | 19 | wrapType(field: GraphQLField | GraphQLInputField) { 20 | if ( 21 | field.type instanceof GraphQLNonNull && 22 | field.type.ofType instanceof GraphQLScalarType 23 | ) { 24 | field.type = new GraphQLNonNull( 25 | new MaxLength(field.type.ofType, this.args.limit) 26 | ) 27 | } else if (field.type instanceof GraphQLScalarType) { 28 | field.type = new MaxLength(field.type, this.args.limit) 29 | } else { 30 | throw new Error(`Not a scalar type: ${field.type}`) 31 | } 32 | } 33 | } 34 | 35 | export class minLength extends SchemaDirectiveVisitor { 36 | visitInputFieldDefinition(field: GraphQLInputField) { 37 | this.wrapType(field) 38 | } 39 | 40 | visitFieldDefinition(field: GraphQLField) { 41 | this.wrapType(field) 42 | } 43 | 44 | wrapType(field: GraphQLField | GraphQLInputField) { 45 | if ( 46 | field.type instanceof GraphQLNonNull && 47 | field.type.ofType instanceof GraphQLScalarType 48 | ) { 49 | field.type = new GraphQLNonNull( 50 | new MinLength(field.type.ofType, this.args.limit) 51 | ) 52 | } else if (field.type instanceof GraphQLScalarType) { 53 | field.type = new MinLength(field.type, this.args.limit) 54 | } else { 55 | throw new Error(`Not a scalar type: ${field.type}`) 56 | } 57 | } 58 | } 59 | 60 | export class greaterThan extends SchemaDirectiveVisitor { 61 | visitInputFieldDefinition(field: GraphQLInputField) { 62 | this.wrapType(field) 63 | } 64 | 65 | visitFieldDefinition(field: GraphQLField) { 66 | this.wrapType(field) 67 | } 68 | 69 | wrapType(field: GraphQLField | GraphQLInputField) { 70 | if ( 71 | field.type instanceof GraphQLNonNull && 72 | field.type.ofType instanceof GraphQLScalarType 73 | ) { 74 | field.type = new GraphQLNonNull( 75 | new GreaterThan(field.type.ofType, this.args.value) 76 | ) 77 | } else if (field.type instanceof GraphQLScalarType) { 78 | field.type = new GreaterThan(field.type, this.args.value) 79 | } else { 80 | throw new Error(`Not a scalar type: ${field.type}`) 81 | } 82 | } 83 | } 84 | 85 | export class lessThan extends SchemaDirectiveVisitor { 86 | visitInputFieldDefinition(field: GraphQLInputField) { 87 | this.wrapType(field) 88 | } 89 | 90 | visitFieldDefinition(field: GraphQLField) { 91 | this.wrapType(field) 92 | } 93 | 94 | wrapType(field: GraphQLField | GraphQLInputField) { 95 | if ( 96 | field.type instanceof GraphQLNonNull && 97 | field.type.ofType instanceof GraphQLScalarType 98 | ) { 99 | field.type = new GraphQLNonNull( 100 | new LessThan(field.type.ofType, this.args.value) 101 | ) 102 | } else if (field.type instanceof GraphQLScalarType) { 103 | field.type = new LessThan(field.type, this.args.value) 104 | } else { 105 | throw new Error(`Not a scalar type: ${field.type}`) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@saeris/graphql-directives", 3 | "version": "0.5.0", 4 | "description": "A collection of custom GraphQL Schema Directives for use with Apollo Server", 5 | "keywords": [ 6 | "apollo", 7 | "graphql", 8 | "directives", 9 | "schema directives" 10 | ], 11 | "author": "Drake Costa (https://github.com/Saeris/)", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/saeris/graphql-directives.git" 15 | }, 16 | "license": "MIT", 17 | "publishConfig": { 18 | "access": "public" 19 | }, 20 | "main": "src/index.ts", 21 | "scripts": { 22 | "dev": "nodemon --watch src --watch example --exec run-s build serve", 23 | "build": "pika build", 24 | "build:docs": "fiddly", 25 | "serve": "babel-node --config-file ./example/.babelrc.js -x \".ts,.tsx\" example/index.ts", 26 | "typecheck": "tsc --noEmit", 27 | "lint": "eslint ./src/**/*.{js,ts}", 28 | "test": "yarn lint && yarn typecheck && jest -c ./jest.config.js", 29 | "test:watch": "jest -c ./jest.config.js --watch", 30 | "test:coverage": "jest -c ./jest.config.js && codecov", 31 | "precommit": "lint-staged", 32 | "release": "pika publish", 33 | "version": "npm run build" 34 | }, 35 | "dependencies": { 36 | "apollo-server": "^2.18.2", 37 | "date-fns": "2.16.1", 38 | "dinero.js": "^1.8.1", 39 | "libphonenumber-js": "^1.8.4", 40 | "lodash": "^4.17.20", 41 | "mathjs": "^7.5.1", 42 | "numeral": "2.0.6" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.11.6", 46 | "@babel/node": "^7.10.5", 47 | "@babel/plugin-proposal-class-properties": "^7.10.4", 48 | "@babel/plugin-proposal-object-rest-spread": "^7.11.0", 49 | "@babel/plugin-proposal-optional-chaining": "^7.11.0", 50 | "@babel/preset-env": "^7.11.5", 51 | "@babel/preset-typescript": "^7.10.4", 52 | "@pika/pack": "^0.5.0", 53 | "@pika/plugin-build-node": "^0.9.2", 54 | "@pika/plugin-build-types": "^0.9.2", 55 | "@pika/plugin-build-web": "^0.9.2", 56 | "@pika/plugin-standard-pkg": "^0.9.2", 57 | "@types/dinero.js": "^1.6.5", 58 | "@types/graphql": "14.5.0", 59 | "@types/jest": "^26.0.14", 60 | "@types/lodash-es": "^4.17.3", 61 | "@types/mathjs": "^6.0.5", 62 | "@types/node": "^14.11.8", 63 | "@types/numeral": "^0.0.28", 64 | "@typescript-eslint/eslint-plugin": "^4.4.1", 65 | "@typescript-eslint/parser": "^4.4.1", 66 | "babel-core": "^7.0.0-bridge.0", 67 | "babel-eslint": "^10.1.0", 68 | "babel-jest": "^26.5.2", 69 | "codecov": "^3.8.0", 70 | "core-js": "3.6.5", 71 | "eslint": "^7.11.0", 72 | "eslint-plugin-import": "^2.22.1", 73 | "eslint-plugin-jest": "^24.1.0", 74 | "eslint-plugin-promise": "^4.2.1", 75 | "esm": "3.2.25", 76 | "fiddly": "^0.9.1", 77 | "graphql": "15.3.0", 78 | "graphql-tag": "^2.11.0", 79 | "jest": "^26.5.3", 80 | "lint-staged": "^10.4.0", 81 | "nodemon": "2.0.5", 82 | "npm-run-all": "^4.1.5", 83 | "prettier": "^2.1.2", 84 | "typescript": "^4.0.3" 85 | }, 86 | "peerDependencies": { 87 | "graphql": "^15.3.0", 88 | "graphql-tag": "^2.11.0" 89 | }, 90 | "resolutions": { 91 | "graphql-tools": "4.0.7" 92 | }, 93 | "@pika/pack": { 94 | "pipeline": [ 95 | [ 96 | "@pika/plugin-standard-pkg", 97 | { 98 | "exclude": [ 99 | "__TEST__/*" 100 | ] 101 | } 102 | ], 103 | [ 104 | "@pika/plugin-build-node" 105 | ], 106 | [ 107 | "@pika/plugin-build-web" 108 | ], 109 | [ 110 | "@pika/plugin-build-types" 111 | ] 112 | ] 113 | }, 114 | "gitHooks": { 115 | "pre-commit": "lint-staged" 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/units.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from "apollo-server" 2 | import { 3 | defaultFieldResolver, 4 | GraphQLDirective, 5 | DirectiveLocation, 6 | GraphQLString, 7 | GraphQLBoolean, 8 | GraphQLField, 9 | GraphQLArgument, 10 | GraphQLEnumType 11 | } from "graphql" 12 | import { unit } from "mathjs" 13 | import { directiveToString, directiveToDocumentNode } from "./utils" 14 | 15 | class createUnitDirective extends SchemaDirectiveVisitor { 16 | unit: string = `` 17 | static _unit: string = `` 18 | baseEnum!: GraphQLEnumType 19 | static _baseEnum: GraphQLEnumType 20 | 21 | static getDirectiveDeclaration() { 22 | return new GraphQLDirective({ 23 | name: `convert${this._unit}`, 24 | locations: [DirectiveLocation.FIELD_DEFINITION], 25 | args: { 26 | originalUnit: { 27 | type: this._baseEnum 28 | }, 29 | defaultRaw: { 30 | type: GraphQLBoolean, 31 | defaultValue: false 32 | } 33 | } 34 | }) 35 | } 36 | 37 | static toString() { 38 | return directiveToString(this.getDirectiveDeclaration()) 39 | } 40 | 41 | static toDocumentNode() { 42 | return directiveToDocumentNode(this.getDirectiveDeclaration()) 43 | } 44 | 45 | visitFieldDefinition(field: GraphQLField) { 46 | const { resolve = defaultFieldResolver } = field 47 | const { originalUnit, defaultRaw } = this.args 48 | 49 | field.args.push({ 50 | name: `convertTo`, 51 | type: this.baseEnum 52 | } as GraphQLArgument) 53 | field.args.push({ name: `raw`, type: GraphQLBoolean } as GraphQLArgument) 54 | 55 | field.resolve = async function ( 56 | source, 57 | { convertTo, raw, ...args }, 58 | context, 59 | info 60 | ) { 61 | const result = await resolve.call(this, source, args, context, info) 62 | const transform = (input: number) => { 63 | const value = unit(input, originalUnit).to(convertTo || originalUnit) 64 | return raw || defaultRaw 65 | ? value.toNumeric(convertTo || originalUnit) 66 | : value.toString() 67 | } 68 | return Array.isArray(result) ? result.map(transform) : transform(result) 69 | } 70 | 71 | field.type = GraphQLString 72 | } 73 | } 74 | 75 | const decimalPrefixes = { 76 | deca: `da`, 77 | hecto: `h`, 78 | kilo: `k`, 79 | mega: `M`, 80 | giga: `G`, 81 | tera: `T`, 82 | peta: `P`, 83 | exa: `E`, 84 | zetta: `Z`, 85 | yotta: `Y`, 86 | "": ``, 87 | deci: `d`, 88 | centi: `c`, 89 | milli: `m`, 90 | miro: `u`, 91 | nano: `n`, 92 | pico: `p`, 93 | femto: `f`, 94 | atto: `a`, 95 | zpeto: `z`, 96 | yocto: `y` 97 | } 98 | 99 | const binaryPrefixes = { 100 | kibi: `Ki`, 101 | mebi: `Mi`, 102 | gibi: `Gi`, 103 | tebi: `Ti`, 104 | pebi: `Pi`, 105 | exi: `Ei`, 106 | zebi: `Zi`, 107 | yobi: `Yi`, 108 | "": ``, 109 | kilo: `k`, 110 | mega: `M`, 111 | giga: `G`, 112 | tera: `T`, 113 | peta: `P`, 114 | exa: `E`, 115 | zetta: `Z`, 116 | yotta: `Y` 117 | } 118 | 119 | const applyPrefixes = (arr: string[], unitName: string) => 120 | arr.map((prefix) => `${prefix}${unitName}`) 121 | 122 | const generatePrefixes = ( 123 | normalPrefixes: string[], 124 | abbreviationPrefixes: string[] 125 | ) => (singular: string, plural: string, abbreviation: string) => 126 | [ 127 | ...applyPrefixes(normalPrefixes, singular), 128 | ...applyPrefixes(normalPrefixes, plural), 129 | ...applyPrefixes(abbreviationPrefixes, abbreviation) 130 | ].join(`\n`) 131 | 132 | const generateDecimalPrefixes = generatePrefixes( 133 | Object.keys(decimalPrefixes), 134 | Object.values(decimalPrefixes) 135 | ) 136 | 137 | const generateBinaryPrefixes = generatePrefixes( 138 | Object.keys(binaryPrefixes), 139 | Object.values(binaryPrefixes) 140 | ) 141 | 142 | const createEnum = (name: string, values: string[]) => 143 | new GraphQLEnumType({ 144 | name, 145 | values: values.reduce<{ [key: string]: {} }>((hash, key) => { 146 | hash[key] = {} 147 | return hash 148 | }, {}) 149 | }) 150 | 151 | const LengthTypesEnum = createEnum(`LengthTypesEnum`, [ 152 | ...generateDecimalPrefixes(`meter`, `meters`, `m`), 153 | `inch`, 154 | `inches`, 155 | `in`, 156 | `foot`, 157 | `feet`, 158 | `ft`, 159 | `yard`, 160 | `yards`, 161 | `yd`, 162 | `mile`, 163 | `miles`, 164 | `mi`, 165 | `link`, 166 | `links`, 167 | `li`, 168 | `rod`, 169 | `rods`, 170 | `rd`, 171 | `chain`, 172 | `chains`, 173 | `ch`, 174 | `angstrom`, 175 | `angstroms`, 176 | `mil`, 177 | `mils` 178 | ]) 179 | 180 | export class convertLength extends createUnitDirective { 181 | unit = `Length` 182 | static _unit = `Length` 183 | baseEnum = LengthTypesEnum 184 | static _baseEnum = LengthTypesEnum 185 | } 186 | 187 | const SurfaceAreaTypesEnum = createEnum(`SurfaceAreaTypesEnum`, [ 188 | `m2`, 189 | `sqin`, 190 | `sqft`, 191 | `sqyd`, 192 | `sqmi`, 193 | `sqrd`, 194 | `sqch`, 195 | `sqmil`, 196 | `acre`, 197 | `acres`, 198 | `hectare`, 199 | `hectares` 200 | ]) 201 | 202 | export class convertSurfaceArea extends createUnitDirective { 203 | unit = `SurfaceArea` 204 | static _unit = `SurfaceArea` 205 | baseEnum = SurfaceAreaTypesEnum 206 | static _baseEnum = SurfaceAreaTypesEnum 207 | } 208 | 209 | const VolumeTypesEnum = createEnum(`VolumeTypesEnum`, [ 210 | `m3`, 211 | ...generateDecimalPrefixes(`litre`, `litres`, `L`), 212 | `cc`, 213 | `cuin`, 214 | `cuft`, 215 | `cuyd`, 216 | `teaspoon`, 217 | `teaspoons`, 218 | `tablespoon`, 219 | `tablespoons` 220 | ]) 221 | 222 | export class convertVolume extends createUnitDirective { 223 | unit = `Volume` 224 | static _unit = `Volume` 225 | baseEnum = VolumeTypesEnum 226 | static _baseEnum = VolumeTypesEnum 227 | } 228 | 229 | const LiquidVolumeTypesEnum = createEnum(`LiquidVolumeTypesEnum`, [ 230 | `minim`, 231 | `min`, 232 | `fluiddram`, 233 | `fldr`, 234 | `fluidounce`, 235 | `floz`, 236 | `gill`, 237 | `gi`, 238 | `cup`, 239 | `cups`, 240 | `cp`, 241 | `pint`, 242 | `pints`, 243 | `pt`, 244 | `quart`, 245 | `quarts`, 246 | `qt`, 247 | `gallon`, 248 | `gallons`, 249 | `gal`, 250 | `beerbarrel`, 251 | `beerbarrels`, 252 | `bbl`, 253 | `oilbarrel`, 254 | `oilbarrels`, 255 | `obl`, 256 | `hogshead`, 257 | `drop`, 258 | `gtt` 259 | ]) 260 | 261 | export class convertLiquidVolume extends createUnitDirective { 262 | unit = `LiquidVolume` 263 | static _unit = `LiquidVolume` 264 | baseEnum = LiquidVolumeTypesEnum 265 | static _baseEnum = LiquidVolumeTypesEnum 266 | } 267 | 268 | const AngleTypesEnum = createEnum(`AngleTypesEnum`, [ 269 | ...generateDecimalPrefixes(`radian`, `radians`, `rad`), 270 | ...generateDecimalPrefixes(`degree`, `degrees`, `deg`), 271 | ...generateDecimalPrefixes(`gradian`, `gradians`, `grad`), 272 | `cycle`, 273 | `arcsec`, 274 | `arcsecond`, 275 | `arcseconds`, 276 | `arcmin`, 277 | `arcminute`, 278 | `arcminutes` 279 | ]) 280 | 281 | export class convertAngle extends createUnitDirective { 282 | unit = `Angle` 283 | static _unit = `Angle` 284 | baseEnum = AngleTypesEnum 285 | static _baseEnum = AngleTypesEnum 286 | } 287 | 288 | const TimeTypesEnum = createEnum(`TimeTypesEnum`, [ 289 | `s`, 290 | `sec`, 291 | `secs`, 292 | `second`, 293 | `seconds`, 294 | `mins`, 295 | `minute`, 296 | `minutes`, 297 | `h`, 298 | `hr`, 299 | `hrs`, 300 | `hour`, 301 | `hours`, 302 | `day`, 303 | `days`, 304 | `week`, 305 | `weeks`, 306 | `month`, 307 | `months`, 308 | `year`, 309 | `years`, 310 | `decade`, 311 | `decades`, 312 | `century`, 313 | `centuries`, 314 | `millennium`, 315 | `millennia` 316 | ]) 317 | 318 | export class convertTime extends createUnitDirective { 319 | unit = `Time` 320 | static _unit = `Time` 321 | baseEnum = TimeTypesEnum 322 | static _baseEnum = TimeTypesEnum 323 | } 324 | 325 | const MassTypesEnum = createEnum(`MassTypesEnum`, [ 326 | ...generateDecimalPrefixes(`gram`, `grams`, `g`), 327 | ...generateDecimalPrefixes(`tonne`, `tonnes`, `t`), 328 | `ton`, 329 | `grain`, 330 | `gr`, 331 | `dram`, 332 | `dr`, 333 | `ounce`, 334 | `oz`, 335 | `poundmass`, 336 | `lbm`, 337 | `lb`, 338 | `lbs`, 339 | `hundredweight`, 340 | `cwt`, 341 | `stick`, 342 | `stone` 343 | ]) 344 | 345 | export class convertMass extends createUnitDirective { 346 | unit = `Mass` 347 | static _unit = `Mass` 348 | baseEnum = MassTypesEnum 349 | static _baseEnum = MassTypesEnum 350 | } 351 | 352 | const TemperatureTypesEnum = createEnum(`TemperatureTypesEnum`, [ 353 | `kelvin`, 354 | `K`, 355 | `celsius`, 356 | `degC`, 357 | `fahrenheit`, 358 | `degF`, 359 | `rankine`, 360 | `degR` 361 | ]) 362 | 363 | export class convertTemperature extends createUnitDirective { 364 | unit = `Temperature` 365 | static _unit = `Temperature` 366 | baseEnum = TemperatureTypesEnum 367 | static _baseEnum = TemperatureTypesEnum 368 | } 369 | 370 | const ForceTypesEnum = createEnum(`ForceTypesEnum`, [ 371 | ...generateDecimalPrefixes(`newton`, `newtons`, `N`), 372 | ...generateDecimalPrefixes(`dyne`, `dynes`, `dyn`), 373 | `poundforce`, 374 | `lbf`, 375 | `kip` 376 | ]) 377 | 378 | export class convertForce extends createUnitDirective { 379 | unit = `Force` 380 | static _unit = `Force` 381 | baseEnum = ForceTypesEnum 382 | static _baseEnum = ForceTypesEnum 383 | } 384 | 385 | const EnergyTypesEnum = createEnum(`EnergyTypesEnum`, [ 386 | `joule`, 387 | `J`, 388 | `erg`, 389 | `Wh`, 390 | `BTU`, 391 | ...generateDecimalPrefixes(`electronvolt`, `electronvolts`, `eV`) 392 | ]) 393 | 394 | export class convertEnergy extends createUnitDirective { 395 | unit = `Energy` 396 | static _unit = `Energy` 397 | baseEnum = EnergyTypesEnum 398 | static _baseEnum = EnergyTypesEnum 399 | } 400 | 401 | const PowerTypesEnum = createEnum(`PowerTypesEnum`, [ 402 | ...generateDecimalPrefixes(`watt`, `watts`, `W`), 403 | `hp` 404 | ]) 405 | 406 | export class convertPower extends createUnitDirective { 407 | unit = `Power` 408 | static _unit = `Power` 409 | baseEnum = PowerTypesEnum 410 | static _baseEnum = PowerTypesEnum 411 | } 412 | 413 | const PressureTypesEnum = createEnum(`PressureTypesEnum`, [ 414 | `Pa`, 415 | `psi`, 416 | `atm`, 417 | `torr`, 418 | `bar`, 419 | `mmHg`, 420 | `mmH2O`, 421 | `cmH2O` 422 | ]) 423 | 424 | export class convertPressure extends createUnitDirective { 425 | unit = `Pressure` 426 | static _unit = `Pressure` 427 | baseEnum = PressureTypesEnum 428 | static _baseEnum = PressureTypesEnum 429 | } 430 | 431 | const ElectroMagneticForceTypesEnum = createEnum( 432 | `ElectroMagneticForceTypesEnum`, 433 | [ 434 | ...generateDecimalPrefixes(`coulomb`, `coulombs`, `C`), 435 | ...generateDecimalPrefixes(`watt`, `watts`, `W`), 436 | ...generateDecimalPrefixes(`farad`, `farads`, `F`), 437 | ...generateDecimalPrefixes(`volt`, `volts`, `V`), 438 | ...generateDecimalPrefixes(`henry`, `henrys`, `H`), 439 | ...generateDecimalPrefixes(`weber`, `webers`, `Wb`), 440 | ...generateDecimalPrefixes(`tesla`, `teslas`, `T`), 441 | ...generateDecimalPrefixes(`electronvolt`, `electronvolts`, `eV`), 442 | ...generateDecimalPrefixes(`ampere`, `amperes`, `A`), 443 | `ohm`, 444 | `siemens` 445 | ] 446 | ) 447 | 448 | export class convertElectroMagneticForce extends createUnitDirective { 449 | unit = `ElectroMagneticForce` 450 | static _unit = `ElectroMagneticForce` 451 | baseEnum = ElectroMagneticForceTypesEnum 452 | static _baseEnum = ElectroMagneticForceTypesEnum 453 | } 454 | 455 | const BinaryTypesEnum = createEnum(`BinaryTypesEnum`, [ 456 | ...generateBinaryPrefixes(`bit`, `bits`, `b`), 457 | ...generateBinaryPrefixes(`byte`, `bytes`, `B`) 458 | ]) 459 | 460 | export class convertBinary extends createUnitDirective { 461 | unit = `Binary` 462 | static _unit = `Binary` 463 | baseEnum = BinaryTypesEnum 464 | static _baseEnum = BinaryTypesEnum 465 | } 466 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: `@typescript-eslint/parser`, 4 | env: { 5 | es6: true, 6 | node: true, 7 | browser: true, 8 | "jest/globals": true 9 | }, 10 | plugins: [ 11 | `@typescript-eslint`, // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin 12 | `jest`, // https://github.com/jest-community/eslint-plugin-jest 13 | `import`, // https://github.com/benmosher/eslint-plugin-import 14 | `promise` // https://github.com/xjamundx/eslint-plugin-promise 15 | ], 16 | settings: { 17 | polyfills: [`fetch`, `Promise`] 18 | }, 19 | parserOptions: { 20 | ecmaVersion: 2020, 21 | sourceType: `module` 22 | }, 23 | rules: { 24 | // Possible Errors 25 | "for-direction": 2, 26 | "no-await-in-loop": 2, 27 | "no-compare-neg-zero": 2, 28 | "no-cond-assign": [2, `always`], 29 | "no-console": 2, 30 | "no-constant-condition": 1, 31 | "no-control-regex": 2, 32 | "no-debugger": 1, 33 | "no-dupe-args": 2, 34 | "no-dupe-keys": 2, 35 | "no-duplicate-case": 2, 36 | "no-empty": 2, 37 | "no-empty-character-class": 2, 38 | "no-ex-assign": 2, 39 | "no-extra-boolean-cast": 2, 40 | "no-extra-parens": [2, `functions`], 41 | "no-extra-semi": 0, 42 | "no-func-assign": 2, 43 | "no-inner-declarations": 2, 44 | "no-invalid-regexp": 2, 45 | "no-irregular-whitespace": 2, 46 | "no-obj-calls": 2, 47 | "no-prototype-builtins": 2, 48 | "no-regex-spaces": 2, 49 | "no-sparse-arrays": 2, 50 | "no-template-curly-in-string": 2, 51 | "no-unexpected-multiline": 0, 52 | "no-unreachable": 2, 53 | "no-unsafe-finally": 2, 54 | "no-unsafe-negation": 2, 55 | "use-isnan": 2, 56 | "valid-jsdoc": 0, 57 | "valid-typeof": 2, 58 | 59 | // Best Practices 60 | "accessor-pairs": 2, 61 | "array-callback-return": 1, 62 | "block-scoped-var": 2, 63 | "class-methods-use-this": 0, 64 | complexity: [0, { max: 20 }], 65 | "consistent-return": 0, 66 | curly: [2, `multi-line`], 67 | "default-case": 2, 68 | "dot-location": [2, `property`], 69 | "dot-notation": [2, { allowKeywords: true }], 70 | eqeqeq: [2, `smart`], 71 | "guard-for-in": 2, 72 | "no-alert": 2, 73 | "no-caller": 2, 74 | "no-case-declarations": 2, 75 | "no-div-regex": 2, 76 | "no-else-return": 2, 77 | "no-empty-function": [2, { allow: [`arrowFunctions`, `constructors`] }], 78 | "no-empty-pattern": 2, 79 | "no-eq-null": 2, 80 | "no-eval": 2, 81 | "no-extend-native": 2, 82 | "no-extra-bind": 2, 83 | "no-extra-label": 2, 84 | "no-fallthrough": 2, 85 | "no-floating-decimal": 2, 86 | "no-global-assign": 2, 87 | "no-implicit-coercion": 0, 88 | "no-implicit-globals": 0, 89 | "no-implied-eval": 2, 90 | "no-invalid-this": 0, 91 | "no-iterator": 2, 92 | "no-labels": 2, 93 | "no-lone-blocks": 2, 94 | "no-loop-func": 2, 95 | "no-magic-numbers": 0, 96 | "no-multi-spaces": [ 97 | 2, 98 | { 99 | exceptions: { 100 | Property: true, 101 | VariableDeclarator: true, 102 | ImportDeclaration: true 103 | } 104 | } 105 | ], 106 | "no-multi-str": 2, 107 | "no-new": 2, 108 | "no-new-func": 2, 109 | "no-new-wrappers": 2, 110 | "no-octal": 2, 111 | "no-octal-escape": 2, 112 | "no-param-reassign": 2, 113 | "no-proto": 2, 114 | "no-redeclare": 2, 115 | "no-restricted-properties": 0, 116 | "no-return-assign": [2, `always`], 117 | "no-return-await": 2, 118 | "no-script-url": 2, 119 | "no-self-assign": [2, { props: true }], 120 | "no-self-compare": 2, 121 | "no-sequences": 2, 122 | "no-throw-literal": 2, 123 | "no-unmodified-loop-condition": 2, 124 | "no-unused-expressions": 0, 125 | "no-unused-labels": 2, 126 | "no-useless-call": 2, 127 | "no-useless-concat": 2, 128 | "no-useless-escape": 2, 129 | "no-useless-return": 2, 130 | "no-void": 2, 131 | "no-warning-comments": 0, 132 | "no-with": 2, 133 | "prefer-promise-reject-errors": 2, 134 | radix: 2, 135 | "require-await": 2, 136 | "vars-on-top": 2, 137 | "wrap-iife": [2, `any`], 138 | yoda: 2, 139 | 140 | // Strict Mode 141 | strict: [2, `never`], 142 | 143 | // Variables 144 | "init-declarations": 0, 145 | "no-catch-shadow": 0, 146 | "no-delete-var": 2, 147 | "no-label-var": 2, 148 | "no-restricted-globals": 0, 149 | "no-shadow": 2, 150 | "no-shadow-restricted-names": 2, 151 | "no-undef": 2, 152 | "no-undef-init": 2, 153 | "no-undefined": 2, 154 | "no-use-before-define": [2, { functions: true, classes: true }], 155 | 156 | // Node.js and CommonJS 157 | "callback-return": 0, 158 | "global-require": 0, 159 | "handle-callback-err": 2, 160 | "no-buffer-constructor": 2, 161 | "no-mixed-requires": 0, 162 | "no-new-require": 2, 163 | "no-path-concat": 0, 164 | "no-process-env": 0, 165 | "no-process-exit": 0, 166 | "no-restricted-modules": 0, 167 | "no-sync": 0, 168 | 169 | // Stylistic Issues 170 | "array-bracket-newline": [2, { multiline: true }], 171 | "array-bracket-spacing": 2, 172 | "array-element-newline": 0, 173 | "block-spacing": [2, `always`], 174 | "brace-style": [2, `1tbs`, { allowSingleLine: true }], 175 | camelcase: [2, { properties: `never` }], 176 | "capitalized-comments": 0, 177 | "comma-dangle": [2, `never`], 178 | "comma-spacing": [2, { before: false, after: true }], 179 | "comma-style": [2, `last`], 180 | "computed-property-spacing": 2, 181 | "consistent-this": 0, 182 | "eol-last": 2, 183 | "func-call-spacing": 2, 184 | "func-name-matching": 0, 185 | "func-names": 0, 186 | "func-style": [2, `declaration`, { allowArrowFunctions: true }], 187 | "function-paren-newline": [0, `consistent`], 188 | "id-blacklist": 0, 189 | "id-length": 0, 190 | "id-match": 0, 191 | indent: 0, // Incompatible with Prettier 192 | "jsx-quotes": [2, `prefer-double`], 193 | "key-spacing": 0, 194 | "keyword-spacing": [2, { before: true, after: true }], 195 | "line-comment-position": 0, 196 | "linebreak-style": 0, 197 | "lines-around-comment": 0, 198 | "lines-between-class-members": [ 199 | 2, 200 | `always`, 201 | { exceptAfterSingleLine: true } 202 | ], 203 | "max-depth": 0, 204 | "max-len": 0, 205 | "max-lines": 0, 206 | "max-nested-callbacks": 0, 207 | "max-params": 0, 208 | "max-statements": 0, 209 | "max-statements-per-line": 0, 210 | "multiline-comment-style": 0, 211 | "multiline-ternary": [2, `always-multiline`], 212 | "new-cap": [2, { newIsCap: true }], 213 | "new-parens": 2, 214 | "newline-per-chained-call": 0, 215 | "no-array-constructor": 0, 216 | "no-bitwise": [2, { allow: [`~`] }], 217 | "no-continue": 2, 218 | "no-inline-comments": 0, 219 | "no-lonely-if": 2, 220 | "no-mixed-operators": 0, 221 | "no-mixed-spaces-and-tabs": [2, `smart-tabs`], 222 | "no-multi-assign": 2, 223 | "no-multiple-empty-lines": [2, { max: 2 }], 224 | "no-negated-condition": 2, 225 | "no-nested-ternary": 0, 226 | "no-new-object": 2, 227 | "no-plusplus": 0, 228 | "no-restricted-syntax": 0, 229 | "no-tabs": 0, 230 | "no-ternary": 0, 231 | "no-trailing-spaces": 2, 232 | "no-underscore-dangle": 0, 233 | "no-unneeded-ternary": 2, 234 | "no-whitespace-before-property": 2, 235 | "nonblock-statement-body-position": 0, 236 | "object-curly-newline": [ 237 | 2, 238 | { 239 | ObjectExpression: { consistent: true }, 240 | ObjectPattern: { consistent: true } 241 | } 242 | ], 243 | "object-curly-spacing": [2, `always`], 244 | "object-property-newline": [2, { allowMultiplePropertiesPerLine: true }], 245 | "one-var": [2, `never`], 246 | "one-var-declaration-per-line": [2, `always`], 247 | "operator-assignment": [2, `always`], 248 | "operator-linebreak": 0, 249 | "padded-blocks": [2, `never`], 250 | "padding-line-between-statements": 0, 251 | "quote-props": [2, `as-needed`], 252 | quotes: 0, // Conflicts with TypeScript, use TypeScript Plugin Rule Instead 253 | "require-jsdoc": 0, 254 | semi: [0, `never`], 255 | "semi-spacing": [2, { before: false, after: true }], 256 | "semi-style": [2, `last`], 257 | "sprt-keys": 0, 258 | "sort-vars": 0, 259 | "space-before-blocks": 2, 260 | "space-before-function-paren": [ 261 | 2, 262 | { anonymous: `always`, named: `never`, asyncArrow: `always` } 263 | ], 264 | "space-in-parens": [2, `never`], 265 | "space-infix-ops": 2, 266 | "space-unary-ops": [2, { words: true, nonwords: false }], 267 | "spaced-comment": [ 268 | 0, 269 | `always`, 270 | { plugins: [`react`], exceptions: [`*`], markers: [`*`] } 271 | ], 272 | "switch-colon-spacing": [2, { before: false, after: true }], 273 | "template-tag-spacing": [2, `never`], 274 | "unicode-bom": 0, 275 | "wrap-regex": 0, 276 | 277 | // ECMAScript 6 278 | "arrow-body-style": [2, `as-needed`], 279 | "arrow-parens": [2, `always`], 280 | "arrow-spacing": [2, { before: true, after: true }], 281 | "constructor-super": 0, 282 | "generator-star-spacing": [2, { before: true, after: false }], 283 | "no-class-assign": 2, 284 | "no-confusing-arrow": 0, 285 | "no-const-assign": 2, 286 | "no-dupe-class-members": 2, 287 | "no-duplicate-imports": 2, 288 | "no-new-symbol": 2, 289 | "no-restricted-imports": 0, 290 | "no-this-before-super": 2, 291 | "no-useless-computed-key": 2, 292 | "no-useless-constructor": 2, 293 | "no-useless-rename": 2, 294 | "no-var": 2, 295 | "object-shorthand": [2, `properties`], 296 | "prefer-arrow-callback": 2, 297 | "prefer-const": 0, 298 | "prefer-destructuring": 0, 299 | "prefer-numeric-literals": 2, 300 | "prefer-rest-params": 2, 301 | "prefer-spread": 2, 302 | "prefer-template": 2, 303 | "require-yield": 2, 304 | "rest-spread-spacing": [2, `never`], 305 | "sort-imports": 0, 306 | "symbol-description": 2, 307 | "template-curly-spacing": [2, `never`], 308 | "yield-star-spacing": [2, { before: false, after: true }], 309 | 310 | /*--- Plugin Specific Rules ---*/ 311 | // typescript 312 | "@typescript-eslint/prefer-interface": 0, 313 | "@typescript-eslint/explicit-function-return-type": 0, 314 | "@typescript-eslint/no-use-before-define": 0, 315 | "@typescript-eslint/camelcase": 0, 316 | "@typescript-eslint/no-var-requires": 0, 317 | "@typescript-eslint/no-unused-vars": [ 318 | `error`, 319 | { vars: `local`, args: `none`, ignoreRestSiblings: true } 320 | ], 321 | "@typescript-eslint/quotes": [`error`, `backtick`, { avoidEscape: true }], 322 | 323 | // jest 324 | "jest/consistent-test-it": 2, 325 | "jest/lowercase-name": 0, 326 | "jest/no-disabled-tests": 1, 327 | "jest/no-focused-tests": 2, 328 | "jest/no-hooks": 0, 329 | "jest/no-identical-title": 2, 330 | "jest/no-jest-import": 2, 331 | "jest/no-large-snapshots": [1, { maxSize: 12 }], 332 | "jest/no-test-prefixes": 2, 333 | "jest/prefer-to-have-length": 1, 334 | "jest/prefer-to-be-null": 1, 335 | "jest/prefer-to-be-undefined": 1, 336 | "jest/prefer-expect-assertions": 0, 337 | "jest/valid-describe": 2, 338 | "jest/valid-expect": 2, 339 | "jest/valid-expect-in-promise": 2, 340 | 341 | // import 342 | "import/no-extraneous-dependencies": 0, 343 | "import/no-cycle": `warn`, 344 | "import/prefer-default-export": 0, 345 | "import/no-named-default": 0, 346 | "import/no-webpack-loader-syntax": 0, 347 | "import/named": 2, 348 | "import/default": 2, 349 | "import/export": 2, 350 | "import/no-deprecated": 2, 351 | "import/no-mutable-exports": 2, 352 | "import/no-duplicates": 2, 353 | "import/no-namespace": 0, 354 | "import/newline-after-import": 2, 355 | "import/order": [ 356 | 2, 357 | { 358 | "newlines-between": `never`, 359 | groups: [ 360 | `builtin`, 361 | [`internal`, `external`], 362 | [`parent`, `sibling`], 363 | `index` 364 | ] 365 | } 366 | ], 367 | 368 | // promise 369 | "promise/catch-or-return": [2, { terminationMethod: [`catch`, `finally`] }], 370 | "promise/always-return": 2, 371 | "promise/no-return-wrap": 2, 372 | "promise/param-names": 2, 373 | "promise/prefer-await-to-then": 2, 374 | "promise/prefer-await-to-callbacks": 2 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🧭 GraphQL Directives

2 |

npmtraviscodecov

3 |

A library of custom GraphQL directives to give your schema super powers!

4 | 5 | --- 6 | 7 | ## 🚧 Under Construction 8 | 9 | > Warning: This library is still under construction! A pre-release version is available, but it is recommended not to use this in production! Use at your own risk! 10 | 11 | ## Table of Contents 12 | 13 | - [Installation](#installation) 14 | - [Usage](#usage) 15 | - [Example](#example) 16 | - [Directives](#directives) 17 | - [Currencies](#currencies) 18 | - [\@formatCurrency](#formatcurrency) 19 | - [Dates](#dates) 20 | - [\@formatdate](#formatdate) 21 | - [Numbers](#numbers) 22 | - [\@formatNumber](#formatnumber) 23 | - [Phone Numbers](#phone-numbers) 24 | - [\@formatPhoneNumber](#formatphonenumber) 25 | - [Strings](#strings) 26 | - [\@camelCase](#camelcase) 27 | - [\@capitalize](#capitalize) 28 | - [\@deburr](#deburr) 29 | - [\@kebabCase](#kebabcase) 30 | - [\@lowerCase](#lowercase) 31 | - [\@lowerFirst](#lowerfirst) 32 | - [\@snakeCase](#snakecase) 33 | - [\@toLower](#tolower) 34 | - [\@toUpper](#toupper) 35 | - [\@trim](#trim) 36 | - [\@upperCase](#uppercase) 37 | - [\@upperFirst](#upperfirst) 38 | - [Measurements](#measurements) 39 | - [\@converLength](#converlength) 40 | - [\@convertSurfaceArea](#convertsurfacearea) 41 | - [\@convertVolume](#convertvolume) 42 | - [\@convertLiquidVolume](#convertliquidvolume) 43 | - [\@convertAngle](#convertangle) 44 | - [\@convertTime](#convertime) 45 | - [\@convertMass](#convertmass) 46 | - [\@convertTemperature](#converttemperature) 47 | - [\@convertForce](#convertforce) 48 | - [\@convertEnergy](#convertenergy) 49 | - [\@convertPower](#convertpower) 50 | - [\@convertPressure](#convertpressure) 51 | - [\@convertBinary](#convertbinary) 52 | - [Acknowledgements](#acknowledgements) 53 | - [License](#license) 54 | 55 | ## 📦 Installation 56 | 57 | ```bash 58 | npm install --save graphql graphql-tag @saeris/graphql-directives 59 | # or 60 | yarn add graphql graphql-tag @saeris/graphql-directives 61 | ``` 62 | 63 | ## 🔧 Usage 64 | 65 | To use these directives add them to your Schema Directives when you initialize your Apollo Server: 66 | 67 | ```js 68 | import { ApolloServer } from "apollo-server" 69 | // Import all directives at once 70 | import Directives from "@saeris/graphql-directives" 71 | // Optionally, you can import individual directives or namespaced groups 72 | // import { formatDate, Strings } from "@saeris/graphql-directives" 73 | import { typeDefs } from "./types" 74 | import { resolvers } from "./resolvers" 75 | 76 | const server = new ApolloServer({ 77 | typeDefs: [ 78 | ...typeDefs, 79 | // Map over each directive and get it's type declaration to add them to your schema 80 | // 81 | // Apollo prefers not to mix and match types for graphql type definitions, so typeDefs 82 | // should be either string[] or DocumentNode[]. We expose two helper methods to get the 83 | // correct type declaration. 84 | // 85 | // - use directive#toString() when your types are plain strings 86 | // - use directive#toDocumentNode() when your types are defined using graphql-tag 87 | ...Object.values(Directives).map((directive) => directive.toDocumentNode()) 88 | // Follow the same pattern to get type declarations for groups 89 | // ...Object.values(Strings).map(directive = directive.toDocumentNode()) 90 | // Or for individual directives, just call the getter directly 91 | // formatDate.toDocumentNode() 92 | ], 93 | resolvers, 94 | // Add the directives to your schemaDirectives map 95 | schemaDirectives: { 96 | ...Directives 97 | // Do the same for namespaced groups 98 | // ...Strings 99 | // For individual directives just use object property shorthand 100 | // formatDate 101 | // Don't rename directives! The following will throw an error: 102 | // customName: formatDate 103 | } 104 | }) 105 | 106 | /* eslint-disable */ 107 | server.listen(endpoint).then(({ url }) => { 108 | console.log(`🚀 Server ready at ${url}`) 109 | }) 110 | ``` 111 | 112 | Now you can use them in your schema just like you would any other Directive: 113 | 114 | ```graphql 115 | type Person { 116 | # ... 117 | birthDate: String @formatDate(defaultFormat: "MMMM D, YYYY") 118 | # ... 119 | } 120 | ``` 121 | 122 | ## 🏖️ Example 123 | 124 | You can quickly take this library for a spin by running the example either locally under the `example` directory (just run `yarn && yarn dev` and open your browser to http://localhost:9000) or live inside of CodeSandbox [here](https://codesandbox.io/s/github/Saeris/graphql-directives/). 125 | 126 | ## 🧭 Directives 127 | 128 | Below is a list of each available directive and it's individual usage. GraphQL schema examples include all of the available arguments for the directive and their default values. Query examples include all of the arguments the directive adds to the field on which it was applied in the schema, along with any default values for those arguments. Many of the directives included in this library will change the return type of the field they are applied to, most commonly that will be a `String`. The return type for each directive is noted in it's description. Additionally, example inputs and outputs are provided for each. 129 | 130 | ### 💵 Currencies 131 | 132 | #### \@formatCurrency 133 | 134 | Allows clients to apply custom formatting to currency values. Uses [`dinero.js`](https://github.com/sarahdayan/dinero.js) under the hood. 135 | 136 | ```js 137 | import { formatCurrency } from "@saeris/graphql-directives" 138 | ``` 139 | 140 | Example Schema Usage: 141 | 142 | ```graphql 143 | type Example { 144 | # Int | Float => String 145 | hourlyRate: Int @formatCurrency( 146 | defaultFormat = "$0,0.00" # String! 147 | defaultRoundingMode = HALF_AWAY_FROM_ZERO # RoundingMode! 148 | ) 149 | } 150 | 151 | # Included Automatically when using @formatCurrncy 152 | enum RoundingMode { 153 | HALF_ODD 154 | HALF_EVEN 155 | HALF_UP 156 | HALF_DOWN 157 | HALF_TOWARD_ZERO 158 | HALF_AWAY_FROM_ZERO 159 | } 160 | ``` 161 | 162 | Example Query Usage: 163 | 164 | ```graphql 165 | query ExampleQuery { 166 | getPerson { 167 | # Raw => Default Format => Requested Format 168 | # 1150 => $11.50 => EUR 11.5 169 | hourlyRate(format: "USD0,0.0", currency: "EUR") # => EUR 11.5 170 | } 171 | } 172 | ``` 173 | 174 | 175 |
176 | Additional Formatting Options: 177 | 178 | > Taken from [dinero.js documentation](https://sarahdayan.github.io/dinero.js/module-Dinero.html#~toFormat) 179 | 180 | | Format | Result Example | 181 | | -------------- | --------------- | 182 | | `$0,0.00` | \$5,000.50 | 183 | | `$0,0` | \$5,001 | 184 | | `$0` | \$5001 | 185 | | `$0.0` | \$5000.5 | 186 | | `USD0,0.0` | USD 5,000.5 | 187 | | `0,0.0 dollar` | 5,000.5 dollars | 188 | 189 | > Note: `$`, `USD`, and `dollar` are all symbols. They will automatically be localized to fit the currency you specify. For example `currency: EUR` will transform `$ => €`, `USD => EUR`, and `dollars => euros`. The formats listed above are not tokens, but instead are pre-defined by dinero.js. Custom formatting is not currently available. 190 | 191 |
192 | 193 | 194 | ### 📆 Dates 195 | 196 | #### \@formatDate 197 | 198 | Allows clients to apply custom formatting to date values. Uses [`date-fns`](https://github.com/date-fns/date-fns) under the hood. 199 | 200 | ```js 201 | import { formatDate } from "@saeris/graphql-directives" 202 | ``` 203 | 204 | Example Schema Usage: 205 | 206 | ```graphql 207 | type Example { 208 | # Int | String => String 209 | date: String @formatDate( 210 | defaultFormat = "MMMM D, YYYY" # String! 211 | ) 212 | } 213 | ``` 214 | 215 | Example Query Usage: 216 | 217 | ```graphql 218 | query ExampleQuery { 219 | getPerson { 220 | # Raw => Default Format => Requested Format 221 | # 1549766240251 => February 9, 2019 => 18:37:20pm - February 9, 2019 222 | birthDate(format: "H:mm:ssa - MMMM D, YYYY") # => 18:37:20pm - February 9, 2019 223 | } 224 | } 225 | ``` 226 | 227 | 228 |
229 | Additional Formatting Options: 230 | 231 | > Table taken from [date-fns documentation](https://date-fns.org/v1.30.1/docs/format) 232 | 233 | | Unit | Token | Result Examples | 234 | | ----------------------- | ------ | -------------------------------- | 235 | | Month | `M` | 1, 2, ..., 12 | 236 | | | `Mo` | 1st, 2nd, ..., 12th | 237 | | | `MM` | 01, 02, ..., 12 | 238 | | | `MMM` | Jan, Feb, ..., Dec | 239 | | | `MMMM` | January, February, ..., December | 240 | | Quarter | `Q` | 1, 2, 3, 4 | 241 | | | `Qo` | 1st, 2nd, 3rd, 4th | 242 | | Day of month | `D` | 1, 2, ..., 31 | 243 | | | `Do` | 1st, 2nd, ..., 31st | 244 | | | `DD` | 01, 02, ..., 31 | 245 | | Day of year | `DDD` | 1, 2, ..., 366 | 246 | | | `DDDo` | 1st, 2nd, ..., 366th | 247 | | | `DDDD` | 001, 002, ..., 366 | 248 | | Day of week | `d` | 0, 1, ..., 6 | 249 | | | `do` | 0th, 1st, ..., 6th | 250 | | | `dd` | Su, Mo, ..., Sa | 251 | | | `ddd` | Sun, Mon, ..., Sat | 252 | | | `dddd` | Sunday, Monday, ..., Saturday | 253 | | Day of ISO week | `E` | 1, 2, ..., 7 | 254 | | ISO week | `W` | 1, 2, ..., 53 | 255 | | | `Wo` | 1st, 2nd, ..., 53rd | 256 | | | `WW` | 01, 02, ..., 53 | 257 | | Year | `YY` | 00, 01, ..., 99 | 258 | | | `YYYY` | 1900, 1901, ..., 2099 | 259 | | ISO week-numbering year | `GG` | 00, 01, ..., 99 | 260 | | | `GGGG` | 1900, 1901, ..., 2099 | 261 | | AM/PM | `A` | AM, PM | 262 | | | `a` | am, pm | 263 | | | `aa` | a.m., p.m. | 264 | | Hour | `H` | 0, 1, ... 23 | 265 | | | `HH` | 00, 01, ... 23 | 266 | | | `h` | 1, 2, ..., 12 | 267 | | | `hh` | 01, 02, ..., 12 | 268 | | Minute | `m` | 0, 1, ..., 59 | 269 | | | `mm` | 00, 01, ..., 59 | 270 | | Second | `s` | 0, 1, ..., 59 | 271 | | | `ss` | 00, 01, ..., 59 | 272 | | 1/10 of second | `S` | 0, 1, ..., 9 | 273 | | 1/100 of second | `SS` | 00, 01, ..., 99 | 274 | | Millisecond | `SSS` | 000, 001, ..., 999 | 275 | | Timezone | `Z` | -01:00, +00:00, ... +12:00 | 276 | | | `ZZ` | -0100, +0000, ..., +1200 | 277 | | Seconds timestamp | `X` | 512969520 | 278 | | Milliseconds timestamp | `x` | 512969520900 | 279 | 280 |
281 | 282 | 283 | ### 🧮 Numbers 284 | 285 | #### \@formatNumber 286 | 287 | Allows clients to apply custom formatting to numerical values. Uses [`numeral`](https://github.com/adamwdraper/Numeral-js) under the hood. 288 | 289 | ```js 290 | import { formatNumber } from "@saeris/graphql-directives" 291 | ``` 292 | 293 | Example Schema Usage: 294 | 295 | ```graphql 296 | type Example { 297 | # Int | Float => String 298 | balance: Float @formatNumber( 299 | defaultFormat = "0,0.0000" # String! 300 | ) 301 | } 302 | ``` 303 | 304 | Example Query Usage: 305 | 306 | ```graphql 307 | query ExampleQuery { 308 | getPerson { 309 | # Raw => Default Format => Requested Format 310 | # 11075.25 => 11,075.2500 => 11.1k 311 | balance(format: "0.0a") # => 11.1k 312 | } 313 | } 314 | ``` 315 | 316 | 317 |
318 | Additional Formatting Options: 319 | 320 | > Tables taken from [numeral documentation](http://numeraljs.com/#format) 321 | 322 | **Numbers** 323 | | Number | Format | String | 324 | |-------------|--------------|---------------| 325 | | 10000 | `0,0.0000` | 10,000.0000 | 326 | | 10000.23 | `0,0` | 10,000 | 327 | | 10000.23 | `+0,0` | +10,000 | 328 | | -10000 | `0,0.0` | -10,000.0 | 329 | | 10000.1234 | `0.000` | 10000.123 | 330 | | 100.1234 | `00000` | 00100 | 331 | | 1000.1234 | `000000,0` | 001,000 | 332 | | 10 | `000.00` | 010.00 | 333 | | 10000.1234 | `0[.]00000` | 10000.12340 | 334 | | -10000 | `(0,0.0000)` | (10,000.0000) | 335 | | -0.23 | `.00` | -.23 | 336 | | -0.23 | `(.00)` | (.23) | 337 | | 0.23 | `0.00000` | 0.23000 | 338 | | 0.23 | `0.0[0000]` | 0.23 | 339 | | 1230974 | `0.0a` | 1.2m | 340 | | 1460 | `0 a` | 1 k | 341 | | -104000 | `0a` | -104k | 342 | | 1 | `0o` | 1st | 343 | | 100 | `0o` | 100th | 344 | 345 | **Currency** 346 | | Number | Format | String | 347 | |-----------|--------------|-------------| 348 | | 1000.234 | `$0,0.00` | \$1,000.23 | 349 | | 1000.2 | `0,0[.]00 $` | 1,000.20 \$ | 350 | | 1001 | `$ 0,0[.]00` | \$ 1,001 | 351 | | -1000.234 | `($0,0)` | (\$1,000) | 352 | | -1000.234 | `$0.00` | -\$1000.23 | 353 | | 1230974 | `($ 0.00 a)` | \$ 1.23 m | 354 | 355 | **Bytes** 356 | | Number | Format | String | 357 | |---------------|------------|------------| 358 | | 100 | `0b` | 100B | 359 | | 1024 | `0b` | 1KB | 360 | | 2048 | `0 ib` | 2 KiB | 361 | | 3072 | `0.0 b` | 3.1 KB | 362 | | 7884486213 | `0.00b` | 7.88GB | 363 | | 3467479682787 | `0.000 ib` | 3.154 TiB | 364 | 365 | **Percentages** 366 | | Number | Format | String | 367 | |---------------|--------------|-----------| 368 | | 1 | `0%` | 100% | 369 | | 0.974878234 | `0.000%` | 97.488% | 370 | | -0.43 | `0 %` | -43 % | 371 | | 0.43 | `(0.000 %)` | 43.000 % | 372 | 373 | **Time** 374 | | Number | Format | String | 375 | |---------|------------|----------| 376 | | 25 | `00:00:00` | 0:00:25 | 377 | | 238 | `00:00:00` | 0:03:58 | 378 | | 63846 | `00:00:00` | 17:44:06 | 379 | 380 | **Exponential** 381 | | Number | Format | String | 382 | |---------------|-------------|----------| 383 | | 1123456789 | `0,0e+0` | 1e+9 | 384 | | 12398734.202 | `0.00e+0` | 1.24e+7 | 385 | | 0.000123987 | `0.000e+0` | 1.240e-4 | 386 | 387 |
388 | 389 | 390 | ### 📞 Phone Numbers 391 | 392 | #### \@formatPhoneNumber 393 | 394 | Allows clients to apply various standard formatting to phone numbers. Uses [`libphonenumber-js`](https://github.com/catamphetamine/libphonenumber-js) under the hood. 395 | 396 | ```js 397 | import { formatPhoneNumber } from "@saeris/graphql-directives" 398 | ``` 399 | 400 | Example Schema Usage: 401 | 402 | ```graphql 403 | type Example { 404 | # String => String 405 | phoneNumber: String @formatPhoneNumber( 406 | defaultFormat = International # PhoneFormats! 407 | ) 408 | } 409 | 410 | # Included Automatically when using @formatPhoneNumber 411 | enum PhoneFormats { 412 | National 413 | International 414 | E164 415 | RFC3966 416 | } 417 | ``` 418 | 419 | Example Query Usage: 420 | 421 | ```graphql 422 | query ExampleQuery { 423 | getPerson { 424 | # Raw => Default Format => Requested Format 425 | # +17895551234 => +1 789 555 1234 => (789) 555-1234 426 | phoneNumber(format: National) # => (789) 555-1234 427 | } 428 | } 429 | ``` 430 | 431 | 432 |
433 | Additional Formatting Options: 434 | 435 | > Table taken from [libphonenumber-js documentation](https://github.com/catamphetamine/libphonenumber-js#formatformat-string-options) 436 | 437 | | Format | Example | 438 | | --------------- | ------------------------ | 439 | | `National` | (213) 373-4253 | 440 | | `International` | +1 213 373 4253 | 441 | | `E164` | +12133734253 | 442 | | `RFC3966` | tel:+12133734253;ext=123 | 443 | 444 |
445 | 446 | 447 | ### 📝 Strings 448 | 449 | #### String Formatting 450 | 451 | The following directives all apply various basic string formats and are designed such that they can be combined with other directives to transform strings without adding extra arguments to a field. They all use [`lodash`](https://github.com/lodash/lodash) under the hood. Descriptions and examples are taken from the [lodash documentation](https://lodash.com/docs/4.17.11). 452 | 453 | #### \@camelCase 454 | 455 | Converts string to camel case. 456 | 457 | ```js 458 | import { camelCase } from "@saeris/graphql-directives" 459 | ``` 460 | 461 | Example Schema Usage: 462 | 463 | ```graphql 464 | type Example { 465 | # String => String 466 | message: String @camelCase 467 | } 468 | ``` 469 | 470 | Example Query Usage: 471 | 472 | ```graphql 473 | query ExampleQuery { 474 | getMessage { 475 | # Raw => Formatted String 476 | # Foo Bar => fooBar 477 | # --foo-bar-- => fooBar 478 | # __FOO_BAR__ => fooBar 479 | message # => fooBar 480 | } 481 | } 482 | ``` 483 | 484 | #### \@capitalize 485 | 486 | Converts the first character of string to upper case and the remaining to lower case. 487 | 488 | ```js 489 | import { capitalize } from "@saeris/graphql-directives" 490 | ``` 491 | 492 | Example Schema Usage: 493 | 494 | ```graphql 495 | type Example { 496 | # String => String 497 | message: String @capitalize 498 | } 499 | ``` 500 | 501 | Example Query Usage: 502 | 503 | ```graphql 504 | query ExampleQuery { 505 | getMessage { 506 | # Raw => Formatted String 507 | # FRED => Fred 508 | message # => Fred 509 | } 510 | } 511 | ``` 512 | 513 | #### \@deburr 514 | 515 | Deburrs string by converting Latin-1 Supplement and Latin Extended-A letters to basic Latin letters and removing combining diacritical marks. 516 | 517 | ```js 518 | import { deburr } from "@saeris/graphql-directives" 519 | ``` 520 | 521 | Example Schema Usage: 522 | 523 | ```graphql 524 | type Example { 525 | # String => String 526 | message: String @deburr 527 | } 528 | ``` 529 | 530 | Example Query Usage: 531 | 532 | ```graphql 533 | query ExampleQuery { 534 | getMessage { 535 | # Raw => Formatted String 536 | # déjà vu => deja vu 537 | message # => deja vu 538 | } 539 | } 540 | ``` 541 | 542 | #### \@kebabCase 543 | 544 | Converts string to kebab case. 545 | 546 | ```js 547 | import { kebabCase } from "@saeris/graphql-directives" 548 | ``` 549 | 550 | Example Schema Usage: 551 | 552 | ```graphql 553 | type Example { 554 | # String => String 555 | message: String @kebabCase 556 | } 557 | ``` 558 | 559 | Example Query Usage: 560 | 561 | ```graphql 562 | query ExampleQuery { 563 | getMessage { 564 | # Raw => Formatted String 565 | # Foo Bar => foo-bar 566 | # fooBar => foo-bar 567 | # __FOO_BAR__ => foo-bar 568 | message # => foo-bar 569 | } 570 | } 571 | ``` 572 | 573 | #### \@lowerCase 574 | 575 | Converts string, as space separated words, to lower case. 576 | 577 | ```js 578 | import { lowerCase } from "@saeris/graphql-directives" 579 | ``` 580 | 581 | Example Schema Usage: 582 | 583 | ```graphql 584 | type Example { 585 | # String => String 586 | message: String @lowerCase 587 | } 588 | ``` 589 | 590 | Example Query Usage: 591 | 592 | ```graphql 593 | query ExampleQuery { 594 | getMessage { 595 | # Raw => Formatted String 596 | # --Foo-Bar-- => foo bar 597 | # fooBar => foo bar 598 | # __FOO_BAR__ => foo bar 599 | message # => foo bar 600 | } 601 | } 602 | ``` 603 | 604 | #### \@lowerFirst 605 | 606 | Converts the first character of string to lower case. 607 | 608 | ```js 609 | import { lowerFirst } from "@saeris/graphql-directives" 610 | ``` 611 | 612 | Example Schema Usage: 613 | 614 | ```graphql 615 | type Example { 616 | # String => String 617 | message: String @lowerFirst 618 | } 619 | ``` 620 | 621 | Example Query Usage: 622 | 623 | ```graphql 624 | query ExampleQuery { 625 | getMessage { 626 | # Raw => Formatted String 627 | # Fred => fred 628 | # FRED => fRED 629 | message # => fRED 630 | } 631 | } 632 | ``` 633 | 634 | #### \@snakeCase 635 | 636 | Converts string to snake case. 637 | 638 | ```js 639 | import { snakeCase } from "@saeris/graphql-directives" 640 | ``` 641 | 642 | Example Schema Usage: 643 | 644 | ```graphql 645 | type Example { 646 | # String => String 647 | message: String @snakeCase 648 | } 649 | ``` 650 | 651 | Example Query Usage: 652 | 653 | ```graphql 654 | query ExampleQuery { 655 | getMessage { 656 | # Raw => Formatted String 657 | # Foo Bar => foo_bar 658 | # fooBar => foo_bar 659 | # --FOO-BAR-- => foo_bar 660 | message # => foo_bar 661 | } 662 | } 663 | ``` 664 | 665 | #### \@toLower 666 | 667 | Converts string, as a whole, to lower case. 668 | 669 | ```js 670 | import { toLower } from "@saeris/graphql-directives" 671 | ``` 672 | 673 | Example Schema Usage: 674 | 675 | ```graphql 676 | type Example { 677 | # String => String 678 | message: String @toLower 679 | } 680 | ``` 681 | 682 | Example Query Usage: 683 | 684 | ```graphql 685 | query ExampleQuery { 686 | getMessage { 687 | # Raw => Formatted String 688 | # --Foo-Bar-- => --foo-bar-- 689 | # fooBar => foobar 690 | # __FOO_BAR__ => __foo_bar__ 691 | message # => foobar 692 | } 693 | } 694 | ``` 695 | 696 | #### \@toUpper 697 | 698 | Converts string, as a whole, to upper case. 699 | 700 | ```js 701 | import { toUpper } from "@saeris/graphql-directives" 702 | ``` 703 | 704 | Example Schema Usage: 705 | 706 | ```graphql 707 | type Example { 708 | # String => String 709 | message: String @toUpper 710 | } 711 | ``` 712 | 713 | Example Query Usage: 714 | 715 | ```graphql 716 | query ExampleQuery { 717 | getMessage { 718 | # Raw => Formatted String 719 | # --foo-bar-- => --FOO-BAR-- 720 | # fooBar => FOOBAR 721 | # __foo_bar__ => __FOO_BAR__ 722 | message # => FOOBAR 723 | } 724 | } 725 | ``` 726 | 727 | #### \@trim 728 | 729 | Removes leading and trailing whitespace from string. 730 | 731 | ```js 732 | import { trim } from "@saeris/graphql-directives" 733 | ``` 734 | 735 | Example Schema Usage: 736 | 737 | ```graphql 738 | type Example { 739 | # String => String 740 | message: String @trim 741 | } 742 | ``` 743 | 744 | Example Query Usage: 745 | 746 | ```graphql 747 | query ExampleQuery { 748 | getMessage { 749 | # Raw => Formatted String 750 | # ' abc ' => 'abc' 751 | message # => abc 752 | } 753 | } 754 | ``` 755 | 756 | #### \@upperCase 757 | 758 | Converts string, as space separated words, to upper case. 759 | 760 | ```js 761 | import { upperCase } from "@saeris/graphql-directives" 762 | ``` 763 | 764 | Example Schema Usage: 765 | 766 | ```graphql 767 | type Example { 768 | # String => String 769 | message: String @upperCase 770 | } 771 | ``` 772 | 773 | Example Query Usage: 774 | 775 | ```graphql 776 | query ExampleQuery { 777 | getMessage { 778 | # Raw => Formatted String 779 | # --foo-bar-- => FOO BAR 780 | # fooBar => FOO BAR 781 | # __foo_bar__ => FOO BAR 782 | message # => FOO BAR 783 | } 784 | } 785 | ``` 786 | 787 | #### \@upperFirst 788 | 789 | Converts the first character of string to upper case. 790 | 791 | ```js 792 | import { upperFirst } from "@saeris/graphql-directives" 793 | ``` 794 | 795 | Example Schema Usage: 796 | 797 | ```graphql 798 | type Example { 799 | # String => String 800 | message: String @upperFirst 801 | } 802 | ``` 803 | 804 | Example Query Usage: 805 | 806 | ```graphql 807 | query ExampleQuery { 808 | getMessage { 809 | # Raw => Formatted String 810 | # fred => Fred 811 | # fRED => FRED 812 | message # => Fred 813 | } 814 | } 815 | ``` 816 | 817 | ### 📐 Measurements 818 | 819 | #### Measurement Conversions 820 | 821 | The following directives all convert a category of measurement from one unit to another, for example feet to meters, gallons to fluid ounces, etc. They all use [`mathjs`](https://github.com/josdejong/mathjs) under the hood. 822 | 823 | Each conversion directive includes a GraphQL Enum with all of the valid values that directive can process. Included are singular, plural, and abbreviated forms of each unit, with SI prefixes where applicable. 824 | 825 | 826 |
827 | Comprehensive list of supported units: 828 | 829 | > Tables taken from [mathjs documentation](http://mathjs.org/docs/datatypes/units.html#reference) 830 | 831 | | Base | Unit | 832 | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 833 | | **Length** | meter (m), inch (in), foot (ft), yard (yd), mile (mi), link (li), rod (rd), chain (ch), angstrom, mil | 834 | | **Surface area** | m2, sqin, sqft, sqyd, sqmi, sqrd, sqch, sqmil, acre, hectare | 835 | | **Volume** | m3, litre (l, L, lt, liter), cc, cuin, cuft, cuyd, teaspoon, tablespoon | 836 | | **Liquid volume** | minim (min), fluiddram (fldr), fluidounce (floz), gill (gi), cup (cp), pint (pt), quart (qt), gallon (gal), beerbarrel (bbl), oilbarrel (obl), hogshead, drop (gtt) | 837 | | **Angles** | rad (radian), deg (degree), grad (gradian), cycle, arcsec (arcsecond), arcmin (arcminute) | 838 | | **Time** | second (s, secs, seconds), minute (mins, minutes), hour (h, hr, hrs, hours), day (days), week (weeks), month (months), year (years), decade (decades), century (centuries), millennium (millennia) | 839 | | **Mass** | gram(g), tonne, ton, grain (gr), dram (dr), ounce (oz), poundmass (lbm, lb, lbs), hundredweight (cwt), stick, stone | 840 | | **Temperature** | kelvin (K), celsius (degC), fahrenheit (degF), rankine (degR) | 841 | | **Force** | newton (N), dyne (dyn), poundforce (lbf), kip | 842 | | **Energy** | joule (J), erg, Wh, BTU, electronvolt (eV) | 843 | | **Power** | watt (W), hp | 844 | | **Pressure** | Pa, psi, atm, torr, bar, mmHg, mmH2O, cmH2O | 845 | | **Binary** | bit (b), byte (B) | 846 | 847 | > Note: Frequency, Electric Current, Amount of Substance, and Luminous Intensity are not currently included as they have only a single unit of measurement inside of mathjs. 848 | 849 | Prefixes 850 | The following decimal prefixes are available. 851 | 852 | | Name | Abbreviation | Value | 853 | | ----- | ------------ | ----- | 854 | | deca | da | 1e1 | 855 | | hecto | h | 1e2 | 856 | | kilo | k | 1e3 | 857 | | mega | M | 1e6 | 858 | | giga | G | 1e9 | 859 | | tera | T | 1e12 | 860 | | peta | P | 1e15 | 861 | | exa | E | 1e18 | 862 | | zetta | Z | 1e21 | 863 | | yotta | Y | 1e24 | 864 | 865 | | Name | Abbreviation | Value | 866 | | ----- | ------------ | ----- | 867 | | deci | d | 1e-1 | 868 | | centi | c | 1e-2 | 869 | | milli | m | 1e-3 | 870 | | micro | u | 1e-6 | 871 | | nano | n | 1e-9 | 872 | | pico | p | 1e-12 | 873 | | femto | f | 1e-15 | 874 | | atto | a | 1e-18 | 875 | | zepto | z | 1e-21 | 876 | | yocto | y | 1e-24 | 877 | 878 | The following binary prefixes are available. They can be used with units bit (b) and byte (B). 879 | 880 | | Name | Abbreviation | Value | 881 | | ---- | ------------ | ------ | 882 | | kibi | Ki | 1024 | 883 | | mebi | Mi | 1024^2 | 884 | | gibi | Gi | 1024^3 | 885 | | tebi | Ti | 1024^4 | 886 | | pebi | Pi | 1024^5 | 887 | | exi | Ei | 1024^6 | 888 | | zebi | Zi | 1024^7 | 889 | | yobi | Yi | 1024^8 | 890 | 891 | | Name | Abbreviation | Value | 892 | | ----- | ------------ | ----- | 893 | | kilo | k | 1e3 | 894 | | mega | M | 1e6 | 895 | | giga | G | 1e9 | 896 | | tera | T | 1e12 | 897 | | peta | P | 1e15 | 898 | | exa | E | 1e18 | 899 | | zetta | Z | 1e21 | 900 | | yotta | Y | 1e24 | 901 | 902 |
903 | 904 | 905 | #### \@convertLength 906 | 907 | Converts length measurements from one unit to another. 908 | 909 | ```js 910 | import { convertLength } from "@saeris/graphql-directives" 911 | ``` 912 | 913 | Example Schema Usage: 914 | 915 | ```graphql 916 | type Example { 917 | # Int | Float => String 918 | height: Float @convertLength( 919 | # Note: There is no pre-configured default, you must specify 920 | # the originalUnit in your schema! 921 | originalUnit = inches # LengthTypesEnum! 922 | # When true, only the value will be returned 923 | defaultRaw = false # Boolean! 924 | ) 925 | } 926 | ``` 927 | 928 | Example Query Usage: 929 | 930 | ```graphql 931 | query ExampleQuery { 932 | getPerson { 933 | # Raw => Original Unit => Requested Unit 934 | # 70.5 => 70.5 inches => 5.875 feet 935 | height(convertTo: feet) # => 5.875 feet 936 | } 937 | } 938 | ``` 939 | 940 | #### \@convertSurfaceArea 941 | 942 | Converts surface area measurements from one unit to another. 943 | 944 | ```js 945 | import { convertSurfaceArea } from "@saeris/graphql-directives" 946 | ``` 947 | 948 | Example Schema Usage: 949 | 950 | ```graphql 951 | type Example { 952 | # Int | Float => String 953 | roomDimensions: Float @convertSurfaceArea( 954 | # Note: There is no pre-configured default, you must specify 955 | # the originalUnit in your schema! 956 | originalUnit = sqft # SurfaceAreaTypesEnum! 957 | # When true, only the value will be returned 958 | defaultRaw = false # Boolean! 959 | ) 960 | } 961 | ``` 962 | 963 | Example Query Usage: 964 | 965 | ```graphql 966 | query ExampleQuery { 967 | getPerson { 968 | # Raw => Original Unit => Requested Unit 969 | # 25.75 => 25.75 sqft => 2.3922532800000003 m2 970 | roomDimensions(convertTo: m2) # => 2.3922532800000003 m2 971 | } 972 | } 973 | ``` 974 | 975 | #### \@convertVolume 976 | 977 | Converts volume measurements from one unit to another. 978 | 979 | ```js 980 | import { convertVolume } from "@saeris/graphql-directives" 981 | ``` 982 | 983 | Example Schema Usage: 984 | 985 | ```graphql 986 | type Example { 987 | # Int | Float => String 988 | bagSize: Float @convertVolume( 989 | # Note: There is no pre-configured default, you must specify 990 | # the originalUnit in your schema! 991 | originalUnit = cuin # VolumeTypesEnum! 992 | # When true, only the value will be returned 993 | defaultRaw = false # Boolean! 994 | ) 995 | } 996 | ``` 997 | 998 | Example Query Usage: 999 | 1000 | ```graphql 1001 | query ExampleQuery { 1002 | getPerson { 1003 | # Raw => Original Unit => Requested Unit 1004 | # 2772 => 2772 cuin => 45.424941407999995 litre 1005 | bagSize(convertTo: litre) # => 45.424941407999995 litre 1006 | } 1007 | } 1008 | ``` 1009 | 1010 | #### \@convertLiquidVolume 1011 | 1012 | Converts liquid volume measurements from one unit to another. 1013 | 1014 | ```js 1015 | import { convertLiquidVolume } from "@saeris/graphql-directives" 1016 | ``` 1017 | 1018 | Example Schema Usage: 1019 | 1020 | ```graphql 1021 | type Example { 1022 | # Int | Float => String 1023 | coffeeConsumed: Float @convertLiquidVolume( 1024 | # Note: There is no pre-configured default, you must specify 1025 | # the originalUnit in your schema! 1026 | originalUnit = fluidounce # LiquidVolumeTypesEnum! 1027 | # When true, only the value will be returned 1028 | defaultRaw = false # Boolean! 1029 | ) 1030 | } 1031 | ``` 1032 | 1033 | Example Query Usage: 1034 | 1035 | ```graphql 1036 | query ExampleQuery { 1037 | getPerson { 1038 | # Raw => Original Unit => Requested Unit 1039 | # 21.125 => 21.125 fluidounce => 2.6406254464508376 cup 1040 | coffeeConsumed(convertTo: cup) # => 2.6406254464508376 cup 1041 | } 1042 | } 1043 | ``` 1044 | 1045 | #### \@convertAngle 1046 | 1047 | Converts angle measurements from one unit to another. 1048 | 1049 | ```js 1050 | import { convertAngle } from "@saeris/graphql-directives" 1051 | ``` 1052 | 1053 | Example Schema Usage: 1054 | 1055 | ```graphql 1056 | type Example { 1057 | # Int | Float => String 1058 | trajectory: Float @convertAngle( 1059 | # Note: There is no pre-configured default, you must specify 1060 | # the originalUnit in your schema! 1061 | originalUnit = deg # AngleTypesEnum! 1062 | # When true, only the value will be returned 1063 | defaultRaw = false # Boolean! 1064 | ) 1065 | } 1066 | ``` 1067 | 1068 | Example Query Usage: 1069 | 1070 | ```graphql 1071 | query ExampleQuery { 1072 | getPerson { 1073 | # Raw => Original Unit => Requested Unit 1074 | # 21.125 => 21.125 fluidounce => 2.6406254464508376 cup 1075 | trajectory(convertTo: rad) # => 2.6406254464508376 cup 1076 | } 1077 | } 1078 | ``` 1079 | 1080 | #### \@convertTime 1081 | 1082 | Converts time measurements from one unit to another. 1083 | 1084 | ```js 1085 | import { convertTime } from "@saeris/graphql-directives" 1086 | ``` 1087 | 1088 | Example Schema Usage: 1089 | 1090 | ```graphql 1091 | type Example { 1092 | # Int | Float => String 1093 | age: Int @convertTime( 1094 | # Note: There is no pre-configured default, you must specify 1095 | # the originalUnit in your schema! 1096 | originalUnit = years # TimeTypesEnum! 1097 | # When true, only the value will be returned 1098 | defaultRaw = false # Boolean! 1099 | ) 1100 | } 1101 | ``` 1102 | 1103 | Example Query Usage: 1104 | 1105 | ```graphql 1106 | query ExampleQuery { 1107 | getPerson { 1108 | # Raw => Original Unit => Requested Unit 1109 | # 21 => 21 years => 7670.25 days 1110 | age(convertTo: days) # => 7670.25 days 1111 | } 1112 | } 1113 | ``` 1114 | 1115 | #### \@convertMass 1116 | 1117 | Converts mass/weight measurements from one unit to another. 1118 | 1119 | ```js 1120 | import { convertTime } from "@saeris/graphql-directives" 1121 | ``` 1122 | 1123 | Example Schema Usage: 1124 | 1125 | ```graphql 1126 | type Example { 1127 | # Int | Float => String 1128 | weight: Int @convertTime( 1129 | # Note: There is no pre-configured default, you must specify 1130 | # the originalUnit in your schema! 1131 | originalUnit = poundmass # TimeTypesEnum! 1132 | # When true, only the value will be returned 1133 | defaultRaw = false # Boolean! 1134 | ) 1135 | } 1136 | ``` 1137 | 1138 | Example Query Usage: 1139 | 1140 | ```graphql 1141 | query ExampleQuery { 1142 | getPerson { 1143 | # Raw => Original Unit => Requested Unit 1144 | # 21 => 21 years => 7670.25 days 1145 | weight(convertTo: gram) # => 7670.25 days 1146 | } 1147 | } 1148 | ``` 1149 | 1150 | #### \@convertTemperature 1151 | 1152 | Converts temperature measurements from one unit to another. 1153 | 1154 | ```js 1155 | import { convertTemperature } from "@saeris/graphql-directives" 1156 | ``` 1157 | 1158 | Example Schema Usage: 1159 | 1160 | ```graphql 1161 | type Example { 1162 | # Int | Float => String 1163 | temperature: Float @convertTemperature( 1164 | # Note: There is no pre-configured default, you must specify 1165 | # the originalUnit in your schema! 1166 | originalUnit = degF # TemperatureTypesEnum! 1167 | # When true, only the value will be returned 1168 | defaultRaw = false # Boolean! 1169 | ) 1170 | } 1171 | ``` 1172 | 1173 | Example Query Usage: 1174 | 1175 | ```graphql 1176 | query ExampleQuery { 1177 | getPerson { 1178 | # Raw => Original Unit => Requested Unit 1179 | # 21 => 21 years => 7670.25 days 1180 | temperature(convertTo: degC) # => 7670.25 days 1181 | } 1182 | } 1183 | ``` 1184 | 1185 | #### \@convertForce 1186 | 1187 | Converts force measurements from one unit to another. 1188 | 1189 | ```js 1190 | import { convertForce } from "@saeris/graphql-directives" 1191 | ``` 1192 | 1193 | Example Schema Usage: 1194 | 1195 | ```graphql 1196 | type Example { 1197 | # Int | Float => String 1198 | temperature: Float @convertForce( 1199 | # Note: There is no pre-configured default, you must specify 1200 | # the originalUnit in your schema! 1201 | originalUnit = degF # ForceTypesEnum! 1202 | # When true, only the value will be returned 1203 | defaultRaw = false # Boolean! 1204 | ) 1205 | } 1206 | ``` 1207 | 1208 | Example Query Usage: 1209 | 1210 | ```graphql 1211 | query ExampleQuery { 1212 | getPerson { 1213 | # Raw => Original Unit => Requested Unit 1214 | # 21 => 21 years => 7670.25 days 1215 | temperature(convertTo: degC) # => 7670.25 days 1216 | } 1217 | } 1218 | ``` 1219 | 1220 | #### \@convertEnergy 1221 | 1222 | Converts energy measurements from one unit to another. 1223 | 1224 | ```js 1225 | import { convertEnergy } from "@saeris/graphql-directives" 1226 | ``` 1227 | 1228 | Example Schema Usage: 1229 | 1230 | ```graphql 1231 | type Example { 1232 | # Int | Float => String 1233 | temperature: Float @convertEnergy( 1234 | # Note: There is no pre-configured default, you must specify 1235 | # the originalUnit in your schema! 1236 | originalUnit = degF # EnergyTypesEnum! 1237 | # When true, only the value will be returned 1238 | defaultRaw = false # Boolean! 1239 | ) 1240 | } 1241 | ``` 1242 | 1243 | Example Query Usage: 1244 | 1245 | ```graphql 1246 | query ExampleQuery { 1247 | getPerson { 1248 | # Raw => Original Unit => Requested Unit 1249 | # 21 => 21 years => 7670.25 days 1250 | temperature(convertTo: degC) # => 7670.25 days 1251 | } 1252 | } 1253 | ``` 1254 | 1255 | #### \@convertPower 1256 | 1257 | Converts power measurements from one unit to another. 1258 | 1259 | ```js 1260 | import { convertPower } from "@saeris/graphql-directives" 1261 | ``` 1262 | 1263 | Example Schema Usage: 1264 | 1265 | ```graphql 1266 | type Example { 1267 | # Int | Float => String 1268 | temperature: Float @convertPower( 1269 | # Note: There is no pre-configured default, you must specify 1270 | # the originalUnit in your schema! 1271 | originalUnit = degF # PowerTypesEnum! 1272 | # When true, only the value will be returned 1273 | defaultRaw = false # Boolean! 1274 | ) 1275 | } 1276 | ``` 1277 | 1278 | Example Query Usage: 1279 | 1280 | ```graphql 1281 | query ExampleQuery { 1282 | getPerson { 1283 | # Raw => Original Unit => Requested Unit 1284 | # 21 => 21 years => 7670.25 days 1285 | temperature(convertTo: degC) # => 7670.25 days 1286 | } 1287 | } 1288 | ``` 1289 | 1290 | #### \@convertPressure 1291 | 1292 | Converts pressure measurements from one unit to another. 1293 | 1294 | ```js 1295 | import { convertPressure } from "@saeris/graphql-directives" 1296 | ``` 1297 | 1298 | Example Schema Usage: 1299 | 1300 | ```graphql 1301 | type Example { 1302 | # Int | Float => String 1303 | temperature: Float @convertPressure( 1304 | # Note: There is no pre-configured default, you must specify 1305 | # the originalUnit in your schema! 1306 | originalUnit = degF # PressureTypesEnum! 1307 | # When true, only the value will be returned 1308 | defaultRaw = false # Boolean! 1309 | ) 1310 | } 1311 | ``` 1312 | 1313 | Example Query Usage: 1314 | 1315 | ```graphql 1316 | query ExampleQuery { 1317 | getPerson { 1318 | # Raw => Original Unit => Requested Unit 1319 | # 21 => 21 years => 7670.25 days 1320 | temperature(convertTo: degC) # => 7670.25 days 1321 | } 1322 | } 1323 | ``` 1324 | 1325 | #### \@convertBinary 1326 | 1327 | Converts binary measurements from one unit to another. 1328 | 1329 | ```js 1330 | import { convertBinary } from "@saeris/graphql-directives" 1331 | ``` 1332 | 1333 | Example Schema Usage: 1334 | 1335 | ```graphql 1336 | type Example { 1337 | # Int => String 1338 | diskSpace: Int @convertBinary( 1339 | # Note: There is no pre-configured default, you must specify 1340 | # the originalUnit in your schema! 1341 | originalUnit = bytes # BinaryTypesEnum! 1342 | # When true, only the value will be returned 1343 | defaultRaw = false # Boolean! 1344 | ) 1345 | } 1346 | ``` 1347 | 1348 | Example Query Usage: 1349 | 1350 | ```graphql 1351 | query ExampleQuery { 1352 | getPerson { 1353 | # Raw => Original Unit => Requested Unit 1354 | # 1024 => 1024 bytes => 78192 bits 1355 | diskSpace(convertTo: bits) # => 8192 bits 1356 | } 1357 | } 1358 | ``` 1359 | 1360 | ## 📣 Acknowledgements 1361 | 1362 | This library was inspired by [@lirown/graphql-custom-directives](https://github.com/lirown/graphql-custom-directives). It is an Apollo Server implementation based on their [Schema Directives documentation](https://www.apollographql.com/docs/graphql-tools/schema-directives.html). 1363 | 1364 | ## 🥂 License 1365 | 1366 | Released under the [MIT license](https://github.com/Saeris/graphql-directives/blob/master/LICENSE.md). 1367 | --------------------------------------------------------------------------------