added appibase provider

This commit is contained in:
rafik mrabet 2022-03-30 18:33:32 +01:00
parent 52b25b85d6
commit 244ad904f7
66 changed files with 1496 additions and 3 deletions

View File

@ -0,0 +1 @@
COMMERCE_PROVIDER=appibase

View File

@ -0,0 +1,2 @@
node_modules
dist

View File

@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false
}

View File

@ -0,0 +1 @@
# Next.js Local Provider

18
packages/appibase/package-lock.json generated Normal file
View File

@ -0,0 +1,18 @@
{
"name": "appibase",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"js-cookies": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/js-cookies/-/js-cookies-1.0.4.tgz",
"integrity": "sha1-1G5XbEIP9tVULA9SttTvfWN+dU4="
},
"urijs": {
"version": "1.19.10",
"resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.10.tgz",
"integrity": "sha512-EzauQlgKuJgsXOqoMrCiePBf4At5jVqRhXykF3Wfb8ZsOBMxPcfiVBcsHXug4Aepb/ICm2PIgqAUGMelgdrWEg=="
}
}
}

View File

@ -0,0 +1,84 @@
{
"name": "appibase",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"release": "taskr release",
"build": "taskr build",
"dev": "taskr",
"types": "tsc --emitDeclarationOnly",
"prettier-fix": "prettier --write ."
},
"sideEffects": false,
"type": "module",
"exports": {
".": "./dist/index.js",
"./*": [
"./dist/*.js",
"./dist/*/index.js"
],
"./next.config": "./dist/next.config.cjs"
},
"typesVersions": {
"*": {
"*": [
"src/*",
"src/*/index"
],
"next.config": [
"dist/next.config.d.cts"
]
}
},
"files": [
"dist"
],
"publishConfig": {
"typesVersions": {
"*": {
"*": [
"dist/*.d.ts",
"dist/*/index.d.ts"
],
"next.config": [
"dist/next.config.d.cts"
]
}
}
},
"dependencies": {
"@vercel/commerce": "^0.0.1",
"@vercel/fetch": "^6.1.1",
"deserialize-json-api": "^1.4.0",
"js-cookies": "^1.0.4",
"json-api-response-converter": "^1.6.0",
"kitsu": "^10.0.0-alpha.22",
"urijs": "^1.19.10"
},
"peerDependencies": {
"next": "^12",
"react": "^17",
"react-dom": "^17"
},
"devDependencies": {
"@taskr/clear": "^1.1.0",
"@taskr/esnext": "^1.1.0",
"@taskr/watch": "^1.1.0",
"@types/node": "^17.0.8",
"@types/react": "^17.0.38",
"lint-staged": "^12.1.7",
"next": "^12.0.8",
"prettier": "^2.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"taskr": "^1.1.0",
"taskr-swc": "^0.0.1",
"typescript": "^4.5.4"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx,json}": [
"prettier --write",
"git add"
]
}
}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@ -0,0 +1,43 @@
import type { CommerceAPI, CommerceAPIConfig } from '@vercel/commerce/api'
import { getCommerceApi as commerceApi } from '@vercel/commerce/api'
import createFetcher from './utils/fetch-appibase'
import { API_URL } from '../const'
import getAllPages from './operations/get-all-pages'
import getPage from './operations/get-page'
import getSiteInfo from './operations/get-site-info'
import getCustomerWishlist from './operations/get-customer-wishlist'
import getAllProductPaths from './operations/get-all-product-paths'
import getAllProducts from './operations/get-all-products'
import getProduct from './operations/get-product'
export interface LocalConfig extends CommerceAPIConfig {}
const config: LocalConfig = {
commerceUrl: API_URL,
apiToken: '',
cartCookie: '',
customerCookie: '',
cartCookieMaxAge: 2592000,
fetch: createFetcher(() => getCommerceApi().getConfig()),
}
const operations = {
getAllPages,
getPage,
getSiteInfo,
getCustomerWishlist,
getAllProductPaths,
getAllProducts,
getProduct,
}
export const provider = { config, operations }
export type Provider = typeof provider
export type LocalAPI<P extends Provider = Provider> = CommerceAPI<P | any>
export function getCommerceApi<P extends Provider>(
customProvider: P = provider as any
): LocalAPI<P> {
return commerceApi(customProvider as any)
}

View File

@ -0,0 +1,21 @@
export type Page = { url: string }
export type GetAllPagesResult = { pages: Page[] }
import type { LocalConfig } from '../index'
export default function getAllPagesOperation() {
function getAllPages({
config,
preview,
}: {
url?: string
config?: Partial<LocalConfig>
preview?: boolean
}): Promise<GetAllPagesResult> {
console.log('GETTING ALL PAGES');
return Promise.resolve({
pages: [],
})
}
return getAllPages
}

View File

@ -0,0 +1,17 @@
import data from '../../data.json'
export type GetAllProductPathsResult = {
products: Array<{ path: string }>
}
export default function getAllProductPathsOperation() {
function getAllProductPaths(): Promise<GetAllProductPathsResult> {
console.log('GETTING ALL PATHS');
return Promise.resolve({
products: [] //data.products.map(({ path }) => ({ path })),
})
}
return getAllProductPaths
}

View File

@ -0,0 +1,33 @@
import { Product } from '@vercel/commerce/types/product'
import { GetAllProductsOperation } from '@vercel/commerce/types/product'
import type { OperationContext } from '@vercel/commerce/api/operations'
import type { LocalConfig, Provider } from '../index'
import { NormalizeProduct } from '../utils/normalize'
import type { AppibaseProduct } from '../../types'
export default function getAllProductsOperation({
commerce,
}: OperationContext<any>) {
async function getAllProducts<T extends GetAllProductsOperation>({
query = '',
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<LocalConfig>
preview?: boolean
} = {}): Promise<{ products: Product[] | any[] }> {
const { fetch } = commerce.getConfig(config)
const { data: fetchedProducts } = await fetch('/products?filter[is_parent_true]=true&include=prices,options');
const products = fetchedProducts.map((p : AppibaseProduct) => <Product> NormalizeProduct(p))
return {
products
}
}
return getAllProducts
}

View File

@ -0,0 +1,6 @@
export default function getCustomerWishlistOperation() {
function getCustomerWishlist(): any {
return { wishlist: {} }
}
return getCustomerWishlist
}

View File

@ -0,0 +1,15 @@
export type Page = any
export type GetPageResult = { page?: Page }
export type PageVariables = {
id: number
}
export default function getPageOperation() {
function getPage(): Promise<GetPageResult> {
console.log('GETTING PAGE');
return Promise.resolve({})
}
return getPage
}

View File

@ -0,0 +1,29 @@
import type { LocalConfig } from '../index'
import { Product } from '@vercel/commerce/types/product'
import { GetProductOperation } from '@vercel/commerce/types/product'
import type { OperationContext } from '@vercel/commerce/api/operations'
import { NormalizeProduct } from '../utils/normalize'
export default function getProductOperation({
commerce,
}: OperationContext<any>) {
async function getProduct<T extends GetProductOperation>({
query = '',
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<LocalConfig>
preview?: boolean
} = {}): Promise<Product | {} | any> {
const { fetch } = commerce.getConfig(config)
const { data: fetchedProduct } = await fetch(`/products/${variables?.slug}?include=products,variations,options,prices,stock_items,variations.variation_options`);
return {
product: NormalizeProduct(fetchedProduct),
}
}
return getProduct
}

View File

@ -0,0 +1,38 @@
import { OperationContext } from '@vercel/commerce/api/operations'
import { Category } from '@vercel/commerce/types/site'
import { NormalizeCategory } from '../utils/normalize'
import { LocalConfig } from '../index'
import { AppibaseCollection } from '../../types'
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {
categories: Category[]
brands: any[]
}
> = T
export default function getSiteInfoOperation({ commerce }: OperationContext<any>) {
async function getSiteInfo({
query,
variables,
config,
}: {
query?: string
variables?: any
config?: Partial<LocalConfig>
preview?: boolean
} = {}): Promise<GetSiteInfoResult> {
const { fetch } = commerce.getConfig(config)
const { res : { data } } = await fetch('/collections');
const categories = data.map((p : AppibaseCollection) => <Category> NormalizeCategory(p))
return Promise.resolve({
categories,
brands: [],
})
}
return getSiteInfo
}

View File

@ -0,0 +1,6 @@
export { default as getPage } from './get-page'
export { default as getSiteInfo } from './get-site-info'
export { default as getAllPages } from './get-all-pages'
export { default as getProduct } from './get-product'
export { default as getAllProducts } from './get-all-products'
export { default as getAllProductPaths } from './get-all-product-paths'

View File

@ -0,0 +1,35 @@
import Cookies from 'js-cookie'
import Kitsu from "kitsu";
import { API_URL, SCOPE, CLIENT_ID_STOREFRONT } from '../../const'
const GetAccessToken = async () => {
const fromCookies = Cookies.get('access_token');
if(!fromCookies) {
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Authorization", `Basic ${Buffer.from(CLIENT_ID_STOREFRONT).toString('base64')}`);
myHeaders.append("Accept", "application/json");
const urlencoded = new URLSearchParams();
urlencoded.append("grant_type", "client_credentials");
urlencoded.append("scope", SCOPE);
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: urlencoded,
};
const response = await fetch(API_URL + "/oauth/token", requestOptions)
const { access_token, expires_in } = await response.json()
Cookies.set('access_token', access_token, { expires: expires_in / 60 / 60 / 24 })
return access_token
}
else return fromCookies
}
export { GetAccessToken }

View File

@ -0,0 +1,28 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
import type { GraphQLFetcher } from '@vercel/commerce/api'
import Kitsu from "kitsu";
import type { LocalConfig } from '../index'
import { API_URL } from '../../const'
import { GetAccessToken } from './access-token'
const fetchApi: (getConfig: () => LocalConfig) => GraphQLFetcher =
(getConfig) =>
async (query: string, { variables, preview } = {}, fetchOptions) => {
const config = getConfig()
const api = new Kitsu({ baseURL: API_URL + '/api/v1' })
api.headers.Authorization = `Bearer ${await GetAccessToken()}`
const res = await api.get(query)
if (res.errors) {
throw new FetcherError({
errors: res.errors ?? [{ message: 'Failed to fetch for API' }],
status: res.status,
})
}
return { data: res.data, res }
}
export default fetchApi

View File

@ -0,0 +1,3 @@
import zeitFetch from '@vercel/fetch'
export default zeitFetch()

View File

@ -0,0 +1,82 @@
import { Product, ProductOption, ProductImage } from '@vercel/commerce/types/product'
import { Category } from '@vercel/commerce/types/site'
import { Cart, LineItem } from '@vercel/commerce/types/cart'
import type { AppibaseProduct, AppibaseCollection, AppibaseCart } from '../../types'
const NormalizeProduct = (product: AppibaseProduct): Product => {
const options: ProductOption[] = [];
for(const variation of (product.variations?.data || [])) {
const option : ProductOption | undefined = options.find(o => o.displayName === variation.name);
if(!option) {
options.push({
id: `option-${variation.name.toLowerCase()}`,
displayName : variation.name, values: variation.options?.map(o => ({ label: o.name })) || []
});
}
}
return {
id: product.id,
name: product.name,
description: product.description,
images: product.image_urls.map(i => <ProductImage> { url: i }),
sku: product.sku,
slug: product.sku,
variants: product.children?.data?.map(p => ({
id: p.id,
options: p.variation_options?.data.map(o => ({
__typename: "MultipleChoiceOption",
id: o.id,
displayName: o.variation_name || "",
values: [{ label: o.name || "" }]
})) || []
})) || [],
price: { value: product.prices.data[0].amount.float, currencyCode: product.prices.data[0].currency },
options
}
}
const NormalizeCategory = (collection: AppibaseCollection): Category => {
return {
id: String(collection.id),
name: collection.name,
slug: collection.slug,
path: '/' + collection.slug,
}
}
const NormalizeCart = (cart: AppibaseCart): Cart => {
return {
id: String(cart.id),
createdAt: (new Date()).toDateString(),
currency: {
code: cart.currency
},
taxesIncluded: cart.tax_incl,
lineItemsSubtotalPrice: cart.subtotal_amount.float,
subtotalPrice: cart.subtotal_amount.float,
totalPrice: cart.total_amount.float,
lineItems: cart.cart_items?.data.map(i => ({
id: i.id,
variantId: i.id,
productId: i.id,
name: i.name,
discounts: [],
path: i.sku,
variant: {
sku: i.sku,
id: i.id,
name: i.name,
requiresShipping: true,
price: i.price.float,
listPrice: i.price.float,
image: { url: i.image_url }
},
quantity: parseInt(i.quantity)
})) || []
}
}
export { NormalizeProduct, NormalizeCategory, NormalizeCart }

View File

@ -0,0 +1,3 @@
export { default as useLogin } from './use-login'
export { default as useLogout } from './use-logout'
export { default as useSignup } from './use-signup'

View File

@ -0,0 +1,16 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login'
export default useLogin as UseLogin<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook: () => () => {
return async function () {}
},
}

View File

@ -0,0 +1,17 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout'
export default useLogout as UseLogout<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook:
({ fetch }) =>
() =>
async () => {},
}

View File

@ -0,0 +1,19 @@
import { useCallback } from 'react'
import useCustomer from '../customer/use-customer'
import { MutationHook } from '@vercel/commerce/utils/types'
import useSignup, { UseSignup } from '@vercel/commerce/auth/use-signup'
export default useSignup as UseSignup<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook:
({ fetch }) =>
() =>
() => {},
}

View File

@ -0,0 +1,4 @@
export { default as useCart } from './use-cart'
export { default as useAddItem } from './use-add-item'
export { default as useRemoveItem } from './use-remove-item'
export { default as useUpdateItem } from './use-update-item'

View File

@ -0,0 +1,39 @@
import { useCallback } from 'react';
import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item'
import { MutationHook } from '@vercel/commerce/utils/types'
import Cookies from 'js-cookie'
import { NormalizeCart } from '../api/utils/normalize'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input: item, options, fetch }) {
const fromCookies = Cookies.get('cart_id');
await fetch({
query : `/carts/${fromCookies}/cart_items`,
method: 'POST',
body: {
quantity: 1,
item: {
data : {
type: 'product',
id: item.variantId
}
}
}
})
return;
},
useHook:
({ fetch }) =>
() => {
return useCallback(async function addItem(input) {
const data = await fetch({ input });
return data;
}, [fetch]);
},
}

View File

@ -0,0 +1,51 @@
import { useMemo } from 'react'
import { SWRHook } from '@vercel/commerce/utils/types'
import useCart, { UseCart } from '@vercel/commerce/cart/use-cart'
import Cookies from 'js-cookie'
import { NormalizeCart } from '../api/utils/normalize'
import { STORE_ID } from '../const'
export default useCart as UseCart<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: ''
},
async fetcher({ fetch }) {
const fromCookies = Cookies.get('cart_id');
if(!fromCookies) {
const { data } = await fetch({ query : `/stores/${STORE_ID}/carts`, method: "POST", body: { id: '' } })
Cookies.set('cart_id', data.id)
return NormalizeCart(data)
}
else {
const { data } = await fetch({ query : `/carts/${fromCookies}?include=cart_items` })
return NormalizeCart(data)
}
},
useHook:
({ useData }) =>
(input) => {
const response = useData({
swrOptions: { revalidateOnFocus: true, ...input?.swrOptions },
})
return useMemo(
() =>
Object.create(response, {
isEmpty: {
get() {
return (response.data?.lineItems.length ?? 0) <= 0
},
enumerable: true,
},
}),
[response]
)
},
}

View File

@ -0,0 +1,40 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import { useCallback } from 'react'
import useRemoveItem, {
UseRemoveItem,
} from '@vercel/commerce/cart/use-remove-item'
import Cookies from 'js-cookie'
import { NormalizeCart } from '../api/utils/normalize'
import useCart from './use-cart'
export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input: item, options, fetch }) {
await fetch({
query : `cart_items`,
method: 'DELETE',
body: item.id
})
const fromCookies = Cookies.get('cart_id');
const { data } = await fetch({ query : `/carts/${fromCookies}?include=cart_items` })
return NormalizeCart(data)
},
useHook:
({ fetch }) =>
() => {
const { mutate } = useCart()
return useCallback(async function removeItem(input) {
const data = await fetch({ input });
await mutate(data, true)
return data;
}, [fetch, mutate]);
},
}

View File

@ -0,0 +1,45 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import { useCallback } from 'react'
import useUpdateItem, {
UseUpdateItem,
} from '@vercel/commerce/cart/use-update-item'
import { NormalizeCart } from '../api/utils/normalize'
import Cookies from 'js-cookie'
import useCart from './use-cart'
export default useUpdateItem as UseUpdateItem<any>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {
await fetch({
query : `cart_items`,
method: 'PATCH',
body: {
id: input.item.id,
type: 'cart_item',
quantity: String(input.quantity)
}
})
const fromCookies = Cookies.get('cart_id');
const { data } = await fetch({ query : `/carts/${fromCookies}?include=cart_items` })
return NormalizeCart(data)
},
useHook:
({ fetch }) =>
({ item }) => {
const { mutate } = useCart()
return useCallback(async function updateItem(input) {
const data = await fetch({ input: { item, quantity: input.quantity } });
await mutate(data, true)
return data;
}, [fetch, mutate]);
},
}

View File

@ -0,0 +1,16 @@
import { SWRHook } from '@vercel/commerce/utils/types'
import useCheckout, {
UseCheckout,
} from '@vercel/commerce/checkout/use-checkout'
export default useCheckout as UseCheckout<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ useData }) =>
async (input) => ({}),
}

View File

@ -0,0 +1,10 @@
{
"provider": "appibase",
"features": {
"wishlist": false,
"cart": true,
"search": true,
"customerAuth": false,
"customCheckout": true
}
}

View File

@ -0,0 +1,7 @@
export const API_URL = `https://appibase.com`
export const SCOPE = `storefront client`
export const CLIENT_ID_STOREFRONT = process.env.APPIBASE_CLIENT_ID_STOREFRONT
export const STORE_ID = process.env.APPIBASE_STORE_ID

View File

@ -0,0 +1,17 @@
import useAddItem, {
UseAddItem,
} from '@vercel/commerce/customer/address/use-add-item'
import { MutationHook } from '@vercel/commerce/utils/types'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() =>
async () => ({}),
}

View File

@ -0,0 +1,17 @@
import useAddItem, {
UseAddItem,
} from '@vercel/commerce/customer/card/use-add-item'
import { MutationHook } from '@vercel/commerce/utils/types'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() =>
async () => ({}),
}

View File

@ -0,0 +1 @@
export { default as useCustomer } from './use-customer'

View File

@ -0,0 +1,17 @@
import { SWRHook } from '@vercel/commerce/utils/types'
import useCustomer, {
UseCustomer,
} from '@vercel/commerce/customer/use-customer'
export default useCustomer as UseCustomer<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook: () => () => {
return async function addItem() {
return {}
}
},
}

View File

@ -0,0 +1,235 @@
{
"products": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjA=",
"name": "New Short Sleeve T-Shirt",
"vendor": "Next.js",
"path": "/new-short-sleeve-t-shirt",
"slug": "new-short-sleeve-t-shirt",
"price": { "value": 25, "currencyCode": "USD" },
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [
{
"url": "/assets/drop-shirt-0.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/drop-shirt-1.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/drop-shirt-2.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
}
],
"variants": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"options": [
{
"__typename": "MultipleChoiceOption",
"id": "asd",
"displayName": "Size",
"values": [
{
"label": "XL"
}
]
}
]
}
],
"options": [
{
"id": "option-color",
"displayName": "Color",
"values": [
{
"label": "color",
"hexColors": ["#222"]
}
]
},
{
"id": "option-size",
"displayName": "Size",
"values": [
{
"label": "S"
},
{
"label": "M"
},
{
"label": "L"
}
]
}
]
},
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9ksdWN0LzU0NDczMjUwMjQ0MjA=",
"name": "Lightweight Jacket",
"vendor": "Next.js",
"path": "/lightweight-jacket",
"slug": "lightweight-jacket",
"price": { "value": 249.99, "currencyCode": "USD" },
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [
{
"url": "/assets/lightweight-jacket-0.png",
"altText": "Lightweight Jacket",
"width": 1000,
"height": 1000
},
{
"url": "/assets/lightweight-jacket-1.png",
"altText": "Lightweight Jacket",
"width": 1000,
"height": 1000
},
{
"url": "/assets/lightweight-jacket-2.png",
"altText": "Lightweight Jacket",
"width": 1000,
"height": 1000
}
],
"variants": [
{
"id": "Z2lkOid8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"options": [
{
"__typename": "MultipleChoiceOption",
"id": "asd",
"displayName": "Size",
"values": [
{
"label": "XL"
}
]
}
]
}
],
"options": [
{
"id": "option-color",
"displayName": "Color",
"values": [
{
"label": "color",
"hexColors": ["#222"]
}
]
},
{
"id": "option-size",
"displayName": "Size",
"values": [
{
"label": "S"
},
{
"label": "M"
},
{
"label": "L"
}
]
}
]
},
{
"id": "Z2lkOis8vc2hvcGlmsddeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjA=",
"name": "Shirt",
"vendor": "Next.js",
"path": "/shirt",
"slug": "shirt",
"price": { "value": 25, "currencyCode": "USD" },
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [
{
"url": "/assets/t-shirt-0.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-1.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-2.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-3.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
},
{
"url": "/assets/t-shirt-4.png",
"altText": "Shirt",
"width": 1000,
"height": 1000
}
],
"variants": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcms9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"options": [
{
"__typename": "MultipleChoiceOption",
"id": "asd",
"displayName": "Size",
"values": [
{
"label": "XL"
}
]
}
]
}
],
"options": [
{
"id": "option-color",
"displayName": "Color",
"values": [
{
"label": "color",
"hexColors": ["#222"]
}
]
},
{
"id": "option-size",
"displayName": "Size",
"values": [
{
"label": "S"
},
{
"label": "M"
},
{
"label": "L"
}
]
}
]
}
]
}

View File

@ -0,0 +1,25 @@
import { Fetcher } from '@vercel/commerce/utils/types'
import { API_URL } from './const'
import { handleFetchResponse } from './utils'
import { GetAccessToken } from './api/utils/access-token'
import Kitsu from "kitsu";
const fetcher: Fetcher = async ({
method = 'GET',
variables,
query = '',
body
}) => {
const { locale, ...vars } = variables ?? {}
const api = new Kitsu({ baseURL: API_URL + '/api/v1', camelCaseTypes: false, pluralize: false })
api.headers.Authorization = `Bearer ${await GetAccessToken()}`
if(method === 'GET') return await api.get(query)
else if(method === 'POST') return await api.create(query, body)
else if(method === 'DELETE') return await api.remove(query, body)
else if(method === 'PATCH') return await api.update(query, body)
}
export default fetcher

View File

@ -0,0 +1,12 @@
import {
getCommerceProvider,
useCommerce as useCoreCommerce,
} from '@vercel/commerce'
import { appibaseProvider, AppibaseProvider } from './provider'
export { appibaseProvider }
export type { AppibaseProvider }
export const CommerceProvider = getCommerceProvider(appibaseProvider)
export const useCommerce = () => useCoreCommerce<AppibaseProvider>()

View File

@ -0,0 +1,8 @@
const commerce = require('./commerce.config.json')
module.exports = {
commerce,
images: {
domains: ['localhost', 'shopify.vercel.store', 'vercel.store', 'demo.vercel.store'],
},
}

View File

@ -0,0 +1,2 @@
export { default as usePrice } from './use-price'
export { default as useSearch } from './use-search'

View File

@ -0,0 +1,2 @@
export * from '@vercel/commerce/product/use-price'
export { default } from '@vercel/commerce/product/use-price'

View File

@ -0,0 +1,54 @@
import { SWRHook } from '@vercel/commerce/utils/types'
import useSearch, { UseSearch } from '@vercel/commerce/product/use-search'
export default useSearch as UseSearch<typeof handler>
import type { AppibaseProduct } from '../types'
import { NormalizeProduct } from '../api/utils/normalize'
export type SearchProductsInput = {
search?: string
categoryId?: number | string
brandId?: number
sort?: string
locale?: string
}
export const handler: SWRHook<any> = {
fetchOptions: {
url: '/products?filter[is_parent_true]=true&include=prices',
method: 'GET',
},
async fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
let url = options.url
if (search) url += `&filter[name_cont_all]=${search}`
if (Number.isInteger(Number(categoryId)))
url += `&filter[collections_id_eq]=${categoryId}`
// if (Number.isInteger(brandId))
// url.searchParams.set('brandId', String(brandId))
// if (sort) url.searchParams.set('sort', sort)
const response = await fetch({
query: url,
method: options.method,
})
return { found: response.data.length, products: response.data.map((p : AppibaseProduct) => NormalizeProduct(p) ) }
},
useHook:
({ useData }) =>
(input = {}) => {
return useData({
input: [
['search', input.search],
['categoryId', input.categoryId],
['brandId', input.brandId],
['sort', input.sort],
],
swrOptions: {
revalidateOnFocus: false,
...input.swrOptions,
},
})
},
}

View File

@ -0,0 +1,22 @@
import fetcher from './fetcher'
import { handler as useCart } from './cart/use-cart'
import { handler as useAddItem } from './cart/use-add-item'
import { handler as useUpdateItem } from './cart/use-update-item'
import { handler as useRemoveItem } from './cart/use-remove-item'
import { handler as useCustomer } from './customer/use-customer'
import { handler as useSearch } from './product/use-search'
import { handler as useLogin } from './auth/use-login'
import { handler as useLogout } from './auth/use-logout'
import { handler as useSignup } from './auth/use-signup'
export const appibaseProvider = {
locale: 'en-us',
cartCookie: 'session',
fetcher: fetcher,
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
customer: { useCustomer },
products: { useSearch },
auth: { useLogin, useLogout, useSignup },
}
export type AppibaseProvider = typeof appibaseProvider

View File

@ -0,0 +1,132 @@
export type AppibaseVariation = {
id: string
name: string
options?: AppibaseVariationOption[]
}
export type AppibaseVariationOption = {
id: string
name: string
variation_name?: string
variation?: AppibaseVariation
}
export type PriceList = {
id: number
name: string
description?: string
currency: string
tax_incl: boolean
}
export type Amount = {
cents: number
float: number
formatted: string
}
export type AppibasePrices = {
data: AppibasePrice[]
}
export type AppibasePrice = {
id: number
currency: string
amount: Amount
original_amount: Amount
price_list?: PriceList
product?: AppibaseProduct
}
export type StockLocation = {
id: number
name: string
description?: string
}
export type StockItem = {
id: number
quantity: number
reserved: number
available: number
stock_location?: StockLocation
product?: AppibaseProduct
}
export type AppibaseVariations = {
data: AppibaseVariation[]
}
export type AppibaseProductChildren = {
data: AppibaseProduct[]
}
export type AppibaseVariationOptions = {
data: AppibaseVariationOption[]
}
export type AppibaseProduct = {
id: string
name: string
description: string
slug: string
sku: string
category: string
vendor: string
tags: string[]
image_urls: string[]
is_parent: boolean
active: boolean
livemode: boolean
parent?: AppibaseProduct
children?: AppibaseProductChildren
variations?: AppibaseVariations
variation_options?: AppibaseVariationOptions
prices: AppibasePrices
stock_items?: StockItem[]
}
export type AppibaseCollection = {
id: number
name: string
description: string
slug: string
image_url: string
is_parent: boolean
active: boolean
livemode: boolean
parent?: AppibaseCollection
children?: AppibaseCollection[]
products?: AppibaseProduct[]
}
export type AppibaseCartItems = {
data: AppibaseCartItem[]
}
export type AppibaseCart = {
id: string
guest: boolean
email: string
currency: string
tax_incl: boolean
tax_rate: number
subtotal_amount: Amount
shipping_amount: Amount
tax_amount: Amount
total_amount: Amount
cart_items: AppibaseCartItems
}
export type AppibaseCartItem = {
id: string
name: string
description: string
sku: string
image_url: string
quantity: string
currency: string
price: Amount
original_price: Amount
subtotal_amount: Amount
}

View File

@ -0,0 +1,27 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
export function getError(errors: any[] | null, status: number) {
errors = errors ?? [{ message: 'Failed to fetch Shopify API' }]
return new FetcherError({ errors, status })
}
export async function getAsyncError(res: Response) {
const data = await res.json()
return getError(data.errors, res.status)
}
const handleFetchResponse = async (res: Response) => {
if (res.ok) {
const { data, errors } = await res.json()
if (errors && errors.length) {
throw getError(errors, res.status)
}
return data
}
throw await getAsyncError(res)
}
export default handleFetchResponse

View File

@ -0,0 +1 @@
export { default as handleFetchResponse } from './handle-fetch-response'

View File

@ -0,0 +1,13 @@
import { useCallback } from 'react'
export function emptyHook() {
const useEmptyHook = async (options = {}) => {
return useCallback(async function () {
return Promise.resolve()
}, [])
}
return useEmptyHook
}
export default emptyHook

View File

@ -0,0 +1,17 @@
import { useCallback } from 'react'
type Options = {
includeProducts?: boolean
}
export function emptyHook(options?: Options) {
const useEmptyHook = async ({ id }: { id: string | number }) => {
return useCallback(async function () {
return Promise.resolve()
}, [])
}
return useEmptyHook
}
export default emptyHook

View File

@ -0,0 +1,43 @@
import { HookFetcher } from '@vercel/commerce/utils/types'
import type { Product } from '@vercel/commerce/types/product'
const defaultOpts = {}
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: Product
}
]
}
export interface UseWishlistOptions {
includeProducts?: boolean
}
export interface UseWishlistInput extends UseWishlistOptions {
customerId?: number
}
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
return null
}
export function extendHook(
customFetcher: typeof fetcher,
// swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
swrOptions?: any
) {
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
return { data: null }
}
useWishlist.extend = extendHook
return useWishlist
}
export default extendHook(fetcher)

View File

@ -0,0 +1,20 @@
export async function build(task, opts) {
await task
.source('src/**/*.+(ts|tsx|js)')
.swc({ dev: opts.dev, outDir: 'dist', baseUrl: 'src' })
.target('dist')
.source('src/**/*.+(cjs|json)')
.target('dist')
task.$.log('Compiled src files')
}
export async function release(task) {
await task.clear('dist').start('build')
}
export default async function dev(task) {
const opts = { dev: true }
await task.clear('dist')
await task.start('build', opts)
await task.watch('src/**/*.+(ts|tsx|js|cjs|json)', 'build', opts)
}

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"outDir": "dist",
"baseUrl": "src",
"lib": ["dom", "dom.iterable", "esnext"],
"declaration": true,
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"incremental": true,
"jsx": "react-jsx"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View File

@ -19,6 +19,7 @@ const PROVIDERS = [
'@vercel/commerce-kibocommerce', '@vercel/commerce-kibocommerce',
'@vercel/commerce-spree', '@vercel/commerce-spree',
'@vercel/commerce-commercejs', '@vercel/commerce-commercejs',
'appibase'
] ]
function getProviderName() { function getProviderName() {

View File

@ -4,6 +4,6 @@
"search": true, "search": true,
"wishlist": false, "wishlist": false,
"customerAuth": false, "customerAuth": false,
"customCheckout": false "customCheckout": true
} }
} }

View File

@ -23,8 +23,8 @@
"@components/*": ["components/*"], "@components/*": ["components/*"],
"@commerce": ["../packages/commerce/src"], "@commerce": ["../packages/commerce/src"],
"@commerce/*": ["../packages/commerce/src/*"], "@commerce/*": ["../packages/commerce/src/*"],
"@framework": ["../packages/local/src"], "@framework": ["../packages/appibase/src"],
"@framework/*": ["../packages/local/src/*"] "@framework/*": ["../packages/appibase/src/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"], "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],

View File

@ -1985,6 +1985,13 @@ axios@^0.21.1:
dependencies: dependencies:
follow-redirects "^1.14.0" follow-redirects "^1.14.0"
axios@^0.26.0:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"
axobject-query@^2.2.0: axobject-query@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@ -2802,6 +2809,15 @@ dependency-graph@^0.11.0:
resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27"
integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg== integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==
deserialize-json-api@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/deserialize-json-api/-/deserialize-json-api-1.4.0.tgz#776df0d56db1cab420d1d58e7b3d916f0140fda8"
integrity sha512-rfViAb34ATDUygZwuU8VSi/FC1T5m+eIAjPiTwcuwCvf3V6uWdnKSYZ1Y8myHW9RCKPhnBKMHp/3ZVF/uEZISQ==
dependencies:
lodash.camelcase "^4.3.0"
lodash.isarray "^4.0.0"
lodash.isplainobject "^4.0.6"
detect-indent@^6.0.0: detect-indent@^6.0.0:
version "6.1.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
@ -3469,6 +3485,11 @@ follow-redirects@^1.14.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
for-in@^1.0.1, for-in@^1.0.2: for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -4383,6 +4404,11 @@ js-cookie@^3.0.1:
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw== integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
js-cookies@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/js-cookies/-/js-cookies-1.0.4.tgz#d46e576c420ff6d5542c0f52b6d4ef7d637e754e"
integrity sha1-1G5XbEIP9tVULA9SttTvfWN+dU4=
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -4400,6 +4426,11 @@ jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json-api-response-converter@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/json-api-response-converter/-/json-api-response-converter-1.6.0.tgz#66fc07c92d39da8a0a0d2eebc10b8b1b9b38d962"
integrity sha512-PetQPJUmimf5OuDPkjWij85LdNKctG9SnGQ9EfdOQxt2nwEviraXGekEgJcEwsgeKxnrG2zD7D+nQJ6YAkzM7Q==
json-buffer@3.0.0: json-buffer@3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
@ -4531,6 +4562,20 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
kitsu-core@^10.0.0-alpha.22:
version "10.0.0-alpha.22"
resolved "https://registry.yarnpkg.com/kitsu-core/-/kitsu-core-10.0.0-alpha.22.tgz#783fa6352245f147b1ddde057aef424d4d0e4da9"
integrity sha512-+1O27hmKlTxYSTOuJw0D5leJMD5KMpcRczb4k3CGKylRMyBlNqsCCFLb6FNDRIDVu4szRYKAnypljVJMgSkovg==
kitsu@^10.0.0-alpha.22:
version "10.0.0-alpha.22"
resolved "https://registry.yarnpkg.com/kitsu/-/kitsu-10.0.0-alpha.22.tgz#d30281bad42572bed5ea78adf6fe3e7b92e93b65"
integrity sha512-EgDxOfiBEBj7n0FgEqzJSReqiXcUDwcKNKiVJcgbaCjISdkN9PdiSNTN3KWRYS4EuetLkIjWpH40VMkGAW4fjg==
dependencies:
axios "^0.26.0"
kitsu-core "^10.0.0-alpha.22"
pluralize "^8.0.0"
language-subtag-registry@~0.3.2: language-subtag-registry@~0.3.2:
version "0.3.21" version "0.3.21"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
@ -4694,6 +4739,11 @@ lodash.includes@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isarray@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-4.0.0.tgz#2aca496b28c4ca6d726715313590c02e6ea34403"
integrity sha1-KspJayjEym1yZxUxNZDALm6jRAM=
lodash.isboolean@^3.0.3: lodash.isboolean@^3.0.3:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
@ -5520,6 +5570,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pluralize@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
posix-character-classes@^0.1.0: posix-character-classes@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@ -7122,6 +7177,11 @@ uri-js@^4.2.2:
dependencies: dependencies:
punycode "^2.1.0" punycode "^2.1.0"
urijs@^1.19.10:
version "1.19.10"
resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.10.tgz#8e2fe70a8192845c180f75074884278f1eea26cb"
integrity sha512-EzauQlgKuJgsXOqoMrCiePBf4At5jVqRhXykF3Wfb8ZsOBMxPcfiVBcsHXug4Aepb/ICm2PIgqAUGMelgdrWEg==
urix@^0.1.0: urix@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"