From 2e25cc2b2aec46de7d5a5a8a8593e920f5bf3056 Mon Sep 17 00:00:00 2001 From: Tiago Gimenes Date: Thu, 9 Sep 2021 10:24:23 -0300 Subject: [PATCH] add source-store --- packages/gatsby-source-store/README.md | 147 +++++++----------- packages/gatsby-source-store/package.json | 5 +- .../gatsby-source-store/src/gatsby-node.ts | 25 ++- packages/gatsby-source-store/src/paginate.ts | 46 ++++++ .../src/sourceCollections.ts | 34 +++- .../gatsby-source-store/src/sourceProducts.ts | 59 ++----- .../gatsby-source-store/src/sourceStore.ts | 64 ++++++++ .../gatsby-source-store/test/blah.test.ts | 7 - .../gatsby-source-store/test/index.test.ts | 1 + 9 files changed, 243 insertions(+), 145 deletions(-) create mode 100644 packages/gatsby-source-store/src/paginate.ts create mode 100644 packages/gatsby-source-store/src/sourceStore.ts delete mode 100644 packages/gatsby-source-store/test/blah.test.ts create mode 100644 packages/gatsby-source-store/test/index.test.ts diff --git a/packages/gatsby-source-store/README.md b/packages/gatsby-source-store/README.md index 93eb55df4e..4d78378854 100644 --- a/packages/gatsby-source-store/README.md +++ b/packages/gatsby-source-store/README.md @@ -1,103 +1,72 @@ -# TSDX User Guide +# gatsby-source-store -Congrats! You just saved yourself hours of work by bootstrapping this project with TSDX. Let’s get you oriented with what’s here and how to use it. +Plugin for sourcing the store-api data layer into Gatsby. -> This TSDX setup is meant for developing libraries (not apps!) that can be published to NPM. If you’re looking to build a Node app, you could use `ts-node-dev`, plain `ts-node`, or simple `tsc`. - -> If you’re new to TypeScript, checkout [this handy cheatsheet](https://devhints.io/typescript) - -## Commands - -TSDX scaffolds your new library inside `/src`. - -To run TSDX, use: - -```bash -npm start # or yarn start +## Install ``` - -This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`. - -To do a one-off build, use `npm run build` or `yarn build`. - -To run tests, use `npm test` or `yarn test`. - -## Configuration - -Code quality is set up for you with `prettier`, `husky`, and `lint-staged`. Adjust the respective fields in `package.json` accordingly. - -### Jest - -Jest tests are set up to run with `npm test` or `yarn test`. - -### Bundle Analysis - -[`size-limit`](https://github.com/ai/size-limit) is set up to calculate the real cost of your library with `npm run size` and visualize the bundle with `npm run analyze`. - -#### Setup Files - -This is the folder structure we set up for you: - -```txt -/src - index.tsx # EDIT THIS -/test - blah.test.tsx # EDIT THIS -.gitignore -package.json -README.md # EDIT THIS -tsconfig.json +yarn add @vtex/gatsby-source-vtex @vtex/store-api ``` -### Rollup - -TSDX uses [Rollup](https://rollupjs.org) as a bundler and generates multiple rollup configs for various module formats and build settings. See [Optimizations](#optimizations) for details. - -### TypeScript - -`tsconfig.json` is set up to interpret `dom` and `esnext` types, as well as `react` for `jsx`. Adjust according to your needs. - -## Continuous Integration - -### GitHub Actions - -Two actions are added by default: - -- `main` which installs deps w/ cache, lints, tests, and builds on all pushes against a Node and OS matrix -- `size` which comments cost comparison of your library on every pull request using [`size-limit`](https://github.com/ai/size-limit) - -## Optimizations - -Please see the main `tsdx` [optimizations docs](https://github.com/palmerhq/tsdx#optimizations). In particular, know that you can take advantage of development-only optimizations: - +# How to use ```js -// ./types/index.d.ts -declare var __DEV__: boolean; +// In your gatsby-config.js +const { getSchema } = require('@vtex/store-api') -// inside your code... -if (__DEV__) { - console.log('foo'); +const options = { + platform: 'vtex', + account: 'my-vtex-account', + environment: 'vtexcommercestable' } -``` - -You can also choose to install and use [invariant](https://github.com/palmerhq/tsdx#invariant) and [warning](https://github.com/palmerhq/tsdx#warning) functions. - -## Module Formats -CJS, ESModules, and UMD module formats are supported. - -The appropriate paths are configured in `package.json` and `dist/index.js` accordingly. Please report if any issues are found. - -## Named Exports - -Per Palmer Group guidelines, [always use named exports.](https://github.com/palmerhq/typescript#exports) Code split inside your React app instead of your React library. +module.exports = { + plugins: [ + // other plugins ... + { + resolve: '@vtex/gatsby-source-store', + options: { + getSchema: () => getSchema(options) + } + }, + ], +} +``` -## Including Styles +# 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 | -There are many ways to ship styles, including with CSS-in-JS. TSDX has no opinion on this, configure how you like. +> 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') -For vanilla CSS, you can include it at the root directory and add it to the `files` section in your `package.json`, so that it can be imported separately by your users and run through their bundler's loader. +const options = { + platform: 'vtex', + account: 'my-vtex-account', + environment: 'vtexcommercestable' +} -## Publishing to NPM +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, + } + }, + ], +} +``` -We recommend using [np](https://github.com/sindresorhus/np). +## How to contribute +Feel free to open issues in our repo. Also, there is a general contributing guideline in there diff --git a/packages/gatsby-source-store/package.json b/packages/gatsby-source-store/package.json index 8201a3be1a..1957039261 100644 --- a/packages/gatsby-source-store/package.json +++ b/packages/gatsby-source-store/package.json @@ -25,8 +25,7 @@ "develop": "tsdx watch", "build": "tsdx build", "test": "tsdx test", - "lint": "tsdx lint", - "prepare": "tsdx build" + "lint": "tsdx lint" }, "peerDependencies": { "gatsby": "^3.11.1", @@ -40,7 +39,7 @@ "devDependencies": { "gatsby": "^3.11.1", "tsdx": "^0.14.1", - "tslib": "^2.3.0", + "tslib": "^2.3.1", "typescript": "^4.3.3" } } diff --git a/packages/gatsby-source-store/src/gatsby-node.ts b/packages/gatsby-source-store/src/gatsby-node.ts index 62dd584f22..6abf9eb8f4 100644 --- a/packages/gatsby-source-store/src/gatsby-node.ts +++ b/packages/gatsby-source-store/src/gatsby-node.ts @@ -1,4 +1,9 @@ -import type { PluginOptionsSchemaArgs, SourceNodesArgs } from 'gatsby' +import { printSchema } from 'graphql' +import type { + CreateSchemaCustomizationArgs, + PluginOptionsSchemaArgs, + SourceNodesArgs, +} from 'gatsby' import type { GraphQLSchema } from 'graphql' import { sourceCollections } from './sourceCollections' @@ -7,6 +12,8 @@ import { sourceProducts } from './sourceProducts' export interface Options { sourceProducts?: boolean sourceCollections?: boolean + maxNumProducts?: number + maxNumCollections?: number getSchema: () => Promise } @@ -14,9 +21,25 @@ 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 diff --git a/packages/gatsby-source-store/src/paginate.ts b/packages/gatsby-source-store/src/paginate.ts new file mode 100644 index 0000000000..9e79a34845 --- /dev/null +++ b/packages/gatsby-source-store/src/paginate.ts @@ -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 => ({ + 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)) + }, +}) diff --git a/packages/gatsby-source-store/src/sourceCollections.ts b/packages/gatsby-source-store/src/sourceCollections.ts index 515730da87..f995052767 100644 --- a/packages/gatsby-source-store/src/sourceCollections.ts +++ b/packages/gatsby-source-store/src/sourceCollections.ts @@ -1,5 +1,37 @@ import type { SourceNodesArgs } from 'gatsby' +import { sourceStoreType } from './sourceStore' import type { Options } from './gatsby-node' -export const sourceCollections = async (_: SourceNodesArgs, __: Options) => {} +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) +} diff --git a/packages/gatsby-source-store/src/sourceProducts.ts b/packages/gatsby-source-store/src/sourceProducts.ts index 009e9a52a6..23785049f4 100644 --- a/packages/gatsby-source-store/src/sourceProducts.ts +++ b/packages/gatsby-source-store/src/sourceProducts.ts @@ -1,37 +1,31 @@ import type { SourceNodesArgs } from 'gatsby' -import { - buildNodeDefinitions, - compileNodeQueries, - generateDefaultFragments, - createSchemaCustomization as toolkitCreateSchemaCustomization, - sourceAllNodes, -} from 'gatsby-graphql-source-toolkit' -import { execute, parse } from 'graphql' -import type { ISourcingConfig } from 'gatsby-graphql-source-toolkit/dist/types' +import { sourceStoreType } from './sourceStore' import type { Options } from './gatsby-node' export const sourceProducts = async ( gatsbyApi: SourceNodesArgs, options: Options ) => { - // Step1. Set up remote schema: - const schema = await options.getSchema() - - // Step2. Configure Gatsby node types + const { maxNumProducts = Infinity } = options const gatsbyNodeTypes = [ { - remoteTypeName: `Product`, + remoteTypeName: `StoreProduct`, queries: ` - # Write your query or mutation here - query LIST_PRODUCTS($first: number, $after: number) { - allProducts { - products { - ..._ProductFragment_ + query LIST_PRODUCTS($first: Int!, $after: String!) { + allProducts(first: $first, after: $after) { + edges { + node { + ..._ProductFragment_ + } + cursor + } + pageInfo { + hasNextPage } } } - fragment _ProductFragment_ on Product { + fragment _ProductFragment_ on StoreProduct { id: productID __typename } @@ -39,28 +33,5 @@ export const sourceProducts = async ( }, ] - // 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) => - execute({ ...args, document: parse(args.query), schema }), - gatsbyTypePrefix: ``, - gatsbyNodeDefs: buildNodeDefinitions({ gatsbyNodeTypes, documents }), - } - - // Step5. Add explicit types to gatsby schema - await toolkitCreateSchemaCustomization(config) - - // Step6. Source nodes - await sourceAllNodes(config) + await sourceStoreType(gatsbyApi, options, gatsbyNodeTypes, maxNumProducts) } diff --git a/packages/gatsby-source-store/src/sourceStore.ts b/packages/gatsby-source-store/src/sourceStore.ts new file mode 100644 index 0000000000..276712e125 --- /dev/null +++ b/packages/gatsby-source-store/src/sourceStore.ts @@ -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) +} diff --git a/packages/gatsby-source-store/test/blah.test.ts b/packages/gatsby-source-store/test/blah.test.ts deleted file mode 100644 index 0493050c9b..0000000000 --- a/packages/gatsby-source-store/test/blah.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { sum } from '../src' - -describe('blah', () => { - it('works', () => { - expect(sum(1, 1)).toEqual(2) - }) -}) diff --git a/packages/gatsby-source-store/test/index.test.ts b/packages/gatsby-source-store/test/index.test.ts new file mode 100644 index 0000000000..616d2bda81 --- /dev/null +++ b/packages/gatsby-source-store/test/index.test.ts @@ -0,0 +1 @@ +test.todo('add tests')