Skip to content

Commit 48ab7ea

Browse files
authored
feat: add support for type prefix (#243)
This PR adds support for setting a custom type prefix, which allows multiple instances of the plugin to be used. The prefix is in addition to the `Sanity` prefix, so for example if you have a `movies` type, setting the type prefix to `Staging` will make the type `StagingSanityMovies`.
1 parent 8fd7079 commit 48ab7ea

File tree

8 files changed

+100
-61
lines changed

8 files changed

+100
-61
lines changed

README.md

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Get up and running in minutes with a fully configured starter project:
2222
- [Generating pages](#generating-pages)
2323
- ["Raw" fields](#raw-fields)
2424
- [Portable Text / Block Content](#portable-text--block-content)
25+
- [Using multiple datasets](#using-multiple-datasets)
2526
- [Real-time content preview with watch mode](#real-time-content-preview-with-watch-mode)
2627
- [Updating content for editors with preview servers](#updating-content-for-editors-with-preview-servers)
2728
- [Using .env variables](#using-env-variables)
@@ -81,15 +82,16 @@ Explore `http://localhost:8000/___graphql` after running `gatsby develop` to und
8182

8283
## Options
8384

84-
| Options | Type | Default | Description |
85-
| --------------- | ------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86-
| projectId | string | | **[required]** Your Sanity project's ID |
87-
| dataset | string | | **[required]** The dataset to fetch from |
88-
| token | string | | Authentication token for fetching data from private datasets, or when using `overlayDrafts` [Learn more](https://www.sanity.io/docs/http-auth) |
89-
| graphqlTag | string | `default` | If the Sanity GraphQL API was deployed using `--tag <name>`, use this to specify the tag name. |
90-
| overlayDrafts | boolean | `false` | Set to `true` in order for drafts to replace their published version. By default, drafts will be skipped. |
91-
| watchMode | boolean | `false` | Set to `true` to keep a listener open and update with the latest changes in realtime. If you add a `token` you will get all content updates down to each key press. |
92-
| watchModeBuffer | number | `150` | How many milliseconds to wait on watchMode changes before applying them to Gatsby's GraphQL layer. Introduced in 7.2.0. |
85+
| Options | Type | Default | Description |
86+
| --------------- | ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87+
| projectId | string | | **[required]** Your Sanity project's ID |
88+
| dataset | string | | **[required]** The dataset to fetch from |
89+
| token | string | | Authentication token for fetching data from private datasets, or when using `overlayDrafts` [Learn more](https://www.sanity.io/docs/http-auth) |
90+
| graphqlTag | string | `default` | If the Sanity GraphQL API was deployed using `--tag <name>`, use this to specify the tag name. |
91+
| overlayDrafts | boolean | `false` | Set to `true` in order for drafts to replace their published version. By default, drafts will be skipped. |
92+
| watchMode | boolean | `false` | Set to `true` to keep a listener open and update with the latest changes in realtime. If you add a `token` you will get all content updates down to each key press. |
93+
| watchModeBuffer | number | `150` | How many milliseconds to wait on watchMode changes before applying them to Gatsby's GraphQL layer. Introduced in 7.2.0. |
94+
| typePrefix | string | | Prefix to use for the GraphQL types. This is prepended to `Sanity` in the type names and allows you to have multiple instances of the plugin in your Gatsby project. |
9395

9496
## Preview of unpublished content
9597

@@ -244,6 +246,35 @@ These data structures can be deep and a chore to query (specifying all the possi
244246

245247
You can install [@portabletext/react](https://www.npmjs.com/package/@portabletext/react) from npm and use it in your Gatsby project to serialize Portable Text. It lets you use your own React components to override defaults and render custom content types. [Learn more about Portable Text in our documentation](https://www.sanity.io/docs/content-studio/what-you-need-to-know-about-block-text).
246248

249+
## Using multiple datasets
250+
251+
If you want to use more than one dataset in your site, you can do so by adding multiple instances of the plugin to your `gatsby-config.js` file. To avoid conflicting type names you can use the `typeName` option to set a custom prefix for the GraphQL types. These can be datasets from different projects, or different datasets within the same project.
252+
253+
```js
254+
// In your gatsby-config.js
255+
module.exports = {
256+
plugins: [
257+
{
258+
resolve: 'gatsby-source-sanity',
259+
options: {
260+
projectId: 'abc123',
261+
dataset: 'production',
262+
},
263+
},
264+
{
265+
resolve: 'gatsby-source-sanity',
266+
options: {
267+
projectId: 'abc123',
268+
dataset: 'staging',
269+
typePrefix: 'Staging',
270+
},
271+
},
272+
],
273+
}
274+
```
275+
276+
In this case, the type names for the first instance will be `Sanity<typeName>`, while the second will be `StagingSanity<typeName>`.
277+
247278
## Real-time content preview with watch mode
248279

249280
While developing, it can often be beneficial to get updates without having to manually restart the build process. By setting `watchMode` to true, this plugin will set up a listener which watches for changes. When it detects a change, the document in question is updated in real-time and will be reflected immediately.

src/gatsby-node.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
} from './util/remoteGraphQLSchema'
4444
import {rewriteGraphQLSchema} from './util/rewriteGraphQLSchema'
4545
import validateConfig, {PluginConfig} from './util/validateConfig'
46+
import {ProcessingOptions} from './util/normalize'
4647

4748
let coreSupportsOnPluginInit: 'unstable' | 'stable' | undefined
4849

@@ -101,7 +102,7 @@ const initializePlugin = async (
101102
stateCache[graphqlSdlKey] = graphqlSdl
102103

103104
reporter.info('[sanity] Stitching GraphQL schemas from SDL')
104-
const typeMap = getTypeMapFromGraphQLSchema(api)
105+
const typeMap = getTypeMapFromGraphQLSchema(api, config.typePrefix)
105106
const typeMapKey = getCacheKey(config, CACHE_KEYS.TYPE_MAP)
106107
stateCache[typeMapKey] = typeMap
107108
} catch (err: any) {
@@ -233,14 +234,15 @@ export const sourceNodes: GatsbyNode['sourceNodes'] = async (
233234
const url = client.getUrl(`/data/export/${dataset}?tag=sanity.gatsby.source-nodes`)
234235

235236
// Stitches together required methods from within the context and actions objects
236-
const processingOptions = {
237+
const processingOptions: ProcessingOptions = {
237238
typeMap,
238239
createNodeId,
239240
createNode,
240241
createContentDigest,
241242
createParentChildLink,
242243
overlayDrafts,
243244
client,
245+
typePrefix: config.typePrefix,
244246
}
245247

246248
// PREVIEW UPDATES THROUGH WEBHOOKS

src/util/cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ export enum CACHE_KEYS {
1111
}
1212

1313
export function getCacheKey(config: PluginConfig, suffix: CACHE_KEYS) {
14-
return `${config.projectId}-${config.dataset}-${suffix}`
14+
return `${config.projectId}-${config.dataset}-${config.typePrefix ?? ''}-${suffix}`
1515
}

src/util/getSyncWithGatsby.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function getSyncWithGatsby(props: {
2020
args: SourceNodesArgs
2121
}): SyncWithGatsby {
2222
const {documents, gatsbyNodes, processingOptions, args} = props
23-
const {typeMap, overlayDrafts} = processingOptions
23+
const {typeMap, overlayDrafts, typePrefix} = processingOptions
2424
const {reporter, actions} = args
2525
const {createNode, deleteNode} = actions
2626

@@ -38,7 +38,7 @@ export default function getSyncWithGatsby(props: {
3838

3939
const doc = draft || published
4040
if (doc) {
41-
const type = getTypeName(doc._type)
41+
const type = getTypeName(doc._type, typePrefix)
4242
if (!typeMap.objects[type]) {
4343
reporter.warn(
4444
`[sanity] Document "${doc._id}" has type ${doc._type} (${type}), which is not declared in the GraphQL schema. Make sure you run "graphql deploy". Skipping document.`,

src/util/normalize.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import {SanityClient} from '@sanity/client'
1212

1313
const scalarTypeNames = specifiedScalarTypes.map((def) => def.name).concat(['JSON', 'Date'])
1414

15-
// Movie => SanityMovie
16-
const typePrefix = 'Sanity'
17-
1815
// Node fields used internally by Gatsby.
1916
export const RESTRICTED_NODE_FIELDS = ['id', 'children', 'parent', 'fields', 'internal']
2017

@@ -26,19 +23,20 @@ export interface ProcessingOptions {
2623
createParentChildLink: Actions['createParentChildLink']
2724
overlayDrafts: boolean
2825
client: SanityClient
26+
typePrefix?: string
2927
}
3028

3129
// Transform a Sanity document into a Gatsby node
3230
export function toGatsbyNode(doc: SanityDocument, options: ProcessingOptions): SanityInputNode {
3331
const {createNodeId, createContentDigest, overlayDrafts} = options
3432

3533
const rawAliases = getRawAliases(doc, options)
36-
const safe = prefixConflictingKeys(doc)
34+
const safe = prefixConflictingKeys(doc, options.typePrefix)
3735
const withRefs = rewriteNodeReferences(safe, options)
3836

3937
addInternalTypesToUnionFields(withRefs, options)
4038

41-
const type = getTypeName(doc._type)
39+
const type = getTypeName(doc._type, options.typePrefix)
4240
const urlBuilder = imageUrlBuilder(options.client)
4341

4442
const gatsbyImageCdnFields = [`SanityImageAsset`, `SanityFileAsset`].includes(type)
@@ -79,7 +77,7 @@ export function toGatsbyNode(doc: SanityDocument, options: ProcessingOptions): S
7977
// movie => SanityMovie
8078
// blog_post => SanityBlogPost
8179
// sanity.imageAsset => SanityImageAsset
82-
export function getTypeName(type: string) {
80+
export function getTypeName(type: string, typePrefix: string | undefined) {
8381
if (!type) {
8482
return type
8583
}
@@ -89,31 +87,34 @@ export function getTypeName(type: string) {
8987
return typeName
9088
}
9189

92-
return `${typePrefix}${typeName.replace(/\s+/g, '').replace(/^Sanity/, '')}`
90+
const sanitized = typeName.replace(/\s+/g, '')
91+
92+
const prefix = `${typePrefix ?? ''}${sanitized.startsWith('Sanity') ? '' : 'Sanity'}`
93+
return sanitized.startsWith(prefix) ? sanitized : `${prefix}${sanitized}`
9394
}
9495

9596
// {foo: 'bar', children: []} => {foo: 'bar', sanityChildren: []}
96-
function prefixConflictingKeys(obj: SanityDocument) {
97+
function prefixConflictingKeys(obj: SanityDocument, typePrefix: string | undefined) {
9798
// Will be overwritten, but initialize for type safety
9899
const initial: SanityDocument = {_id: '', _type: '', _rev: '', _createdAt: '', _updatedAt: ''}
99100

100101
return Object.keys(obj).reduce((target, key) => {
101-
const targetKey = getConflictFreeFieldName(key)
102+
const targetKey = getConflictFreeFieldName(key, typePrefix)
102103
target[targetKey] = obj[key]
103104

104105
return target
105106
}, initial)
106107
}
107108

108-
export function getConflictFreeFieldName(fieldName: string) {
109+
export function getConflictFreeFieldName(fieldName: string, typePrefix: string | undefined) {
109110
return RESTRICTED_NODE_FIELDS.includes(fieldName)
110111
? `${camelCase(typePrefix)}${upperFirst(fieldName)}`
111112
: fieldName
112113
}
113114

114115
function getRawAliases(doc: SanityDocument, options: ProcessingOptions) {
115116
const {typeMap} = options
116-
const typeName = getTypeName(doc._type)
117+
const typeName = getTypeName(doc._type, options.typePrefix)
117118
const type = typeMap.objects[typeName]
118119
if (!type) {
119120
return {}
@@ -158,7 +159,7 @@ function addInternalTypesToUnionFields(doc: SanityDocument, options: ProcessingO
158159
const {typeMap} = options
159160
const types = extractWithPath('..[_type]', doc)
160161

161-
const typeName = getTypeName(doc._type)
162+
const typeName = getTypeName(doc._type, options.typePrefix)
162163
const thisType = typeMap.objects[typeName]
163164
if (!thisType) {
164165
return
@@ -178,7 +179,7 @@ function addInternalTypesToUnionFields(doc: SanityDocument, options: ProcessingO
178179

179180
const parentNode =
180181
type.path.length === parentOffset ? doc : get(doc, type.path.slice(0, -parentOffset))
181-
const parentTypeName = getTypeName(parentNode._type)
182+
const parentTypeName = getTypeName(parentNode._type, options.typePrefix)
182183
const parentType = typeMap.objects[parentTypeName]
183184

184185
if (!parentType) {
@@ -191,13 +192,13 @@ function addInternalTypesToUnionFields(doc: SanityDocument, options: ProcessingO
191192
continue
192193
}
193194

194-
const fieldTypeName = getTypeName(field.namedType.name.value)
195+
const fieldTypeName = getTypeName(field.namedType.name.value, options.typePrefix)
195196

196197
// All this was just to check if we're dealing with a union field
197198
if (!typeMap.unions[fieldTypeName]) {
198199
continue
199200
}
200-
const typeName = getTypeName(type.value)
201+
const typeName = getTypeName(type.value, options.typePrefix)
201202

202203
// Add the internal type to the field
203204
set(doc, type.path.slice(0, -1).concat('internal'), {type: typeName})

src/util/remoteGraphQLSchema.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export async function getRemoteGraphQLSchema(client: SanityClient, config: Plugi
7878
}
7979
}
8080

81-
export function getTypeMapFromGraphQLSchema(sdl: string): TypeMap {
81+
export function getTypeMapFromGraphQLSchema(sdl: string, typePrefix: string | undefined): TypeMap {
8282
const typeMap: TypeMap = {objects: {}, scalars: [], unions: {}}
8383
const remoteSchema = parse(sdl)
8484
const groups = {
@@ -100,7 +100,7 @@ export function getTypeMapFromGraphQLSchema(sdl: string): TypeMap {
100100
return acc
101101
}
102102

103-
const name = getTypeName(typeDef.name.value)
103+
const name = getTypeName(typeDef.name.value, typePrefix)
104104
acc[name] = {
105105
name,
106106
kind: 'Object',
@@ -158,10 +158,10 @@ export function getTypeMapFromGraphQLSchema(sdl: string): TypeMap {
158158

159159
const unions: {[key: string]: UnionTypeDef} = {}
160160
typeMap.unions = groups.UnionTypeDefinition.reduce((acc, typeDef: UnionTypeDefinitionNode) => {
161-
const name = getTypeName(typeDef.name.value)
161+
const name = getTypeName(typeDef.name.value, typePrefix)
162162
acc[name] = {
163163
name,
164-
types: (typeDef.types || []).map((type) => getTypeName(type.name.value)),
164+
types: (typeDef.types || []).map((type) => getTypeName(type.name.value, typePrefix)),
165165
}
166166
return acc
167167
}, unions)

0 commit comments

Comments
 (0)