Skip to content

Commit

Permalink
add source-store
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Sep 9, 2021
1 parent ec7567b commit 68dc9b5
Show file tree
Hide file tree
Showing 10 changed files with 404 additions and 0 deletions.
72 changes: 72 additions & 0 deletions packages/gatsby-source-store/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# gatsby-source-store

Plugin for sourcing the store-api data layer into Gatsby.

## Install
```
yarn add @vtex/gatsby-source-vtex @vtex/store-api
```

# How to use
```js
// In your gatsby-config.js
const { getSchema } = require('@vtex/store-api')

const options = {
platform: 'vtex',
account: 'my-vtex-account',
environment: 'vtexcommercestable'
}

module.exports = {
plugins: [
// other plugins ...
{
resolve: '@vtex/gatsby-source-store',
options: {
getSchema: () => getSchema(options)
}
},
],
}
```

# Options
This plugin brings additional options for you to control how the nodes are sourced.
| Options | type | effect |
|:-----------------:|:-------:|:-----------------------------------:|
| sourceProducts | boolean | false for not sourcing products |
| sourceCollections | boolean | false for not sourcing collections |
| maxNumProducts | number | max number of products to source |
| maxNumCollections | number | max number of collections to source |

> Tip: While sourcing large ecommerces, add the maxNumProducts and maxNumCollections so you can develop whithout waiting for the whole dataset to be downloaded. Maybe try something like:
```js
const { getSchema } = require('@vtex/store-api')

const options = {
platform: 'vtex',
account: 'my-vtex-account',
environment: 'vtexcommercestable'
}

const isProduction = process.env.NODE_ENV === 'production'

module.exports = {
plugins: [
// other plugins ...
{
resolve: '@vtex/gatsby-source-store',
options: {
getSchema: () => getSchema(options)
// Source less products is development for better DX
maxNumProducts: isProduction ? undefined : 100,
maxNumCollections: isProduction ? undefined : 100,
}
},
],
}
```

## How to contribute
Feel free to open issues in our repo. Also, there is a general contributing guideline in there
45 changes: 45 additions & 0 deletions packages/gatsby-source-store/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@vtex/gatsby-source-store",
"version": "1.1.1",
"description": "Gatsby source plugin for building websites using a FastStore compatible server",
"author": "Store Framework",
"license": "MIT",
"main": "dist/index.js",
"module": "dist/gatsby-source-store.esm.js",
"typings": "dist/index.d.ts",
"repository": {
"directory": "packages/gatsby-source-store",
"url": "https://github.com/vtex/faststore",
"type": "git"
},
"files": [
"dist",
"gatsby-*",
"index.js",
"src"
],
"engines": {
"node": ">=10"
},
"scripts": {
"develop": "tsdx watch",
"build": "tsdx build",
"test": "tsdx test",
"lint": "tsdx lint"
},
"peerDependencies": {
"gatsby": "^3.11.1",
"graphql": "^15.0.0"
},
"dependencies": {
"@graphql-tools/delegate": "^7.1.5",
"@graphql-tools/wrap": "^7.0.8",
"gatsby-graphql-source-toolkit": "^2.0.1"
},
"devDependencies": {
"gatsby": "^3.11.1",
"tsdx": "^0.14.1",
"tslib": "^2.3.1",
"typescript": "^4.3.3"
}
}
66 changes: 66 additions & 0 deletions packages/gatsby-source-store/src/gatsby-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { printSchema } from 'graphql'
import type {
CreateSchemaCustomizationArgs,
PluginOptionsSchemaArgs,
SourceNodesArgs,
} from 'gatsby'
import type { GraphQLSchema } from 'graphql'

import { sourceCollections } from './sourceCollections'
import { sourceProducts } from './sourceProducts'

export interface Options {
sourceProducts?: boolean
sourceCollections?: boolean
maxNumProducts?: number
maxNumCollections?: number
getSchema: () => Promise<GraphQLSchema>
}

export const pluginOptionsSchema = ({ Joi }: PluginOptionsSchemaArgs) =>
Joi.object({
sourceProducts: Joi.boolean(),
sourceCollections: Joi.boolean(),
maxNumProducts: Joi.number(),
maxNumCollections: Joi.number(),
getSchema: Joi.function().required(),
})

export const createSchemaCustomization = async (
gatsbyApi: CreateSchemaCustomizationArgs,
options: Options
) => {
const { actions } = gatsbyApi
const schema = await options.getSchema()
const typeDefs = printSchema(schema)
.replace('type StoreCollection {', 'type StoreCollection implements Node {')
.replace(`type StoreProduct {`, `type StoreProduct implements Node {`)
.replace(/(\w*)Connection/g, 'Browser$1Connection')

actions.createTypes(typeDefs)
}

export const sourceNodes = async (
gatsbyApi: SourceNodesArgs,
options: Options
) => {
const { reporter } = gatsbyApi
const lastBuildTime = await gatsbyApi.cache.get(`LAST_BUILD_TIME`)

await gatsbyApi.cache.set(`LAST_BUILD_TIME`, Date.now())

if (lastBuildTime) {
reporter.info(
'[gatsby-source-vtex]: CACHE FOUND! We are about to go FAST! Skipping FETCH'
)
} else {
reporter.info(
'[gatsby-source-vtex]: No cache found. Sourcing all data from scratch'
)
}

await Promise.all([
sourceProducts(gatsbyApi, options),
sourceCollections(gatsbyApi, options),
])
}
1 change: 1 addition & 0 deletions packages/gatsby-source-store/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as GatsbyNode from './gatsby-node'
46 changes: 46 additions & 0 deletions packages/gatsby-source-store/src/paginate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { IPaginationAdapter } from 'gatsby-graphql-source-toolkit'

const DEFAULT_PAGE_SIZE = 99

export interface IRelayPage {
edges: Array<{ cursor: string; node: unknown | null }>
pageInfo: { hasNextPage: boolean }
}

export const RelayForward = (
maxItems: number
): IPaginationAdapter<IRelayPage, unknown> => ({
name: 'RelayForward',
expectedVariableNames: [`first`, `after`],
start() {
return {
variables: { first: DEFAULT_PAGE_SIZE, after: undefined },
hasNextPage: true,
}
},
next(state, page) {
const tail = page.edges[page.edges.length - 1]
const first = Number(state.variables.first) ?? DEFAULT_PAGE_SIZE
const after = tail?.cursor

return {
variables: { first, after },
hasNextPage: Boolean(
page?.pageInfo?.hasNextPage && Number(after) < maxItems
),
}
},
concat(acc, page) {
return {
...acc,
edges: {
...acc.edges,
...page.edges,
},
pageInfo: page.pageInfo,
}
},
getItems(pageOrResult) {
return pageOrResult.edges.map((edge) => (edge ? edge.node : null))
},
})
37 changes: 37 additions & 0 deletions packages/gatsby-source-store/src/sourceCollections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { SourceNodesArgs } from 'gatsby'

import { sourceStoreType } from './sourceStore'
import type { Options } from './gatsby-node'

export const sourceCollections = async (
gatsbyApi: SourceNodesArgs,
options: Options
) => {
const { maxNumCollections = Infinity } = options
const gatsbyNodeTypes = [
{
remoteTypeName: `StoreCollection`,
queries: `
query LIST_COLLECTIONS($first: Int!, $after: String!) {
allCollections(first: $first, after: $after) {
edges {
node {
..._CollectionFragment_
}
cursor
}
pageInfo {
hasNextPage
}
}
}
fragment _CollectionFragment_ on StoreCollection {
id
__typename
}
`,
},
]

await sourceStoreType(gatsbyApi, options, gatsbyNodeTypes, maxNumCollections)
}
37 changes: 37 additions & 0 deletions packages/gatsby-source-store/src/sourceProducts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { SourceNodesArgs } from 'gatsby'

import { sourceStoreType } from './sourceStore'
import type { Options } from './gatsby-node'

export const sourceProducts = async (
gatsbyApi: SourceNodesArgs,
options: Options
) => {
const { maxNumProducts = Infinity } = options
const gatsbyNodeTypes = [
{
remoteTypeName: `StoreProduct`,
queries: `
query LIST_PRODUCTS($first: Int!, $after: String!) {
allProducts(first: $first, after: $after) {
edges {
node {
..._ProductFragment_
}
cursor
}
pageInfo {
hasNextPage
}
}
}
fragment _ProductFragment_ on StoreProduct {
id: productID
__typename
}
`,
},
]

await sourceStoreType(gatsbyApi, options, gatsbyNodeTypes, maxNumProducts)
}
64 changes: 64 additions & 0 deletions packages/gatsby-source-store/src/sourceStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
buildNodeDefinitions,
compileNodeQueries,
createSchemaCustomization as toolkitCreateSchemaCustomization,
generateDefaultFragments,
sourceAllNodes,
} from 'gatsby-graphql-source-toolkit'
import { execute, parse } from 'graphql'
import type { SourceNodesArgs } from 'gatsby'
import type {
IGatsbyNodeConfig,
ISourcingConfig,
} from 'gatsby-graphql-source-toolkit/dist/types'

import type { Options } from './gatsby-node'
import { RelayForward } from './paginate'

export const sourceStoreType = async (
gatsbyApi: SourceNodesArgs,
options: Options,
gatsbyNodeTypes: IGatsbyNodeConfig[],
maxItems: number
) => {
// Step1. Set up remote schema
const schema = await options.getSchema()

// Step3. Provide (or generate) fragments with fields to be fetched
const fragments = generateDefaultFragments({ schema, gatsbyNodeTypes })

// Step4. Compile sourcing queries
const documents = compileNodeQueries({
schema,
gatsbyNodeTypes,
customFragments: fragments,
})

const config: ISourcingConfig = {
gatsbyApi,
schema,
execute: async (args) => {
const response = await execute({
operationName: args.operationName,
document: parse(args.query),
variableValues: args.variables,
schema,
})

if (response.errors?.length) {
response.errors.forEach((e) => console.error(e))
}

return response
},
gatsbyTypePrefix: ``,
gatsbyNodeDefs: buildNodeDefinitions({ gatsbyNodeTypes, documents }),
paginationAdapters: [RelayForward(maxItems)],
}

// Step5. Add explicit types to gatsby schema
await toolkitCreateSchemaCustomization(config)

// Step6. Source nodes
await sourceAllNodes(config)
}
1 change: 1 addition & 0 deletions packages/gatsby-source-store/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test.todo('add tests')
Loading

0 comments on commit 68dc9b5

Please sign in to comment.