Skip to content

Commit

Permalink
add vtex resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Sep 9, 2021
1 parent abf1284 commit ec7567b
Show file tree
Hide file tree
Showing 34 changed files with 1,300 additions and 0 deletions.
41 changes: 41 additions & 0 deletions packages/store-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# @vtex/store-api

The only API you need for building your next ecommerce.

This package defines a front-end first, GraphQL API inspired by clean architecture and schema.org.

GraphQL types defined in this repo extends and simplifies schema.org. This makes it easier to make your frontend search friendly.
Also, by using the clean architecture, all types defined on this schema are not platform specific, but created to resolve an specific business case on your frontend, like rendering listprices, sellers etc.

Alongside the GraphQL type definitions, we provide standard implementations for common ecommerce platforms. Currently we support:
1. VTEX
2. Maybe add yours?

With the typedefs and resolvers, you can create an executable schema and deploy anywhere you want. For instance, one use case would be:
1. Create an Apollo Server instane on Heroku
2. Run the executable schema in a function on Next.JS
3. Run the executable schema during a Gatsby build.

## Install

```bash
yarn add @vtex/store-api
```

## Usage
GraphQL is very versatile and can run in many places. To setup the schema in an apollo server, just:
```ts
import { getSchema } from '@vtex/store-api'
import { ApolloServer } from 'apollo-server'

// Get the Store schema
const schema = await getSchema({ platform: 'vtex', account: 'my-account', environment: 'vtexcommercestable' })

// Setup Apollo Server
const server = new ApolloServer({ schema });

// The `listen` method launches a web server.
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
```
11 changes: 11 additions & 0 deletions packages/store-api/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// File created to resolve .graphql files.
// The main content was extracted from https://github.com/formium/tsdx/blob/master/src/createJestConfig.ts
// Copying some code was necessary because tsdx does shallow merges

module.exports = {
preset: 'ts-jest',
transform: {
'.(graphql)$': 'jest-transform-graphql',
'.(js|jsx)$': 'babel-jest', // jest's default
},
}
35 changes: 35 additions & 0 deletions packages/store-api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@vtex/store-api",
"version": "0.1.0",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"module": "dist/store-api.esm.js",
"files": [
"dist",
"src"
],
"engines": {
"node": ">=10"
},
"scripts": {
"develop": "tsdx watch",
"build": "tsdx build",
"test": "tsdx test",
"lint": "tsdx lint"
},
"peerDependencies": {},
"dependencies": {
"rollup-plugin-graphql": "^0.1.0",
"slugify": "^1.6.0",
"ts-jest": "25.5.1"
},
"devDependencies": {
"babel-jest": "^27.1.1",
"jest-transform-graphql": "^2.1.0",
"ts-jest": "^27.0.5",
"tsdx": "^0.14.1",
"tslib": "^2.3.1",
"typescript": "^4.4.2"
}
}
25 changes: 25 additions & 0 deletions packages/store-api/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { makeExecutableSchema } from '@graphql-tools/schema'

import { getResolvers as getResolversVTEX } from './platforms/vtex'
import { typeDefs } from './typeDefs'
import type { Options as OptionsVTEX } from './platforms/vtex'

export type Options = OptionsVTEX

const getResolversByPlatform = {
vtex: getResolversVTEX,
}

const getTypeDefs = async () => typeDefs

const getResolvers = (options: Options) =>
getResolversByPlatform[options.platform](options)

export const getSchema = async (options: Options) => {
const [resolvers, defs] = await Promise.all([
getResolvers(options),
getTypeDefs(),
])

return makeExecutableSchema({ resolvers, typeDefs: defs })
}
48 changes: 48 additions & 0 deletions packages/store-api/src/platforms/vtex/clients/commerce/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { fetchAPI } from '../common'
import type {
Simulation,
SimulationArgs,
SimulationOptions,
} from './types/Checkout'
import type { CategoryTree } from './types/CategoryTree'
import type { Options } from '../..'
import type { Brand } from './types/Brand'

const getBase = ({ account, environment }: Options) =>
`http://${account}.${environment}.com.br`

export const VtexCommerce = (opts: Options) => {
const base = getBase(opts)

return {
catalog: {
brand: {
list: (): Promise<Brand[]> =>
fetchAPI(`${base}/api/catalog_system/pub/brand/list`),
},
category: {
tree: (depth = 3): Promise<CategoryTree[]> =>
fetchAPI(`${base}/api/catalog_system/pub/category/tree/${depth}`),
},
},
checkout: {
simulation: (
args: SimulationArgs,
options: SimulationOptions = { sc: '1' }
): Promise<Simulation> => {
const params = new URLSearchParams({ ...options })

return fetchAPI(
`${base}/api/checkout/pub/orderForms/simulation?${params.toString()}`,
{
method: 'POST',
body: JSON.stringify(args),
headers: {
'content-type': 'application/json',
},
}
)
},
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Brand {
id: number
name: string
isActive: boolean
title: string
metaTagDescription: string
imageURL: null | string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface CategoryTree {
id: number
name: string
hasChildren: boolean
url: string
children: CategoryTree[]
Title: string
MetaTagDescription: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
export interface PayloadItem {
id: string
quantity: number
seller: string
parentItemIndex?: number | null
parentAssemblyBinding?: string | null
}

export interface ShippingData {
logisticsInfo?: Array<{ regionId?: string | null }>
}

export interface SimulationArgs {
country?: string
items: PayloadItem[]
postalCode?: string
isCheckedIn?: boolean
priceTables?: string[]
marketingData?: Record<string, string>
shippingData?: ShippingData
}

export interface SimulationOptions {
sc: string
}

export interface Simulation {
items: Item[]
ratesAndBenefitsData: RatesAndBenefitsData
paymentData: PaymentData
selectableGifts: any[]
marketingData: MarketingData
postalCode: null
country: null
logisticsInfo: LogisticsInfo[]
messages: any[]
purchaseConditions: PurchaseConditions
pickupPoints: any[]
subscriptionData: null
totals: Total[]
itemMetadata: null
}

export interface Item {
id: string
requestIndex: number
quantity: number
seller: string
sellerChain: string[]
tax: number
priceValidUntil: Date
price: number
listPrice: number
rewardValue: number
sellingPrice: number
offerings: any[]
priceTags: any[]
measurementUnit: string
unitMultiplier: number
parentItemIndex: null
parentAssemblyBinding: null
availability: string
catalogProvider: string
priceDefinition: PriceDefinition
}

export interface PriceDefinition {
calculatedSellingPrice: number
total: number
sellingPrices: SellingPrice[]
}

export interface SellingPrice {
value: number
quantity: number
}

export interface LogisticsInfo {
itemIndex: number
addressId: null
selectedSla: null
selectedDeliveryChannel: null
quantity: number
shipsTo: string[]
slas: any[]
deliveryChannels: DeliveryChannel[]
}

export interface DeliveryChannel {
id: string
}

export interface MarketingData {
utmSource: null
utmMedium: null
utmCampaign: null
utmipage: null
utmiPart: null
utmiCampaign: null
coupon: null
marketingTags: string[]
}

export interface PaymentData {
installmentOptions: InstallmentOption[]
paymentSystems: PaymentSystem[]
payments: any[]
giftCards: any[]
giftCardMessages: any[]
availableAccounts: any[]
availableTokens: any[]
}

export interface InstallmentOption {
paymentSystem: string
bin: null
paymentName: string
paymentGroupName: string
value: number
installments: Installment[]
}

export interface Installment {
count: number
hasInterestRate: boolean
interestRate: number
value: number
total: number
sellerMerchantInstallments?: Installment[]
id?: string
}

export interface PaymentSystem {
id: number
name: string
groupName: string
validator: null
stringId: string
template: string
requiresDocument: boolean
isCustom: boolean
description: null | string
requiresAuthentication: boolean
dueDate: Date
availablePayments: null
}

export interface PurchaseConditions {
itemPurchaseConditions: ItemPurchaseCondition[]
}

export interface ItemPurchaseCondition {
id: string
seller: string
sellerChain: string[]
slas: any[]
price: number
listPrice: number
}

export interface RatesAndBenefitsData {
rateAndBenefitsIdentifiers: any[]
teaser: any[]
}

export interface Total {
id: string
name: string
value: number
}
14 changes: 14 additions & 0 deletions packages/store-api/src/platforms/vtex/clients/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import fetch from 'isomorphic-unfetch'

export const fetchAPI = async (info: RequestInfo, init?: RequestInit) => {
const response = await fetch(info, init)

if (response.ok) {
return response.json()
}

const text = await response.text()

console.error(text)
throw new Error(text)
}
15 changes: 15 additions & 0 deletions packages/store-api/src/platforms/vtex/clients/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { VtexCommerce } from './commerce'
import { IntelligentSearch } from './search'
import type { Options } from '..'

export type Clients = ReturnType<typeof getClients>

export const getClients = (options: Options) => {
const search = IntelligentSearch(options)
const commerce = VtexCommerce(options)

return {
search,
commerce,
}
}
Loading

0 comments on commit ec7567b

Please sign in to comment.