├── .eslintrc.json ├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src └── index.ts ├── test └── index.test.ts ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": ["@typescript-eslint"], 8 | "extends": ["plugin:@typescript-eslint/recommended"], 9 | "rules": { 10 | "curly": "error", 11 | "prefer-const": "error", 12 | "newline-before-return": "error", 13 | "@typescript-eslint/no-unused-vars": [ 14 | "error", 15 | { "argsIgnorePattern": "^_" } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Begin CI... 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Test 26 | run: yarn test --ci --coverage --maxWorkers=2 27 | 28 | - name: Build 29 | run: yarn build 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | shell.nix 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.2.1](https://github.com/angeloashmore/gatsby-node-helpers/compare/v1.2.0...v1.2.1) (2021-02-22) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * namespace globally unique nodes with typePrefix ([159d4de](https://github.com/angeloashmore/gatsby-node-helpers/commit/159d4dec7541693de956e6a0e9153a58f766c044)) 11 | 12 | ## [1.2.0](https://github.com/angeloashmore/gatsby-node-helpers/compare/v1.1.0...v1.2.0) (2021-02-22) 13 | 14 | 15 | ### Features 16 | 17 | * support nodes with globally unique IDs ([125b5b3](https://github.com/angeloashmore/gatsby-node-helpers/commit/125b5b34c0aeee835a1f8ac69e21eb1e2970d835)) 18 | 19 | ## [1.1.0](https://github.com/angeloashmore/gatsby-node-helpers/compare/v1.0.3...v1.1.0) (2021-02-21) 20 | 21 | 22 | ### Features 23 | 24 | * accept non-string node IDs ([c481230](https://github.com/angeloashmore/gatsby-node-helpers/commit/c481230229c268f41ab4f1d846d78b42ab2d3c01)) 25 | 26 | ### [1.0.3](https://github.com/angeloashmore/gatsby-node-helpers/compare/v1.0.2...v1.0.3) (2021-01-06) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * remove exports from package.json ([a05b183](https://github.com/angeloashmore/gatsby-node-helpers/commit/a05b18303321e654ae76f811cab3afd48235083a)) 32 | 33 | ### [1.0.2](https://github.com/angeloashmore/gatsby-node-helpers/compare/v1.0.1...v1.0.2) (2020-12-29) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * wider type for IdentifiableRecord ([e9029bd](https://github.com/angeloashmore/gatsby-node-helpers/commit/e9029bdc2fa33d5cd48e328022e2f6156edc838c)) 39 | 40 | ### [1.0.1](https://github.com/angeloashmore/gatsby-node-helpers/compare/v1.0.0...v1.0.1) (2020-12-29) 41 | 42 | ## [1.0.0](https://github.com/angeloashmore/gatsby-node-helpers/compare/v0.3.0...v1.0.0) (2020-12-29) 43 | 44 | 45 | ### ⚠ BREAKING CHANGES 46 | 47 | * new API (#18) 48 | 49 | ### Features 50 | 51 | * new API ([#18](https://github.com/angeloashmore/gatsby-node-helpers/issues/18)) ([f8ffaf0](https://github.com/angeloashmore/gatsby-node-helpers/commit/f8ffaf00a6c73f9a16b9a5b1395693fbee949a66)) 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Angelo Ashmore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gatsby-node-helpers 2 | 3 | Gatsby node helper functions to aid node creation. To be used when creating 4 | [Gatsby source plugins](https://www.gatsbyjs.org/docs/create-source-plugin/). 5 | 6 | - Automatically adds Gatsby's required node fields such as `contentDigest` 7 | - Namespaces fields conflicting with Gatsby's reserved fields 8 | - Creates portable functions for generating compliant type names and IDs 9 | 10 | ## Status 11 | 12 | [![npm version](https://img.shields.io/npm/v/gatsby-node-helpers?style=flat-square)](https://www.npmjs.com/package/gatsby-node-helpers) 13 | [![Build Status](https://img.shields.io/github/workflow/status/angeloashmore/gatsby-node-helpers/CI?style=flat-square)](https://github.com/angeloashmore/gatsby-node-helpers/actions?query=workflow%3ACI) 14 | 15 | ## Installation 16 | 17 | ``` 18 | npm install --save gatsby-node-helpers 19 | ``` 20 | 21 | ## Quick Guide 22 | 23 | Import the named module: 24 | 25 | ```typescript 26 | import { createNodeHelpers } from 'gatsby-node-helpers' 27 | ``` 28 | 29 | Then call `createNodeHelpers` with options. You will need to pass functions 30 | available in Gatsby's Node APIs, such as `sourceNodes` and 31 | `createSchemaCustomization`. 32 | 33 | The following example shows usage in `gatsby-node.js`'s `sourceNodes` API, but 34 | it can be used elsewhere provided the appropriate helper functions are 35 | available. 36 | 37 | ```typescript 38 | // gatsby-node.ts 39 | 40 | import * as gatsby from 'gatsby' 41 | import { createNodeHelpers } from 'gatsby-node-helpers' 42 | 43 | export const sourceNodes: gatsby.GatsbyNode['sourceNodes'] = async ( 44 | gatsbyArgs: gatsby.SourceNodesArgs, 45 | pluginOptions: gatsby.PluginOptions, 46 | ) => { 47 | const { createNodeId, createContentDigest } = gatsbyArgs 48 | 49 | const nodeHelpers = createNodeHelpers({ 50 | typePrefix: 'Shopify', 51 | createNodeId, 52 | createContentDigest, 53 | }) 54 | } 55 | ``` 56 | 57 | The result of `createNodeHelpers` includes a factory function named 58 | `createNodeFactory` that should be used to prepare an object just before calling 59 | Gatsby's `createNode`. 60 | 61 | The created function will automatically assign Gatsby's required fields, like 62 | `internal` and `contentDigest`, while renaming any conflicting fields. 63 | 64 | ```typescript 65 | const nodeHelpers = createNodeHelpers({ 66 | typePrefix: 'Shopify', 67 | createNodeId: gatsbyArgs.createNodeId, 68 | createContentDigest: gatsbyArgs.createContentDigest, 69 | }) 70 | 71 | const ProductNode = nodeHelpers.createNodeFactory('Product') 72 | const ProductVariantNode = nodeHelpers.createNodeFactory('ProductVariant') 73 | ``` 74 | 75 | In the above example, we can now pass Product objects to `ProductNode` to 76 | prepare the object for Gatsby's `createNode`. 77 | 78 | ```typescript 79 | // gatsby-node.ts 80 | 81 | import * as gatsby from 'gatsby' 82 | import { createNodeHelpers } from 'gatsby-node-helpers' 83 | 84 | export const sourceNodes: gatsby.GatsbyNode['sourceNodes'] = async ( 85 | gatsbyArgs: gatsby.SourceNodesArgs, 86 | pluginOptions: gatsby.PluginOptions, 87 | ) => { 88 | const { actions, createNodeId, createContentDigest } = gatsbyArgs 89 | const { createNodes } = actions 90 | 91 | const nodeHelpers = createNodeHelpers({ 92 | typePrefix: 'Shopify', 93 | createNodeId, 94 | createContentDigest, 95 | }) 96 | 97 | const ProductNode = nodeHelpers.createNodeFactory('Product') 98 | const ProductVariantNode = nodeHelpers.createNodeFactory('ProductVariant') 99 | 100 | // `getAllProducts` is an API function that returns all Shopify products. 101 | const products = await getAllProducts() 102 | 103 | for (const product of products) { 104 | const node = ProductNode(product) 105 | 106 | // `node` now contains all the fields required by `createNode`. 107 | 108 | createNode(node) 109 | } 110 | } 111 | ``` 112 | 113 | ## API 114 | 115 | All functions and types are documented in the source files using 116 | [TSDoc](https://github.com/microsoft/tsdoc) to provide documentation directly in 117 | your editor. 118 | 119 | If you editor does not have TSDoc integration, you can read all documentation by 120 | viewing the source files. 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-node-helpers", 3 | "version": "1.2.1", 4 | "description": "Gatsby node helper functions to aid node creation.", 5 | "source": "./src/index.ts", 6 | "main": "./dist/gatsby-node-helpers.js", 7 | "module": "./dist/gatsby-node-helpers.module.js", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "README.md" 13 | ], 14 | "scripts": { 15 | "prebuild": "rimraf dist", 16 | "build": "microbundle", 17 | "dev": "microbundle watch", 18 | "test": "jest", 19 | "prepare": "yarn build", 20 | "release": "standard-version" 21 | }, 22 | "homepage": "https://github.com/angeloashmore/gatsby-node-helpers", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/angeloashmore/gatsby-node-helpers.git" 26 | }, 27 | "keywords": [ 28 | "gatsby", 29 | "gatsby-plugin" 30 | ], 31 | "author": "Angelo Ashmore", 32 | "license": "MIT", 33 | "dependencies": { 34 | "camel-case": "^4.1.2", 35 | "pascal-case": "^3.1.2" 36 | }, 37 | "peerDependencies": { 38 | "gatsby": ">=2.29" 39 | }, 40 | "devDependencies": { 41 | "@commitlint/cli": "^11.0.0", 42 | "@commitlint/config-conventional": "^11.0.0", 43 | "@types/jest": "^26.0.19", 44 | "@typescript-eslint/eslint-plugin": "^4.11.0", 45 | "@typescript-eslint/parser": "^4.11.0", 46 | "eslint": "^7.16.0", 47 | "gatsby": "^2.29.2", 48 | "gatsby-core-utils": "^1.7.1", 49 | "husky": "^4.3.6", 50 | "jest": "^26.6.3", 51 | "microbundle": "^0.13.0", 52 | "prettier": "^2.2.1", 53 | "rimraf": "^3.0.2", 54 | "standard-version": "^9.0.0", 55 | "ts-jest": "^26.4.4", 56 | "typescript": "^4.1.3" 57 | }, 58 | "prettier": { 59 | "semi": false, 60 | "singleQuote": true, 61 | "trailingComma": "all", 62 | "proseWrap": "always", 63 | "printWidth": 80 64 | }, 65 | "jest": { 66 | "preset": "ts-jest", 67 | "modulePathIgnorePatterns": [ 68 | "/dist/" 69 | ] 70 | }, 71 | "husky": { 72 | "hooks": { 73 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 74 | } 75 | }, 76 | "commitlint": { 77 | "extends": [ 78 | "@commitlint/config-conventional" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as gatsby from 'gatsby' 2 | import * as pc from 'pascal-case' 3 | import * as cc from 'camel-case' 4 | 5 | /** 6 | * Converts a collection of strings to a single Pascal cased string. 7 | * 8 | * @param parts Strings to convert into a single Pascal cased string. 9 | * 10 | * @return Pascal cased string version of `parts`. 11 | */ 12 | const pascalCase = (...parts: (string | null | undefined)[]): string => 13 | pc.pascalCase(parts.filter((p) => p != null).join(' '), { 14 | transform: pc.pascalCaseTransformMerge, 15 | }) 16 | 17 | /** 18 | * Converts a collection of strings to a single camel cased string. 19 | * 20 | * @param parts Strings to convert into a single camel cased string. 21 | * 22 | * @return Camel cased string version of `parts`. 23 | */ 24 | const camelCase = (...parts: (string | null | undefined)[]): string => 25 | cc.camelCase(parts.filter((p) => p != null).join(' '), { 26 | transform: cc.camelCaseTransformMerge, 27 | }) 28 | 29 | /** 30 | * Casts a value to an array. If the input is an array, the input is returned as 31 | * is. Otherwise, the input is returned as a single element array with the input 32 | * as its only value. 33 | * 34 | * @param input Input that will be casted to an array. 35 | * 36 | * @return `input` that is guaranteed to be an array. 37 | */ 38 | const castArray = (input: T | T[]): T[] => 39 | Array.isArray(input) ? input : [input] 40 | 41 | /** 42 | * Reserved fields for Gatsby nodes. 43 | */ 44 | const RESERVED_GATSBY_NODE_FIELDS = [ 45 | 'id', 46 | 'internal', 47 | 'fields', 48 | 'parent', 49 | 'children', 50 | ] as const 51 | 52 | interface CreateNodeHelpersParams { 53 | /** Prefix for all nodes. Used as a namespace for node type names. */ 54 | typePrefix: string 55 | /** 56 | * Prefix for field names. Used as a namespace for fields that conflict with 57 | * Gatsby's reserved field names. 58 | * */ 59 | fieldPrefix?: string 60 | /** Gatsby's `createNodeId` helper. */ 61 | createNodeId: gatsby.SourceNodesArgs['createNodeId'] 62 | /** Gatsby's `createContentDigest` helper. */ 63 | createContentDigest: gatsby.SourceNodesArgs['createContentDigest'] 64 | } 65 | 66 | /** 67 | * A value that can be converted to a string using `toString()`. 68 | */ 69 | export interface Stringable { 70 | toString(): string 71 | } 72 | 73 | /** 74 | * A record that can be globally identified using its `id` field. 75 | */ 76 | export interface IdentifiableRecord { 77 | id: Stringable 78 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 79 | [key: string]: any 80 | } 81 | 82 | /** 83 | * Gatsby node helper functions to aid node creation. 84 | */ 85 | export interface NodeHelpers { 86 | /** 87 | * Creates a namespaced type name in Pascal case. Nodes created using a 88 | * `createNodeFactory` function will automatically be namespaced using this 89 | * function. 90 | * 91 | * @param parts Parts of the type name. If more than one string is provided, 92 | * they will be concatenated in Pascal case. 93 | * 94 | * @return Namespaced type name. 95 | */ 96 | createTypeName: (parts: string | string[]) => string 97 | 98 | /** 99 | * Creates a namespaced field name in camel case. Nodes created using a 100 | * `createNodeFactory` function will automatically have namespaced fields 101 | * using this function ONLY if the name conflicts with Gatsby's reserved 102 | * fields. 103 | * 104 | * @param parts Parts of the field name. If more than one string is provided, 105 | * they will be concatenated in camel case. 106 | * 107 | * @return Namespaced field name. 108 | */ 109 | createFieldName: (parts: string | string[]) => string 110 | 111 | /** 112 | * Creates a deterministic node ID based on the `typePrefix` option provided 113 | * to `createNodeHelpers` and the provided `parts` argument. Providing the 114 | * same `parts` will always return the same result. 115 | * 116 | * @param parts Strings to globally identify a node within the domain of the 117 | * node helpers. 118 | * 119 | * @return Node ID based on the provided `parts`. 120 | */ 121 | createNodeId: (parts: string | string[]) => string 122 | 123 | /** 124 | * Creates a function that will convert an identifiable record (one that has 125 | * an `id` field) to a valid input for Gatsby's `createNode` action. 126 | * 127 | * @param nameParts Parts of the type name for the resulting factory. All 128 | * records called with the resulting function will have a type name based on 129 | * this parameter. 130 | * 131 | * @param options Options to control the resulting function's output. 132 | * 133 | * @return A function that converts an identifiable record to a valid input 134 | * for Gatsby's `createNode` action. 135 | */ 136 | createNodeFactory: ( 137 | nameParts: string | string[], 138 | options?: CreateNodeFactoryOptions, 139 | ) => (node: IdentifiableRecord) => gatsby.NodeInput 140 | } 141 | 142 | /** 143 | * Options for a node factory. 144 | */ 145 | type CreateNodeFactoryOptions = { 146 | /** 147 | * Determines if the node's `id` field is unique within all nodes created with 148 | * this collection of node helpers. 149 | * 150 | * If `false`, the ID will be namespaced with the node's type and the 151 | * `typePrefix` value. 152 | * 153 | * If `true`, the ID will not be namespaced with the node's type, but will still 154 | * be namespaced with the `typePrefix` value. 155 | * 156 | * @defaultValue `false` 157 | */ 158 | idIsGloballyUnique?: boolean 159 | } 160 | 161 | /** 162 | * Creates Gatsby node helper functions to aid node creation. 163 | */ 164 | export const createNodeHelpers = ({ 165 | typePrefix, 166 | fieldPrefix = typePrefix, 167 | createNodeId: gatsbyCreateNodeId, 168 | createContentDigest: gatsbyCreateContentDigest, 169 | }: CreateNodeHelpersParams): NodeHelpers => { 170 | const createTypeName = (nameParts: string | string[]): string => 171 | pascalCase(typePrefix, ...castArray(nameParts)) 172 | 173 | const createFieldName = (nameParts: string | string[]): string => 174 | camelCase(fieldPrefix, ...castArray(nameParts)) 175 | 176 | const createNodeId = (nameParts: string | string[]): string => 177 | gatsbyCreateNodeId( 178 | [typePrefix, ...castArray(nameParts)].filter((p) => p != null).join(' '), 179 | ) 180 | 181 | const createNodeFactory = ( 182 | nameParts: string | string[], 183 | { idIsGloballyUnique = false }: CreateNodeFactoryOptions = {}, 184 | ) => (node: IdentifiableRecord): gatsby.NodeInput => { 185 | const id = idIsGloballyUnique 186 | ? createNodeId(node.id.toString()) 187 | : createNodeId([...castArray(nameParts), node.id.toString()]) 188 | 189 | const res = { 190 | ...node, 191 | id, 192 | internal: { 193 | type: createTypeName(nameParts), 194 | contentDigest: gatsbyCreateContentDigest(node), 195 | }, 196 | } as gatsby.NodeInput 197 | 198 | for (const reservedField of RESERVED_GATSBY_NODE_FIELDS) { 199 | if (reservedField in node) { 200 | res[createFieldName(reservedField)] = node[reservedField] 201 | } 202 | } 203 | 204 | return res 205 | } 206 | 207 | return { 208 | createTypeName, 209 | createFieldName, 210 | createNodeId, 211 | createNodeFactory, 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { createContentDigest } from 'gatsby-core-utils' 2 | 3 | import { createNodeHelpers } from '../src' 4 | 5 | const createNodeId = (input: string): string => `createNodeId(${input})` 6 | 7 | const createConfig = () => ({ 8 | typePrefix: 'typePrefix', 9 | fieldPrefix: 'fieldPrefix', 10 | createNodeId, 11 | createContentDigest, 12 | }) 13 | 14 | const node = { 15 | // Required ID field 16 | id: 'id', 17 | 18 | // Arbitrary data field 19 | foo: 'bar', 20 | 21 | // Reserved Gatsby fields 22 | internal: 'internal', 23 | fields: 'fields', 24 | parent: 'parent', 25 | children: 'children', 26 | } 27 | 28 | test('creates node helpers', () => { 29 | const config = createConfig() 30 | const helpers = createNodeHelpers(config) 31 | 32 | expect(Object.keys(helpers)).toEqual([ 33 | 'createTypeName', 34 | 'createFieldName', 35 | 'createNodeId', 36 | 'createNodeFactory', 37 | ]) 38 | }) 39 | 40 | describe('createTypeName', () => { 41 | test('creates pascalcase name with prefix', () => { 42 | const config = createConfig() 43 | const helpers = createNodeHelpers(config) 44 | 45 | expect(helpers.createTypeName('typeName')).toBe('TypePrefixTypeName') 46 | }) 47 | 48 | test('handles numbers and spaces correctly', () => { 49 | const config = createConfig() 50 | const helpers = createNodeHelpers(config) 51 | 52 | expect(helpers.createTypeName('123')).toBe('TypePrefix123') 53 | expect(helpers.createTypeName('1 2 3')).toBe('TypePrefix123') 54 | expect(helpers.createTypeName('Foo_Bar')).toBe('TypePrefixFooBar') 55 | expect(helpers.createTypeName('Foo Bar')).toBe('TypePrefixFooBar') 56 | }) 57 | 58 | test('supports array input', () => { 59 | const config = createConfig() 60 | const helpers = createNodeHelpers(config) 61 | 62 | expect(helpers.createTypeName(['foo', 'bar'])).toBe('TypePrefixFooBar') 63 | }) 64 | }) 65 | 66 | describe('createFieldName', () => { 67 | test('creates camelcase name with prefix', () => { 68 | const config = createConfig() 69 | const helpers = createNodeHelpers(config) 70 | 71 | expect(helpers.createFieldName('fieldName')).toBe('fieldPrefixFieldName') 72 | }) 73 | 74 | test('handles numbers and spaces correctly', () => { 75 | const config = createConfig() 76 | const helpers = createNodeHelpers(config) 77 | 78 | expect(helpers.createFieldName('123')).toBe('fieldPrefix123') 79 | expect(helpers.createFieldName('1 2 3')).toBe('fieldPrefix123') 80 | expect(helpers.createFieldName('Foo_Bar')).toBe('fieldPrefixFooBar') 81 | expect(helpers.createFieldName('Foo Bar')).toBe('fieldPrefixFooBar') 82 | }) 83 | 84 | test('supports array input', () => { 85 | const config = createConfig() 86 | const helpers = createNodeHelpers(config) 87 | 88 | expect(helpers.createFieldName(['foo', 'bar'])).toBe('fieldPrefixFooBar') 89 | }) 90 | }) 91 | 92 | describe('createNodeId', () => { 93 | test('creates typePrefix namespaced ID using provided createNodeId function', () => { 94 | const config1 = createConfig() 95 | config1.typePrefix = 'type1' 96 | const helpers1 = createNodeHelpers(config1) 97 | 98 | const config2 = createConfig() 99 | config2.typePrefix = 'type2' 100 | const helpers2 = createNodeHelpers(config2) 101 | 102 | expect(helpers1.createNodeId('foo')).toBe('createNodeId(type1 foo)') 103 | expect(helpers2.createNodeId('foo')).toBe('createNodeId(type2 foo)') 104 | }) 105 | 106 | test('supports array input', () => { 107 | const config = createConfig() 108 | const helpers = createNodeHelpers(config) 109 | 110 | expect(helpers.createNodeId(['foo', 'bar'])).toBe( 111 | 'createNodeId(typePrefix foo bar)', 112 | ) 113 | }) 114 | }) 115 | 116 | describe('createNodeFactory', () => { 117 | const config = createConfig() 118 | const helpers = createNodeHelpers(config) 119 | const fn = helpers.createNodeFactory('TypeName') 120 | const nodeInput = fn(node) 121 | 122 | test('modifies id field using createNodeId', () => { 123 | expect(nodeInput.id).toBe(`createNodeId(typePrefix TypeName ${node.id})`) 124 | }) 125 | 126 | test('accepts non-string node IDs', () => { 127 | const modifiedNode = { ...node, id: [1, 2, 3] } 128 | const modifiedNodeInput = fn(modifiedNode) 129 | 130 | expect(modifiedNodeInput.id).toBe( 131 | `createNodeId(typePrefix TypeName ${modifiedNode.id})`, 132 | ) 133 | expect(modifiedNodeInput.fieldPrefixId).toBe(modifiedNode.id) 134 | }) 135 | 136 | test('identifying an id as globally unique does not namespace id', () => { 137 | const modifiedFn = helpers.createNodeFactory('TypeName', { 138 | idIsGloballyUnique: true, 139 | }) 140 | const modifiedNodeInput = modifiedFn(node) 141 | 142 | expect(modifiedNodeInput.id).toBe(`createNodeId(typePrefix ${node.id})`) 143 | }) 144 | 145 | test('adds internal field with required Gatsby fields', () => { 146 | expect(nodeInput.internal).toEqual({ 147 | type: 'TypePrefixTypeName', 148 | contentDigest: expect.any(String), 149 | }) 150 | }) 151 | 152 | test('namespaces conflicting reserved fields', () => { 153 | expect(nodeInput.fieldPrefixId).toBe(node.id) 154 | expect(nodeInput.fieldPrefixInternal).toBe(node.internal) 155 | expect(nodeInput.fieldPrefixFields).toBe(node.fields) 156 | expect(nodeInput.fieldPrefixParent).toBe(node.parent) 157 | expect(nodeInput.fieldPrefixChildren).toBe(node.children) 158 | }) 159 | 160 | test('supports array input', () => { 161 | const modifiedFn = helpers.createNodeFactory(['foo', 'bar']) 162 | const modifiedNodeInput = modifiedFn(node) 163 | 164 | expect(modifiedNodeInput.internal.type).toBe('TypePrefixFooBar') 165 | }) 166 | }) 167 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "target": "ESNext", 6 | "lib": ["ESNext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./src", 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "moduleResolution": "node", 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "noEmit": true 21 | } 22 | } 23 | --------------------------------------------------------------------------------