@meeovi/api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { loadGraphQL } from './src/loader/loadGraphql'
2
+
3
+ loadGraphQL()
4
+
5
+ export * from './src/utils/fetcher'
6
+ export * from './src/loader/registry'
7
+
8
+ // Apollo client
9
+ export * from './src/client/apollo'
10
+
11
+ // REST helper
12
+ export * from './src/client/rest'
13
+
14
+ // GraphQL queries/mutations
15
+ export * from './src/graphql/queries/products'
16
+ export * from './src/graphql/mutations/cart'
17
+
18
+ // Providers
19
+ export * from './src/providers/commerce'
20
+ export * from './src/providers/content'
21
+ export * from './src/providers/search'
22
+
23
+ // Utils
24
+ export * from './src/utils/errors'
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@meeovi/api",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "exports": { ".": "./index.ts" },
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "type": "commonjs",
14
+ "dependencies": {
15
+ "@apollo/client": "^4.1.0",
16
+ "cross-fetch": "^4.1.0",
17
+ "graphql": "^16.12.0",
18
+ "graphql-tag": "^2.12.6"
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'
2
+ import fetch from 'cross-fetch'
3
+
4
+ let client: ApolloClient | null = null
5
+
6
+ export function createApolloClient(uri: string) {
7
+ client = new ApolloClient({
8
+ link: new HttpLink({ uri, fetch }),
9
+ cache: new InMemoryCache()
10
+ })
11
+
12
+ return client
13
+ }
14
+
15
+ export function getApolloClient() {
16
+ if (!client) {
17
+ throw new Error('Apollo client not initialized. Call createApolloClient() first.')
18
+ }
19
+ return client
20
+ }
@@ -0,0 +1,5 @@
1
+ export async function apiFetch(url: string, options: RequestInit = {}) {
2
+ const res = await fetch(url, options)
3
+ if (!res.ok) throw new Error(`API error: ${res.status}`)
4
+ return res.json()
5
+ }
@@ -0,0 +1,18 @@
1
+ import type { Fetcher } from './types'
2
+
3
+ const fetchers: Record<string, Fetcher> = {}
4
+ let activeFetcherName: string | null = null
5
+
6
+ export function registerFetcher(name: string, fetcher: Fetcher) {
7
+ fetchers[name] = fetcher
8
+ }
9
+
10
+ export function setActiveFetcher(name: string) {
11
+ if (!fetchers[name]) throw new Error(`Fetcher "${name}" not registered`)
12
+ activeFetcherName = name
13
+ }
14
+
15
+ export function getFetcher(): Fetcher {
16
+ if (!activeFetcherName) throw new Error('No active fetcher set')
17
+ return fetchers[activeFetcherName]
18
+ }
@@ -0,0 +1,19 @@
1
+
2
+ import { getApolloClient } from '../client/apollo'
3
+ import { Fetcher, FetcherRequest, FetcherResponse } from './types'
4
+
5
+ export const GraphQLFetcher: Fetcher = {
6
+ async execute<T = any>(req: FetcherRequest): Promise<FetcherResponse<T>> {
7
+ try {
8
+ const client = getApolloClient()
9
+ const { data } = await client.query({
10
+ query: req.operation,
11
+ variables: req.variables || {}
12
+ })
13
+
14
+ return { data }
15
+ } catch (error) {
16
+ return { error }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+
2
+ import { Fetcher } from './types'
3
+
4
+ let activeFetcher: Fetcher | null = null
5
+
6
+ export function setFetcher(fetcher: Fetcher) {
7
+ activeFetcher = fetcher
8
+ }
9
+
10
+ export function getFetcher(): Fetcher {
11
+ if (!activeFetcher) {
12
+ throw new Error('No fetcher has been set. Call setFetcher() first.')
13
+ }
14
+ return activeFetcher
15
+ }
@@ -0,0 +1,19 @@
1
+
2
+ import { Fetcher, FetcherRequest, FetcherResponse } from './types'
3
+
4
+ export const RestFetcher: Fetcher = {
5
+ async execute<T = any>(req: FetcherRequest): Promise<FetcherResponse<T>> {
6
+ try {
7
+ const res = await fetch(req.operation, {
8
+ method: req.options?.method || 'GET',
9
+ headers: req.options?.headers,
10
+ body: req.options?.body ? JSON.stringify(req.options.body) : undefined
11
+ })
12
+
13
+ const data = await res.json()
14
+ return { data }
15
+ } catch (error) {
16
+ return { error }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+
2
+ export interface FetcherRequest {
3
+ operation: string
4
+ variables?: Record<string, any>
5
+ options?: Record<string, any>
6
+ }
7
+
8
+ export interface FetcherResponse<T = any> {
9
+ data?: T
10
+ error?: any
11
+ }
12
+
13
+ export interface Fetcher {
14
+ execute<T = any>(req: FetcherRequest): Promise<FetcherResponse<T>>
15
+ }
@@ -0,0 +1,24 @@
1
+ import { gql } from 'graphql-tag'
2
+
3
+ export const GET_PRODUCT = gql`
4
+ query GetProduct($id: ID!) {
5
+ product(id: $id) {
6
+ id
7
+ title
8
+ description
9
+ price
10
+ images
11
+ }
12
+ }
13
+ `
14
+
15
+ export const LIST_PRODUCTS = gql`
16
+ query ListProducts {
17
+ products {
18
+ id
19
+ title
20
+ price
21
+ images
22
+ }
23
+ }
24
+ `
@@ -0,0 +1,49 @@
1
+ import {
2
+ registerNamespacedQuery,
3
+ registerNamespacedMutation,
4
+ registerNamespacedSubscription
5
+ } from './nsRegistry'
6
+
7
+ export function loadGraphQLNamespaced() {
8
+ // queries
9
+ const queryModules = (import.meta as any).glob('../graphql/queries/**/*.ts', { eager: true })
10
+ for (const path in queryModules) {
11
+ const mod = queryModules[path] as Record<string, any>
12
+ const namespace = path
13
+ .replace('../graphql/queries/', '')
14
+ .replace('.ts', '')
15
+ .replace(/\//g, '.')
16
+
17
+ for (const key in mod) {
18
+ registerNamespacedQuery(namespace, key, mod[key])
19
+ }
20
+ }
21
+
22
+ // mutations
23
+ const mutationModules = (import.meta as any).glob('../graphql/mutations/**/*.ts', { eager: true })
24
+ for (const path in mutationModules) {
25
+ const mod = mutationModules[path] as Record<string, any>
26
+ const namespace = path
27
+ .replace('../graphql/mutations/', '')
28
+ .replace('.ts', '')
29
+ .replace(/\//g, '.')
30
+
31
+ for (const key in mod) {
32
+ registerNamespacedMutation(namespace, key, mod[key])
33
+ }
34
+ }
35
+
36
+ // subscriptions
37
+ const subscriptionModules = (import.meta as any).glob('../graphql/subscriptions/**/*.ts', { eager: true })
38
+ for (const path in subscriptionModules) {
39
+ const mod = subscriptionModules[path] as Record<string, any>
40
+ const namespace = path
41
+ .replace('../graphql/subscriptions/', '')
42
+ .replace('.ts', '')
43
+ .replace(/\//g, '.')
44
+
45
+ for (const key in mod) {
46
+ registerNamespacedSubscription(namespace, key, mod[key])
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,34 @@
1
+ import {
2
+ registerQuery,
3
+ registerMutation,
4
+ registerSubscription
5
+ } from './registry'
6
+
7
+ export function loadGraphQL() {
8
+ // Load queries
9
+ const queryModules = (import.meta as any).glob('../graphql/queries/*.ts', { eager: true })
10
+ for (const path in queryModules) {
11
+ const mod = queryModules[path]
12
+ for (const key in mod) {
13
+ registerQuery(key, mod[key])
14
+ }
15
+ }
16
+
17
+ // Load mutations
18
+ const mutationModules = (import.meta as any).glob('../graphql/mutations/*.ts', { eager: true })
19
+ for (const path in mutationModules) {
20
+ const mod = mutationModules[path]
21
+ for (const key in mod) {
22
+ registerMutation(key, mod[key])
23
+ }
24
+ }
25
+
26
+ // Load subscriptions
27
+ const subscriptionModules = (import.meta as any).glob('../graphql/subscriptions/*.ts', { eager: true })
28
+ for (const path in subscriptionModules) {
29
+ const mod = subscriptionModules[path]
30
+ for (const key in mod) {
31
+ registerSubscription(key, mod[key])
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,13 @@
1
+ import { registerRestEndpoint } from '../rest/registry'
2
+ import type { RestEndpoint } from '../rest/types'
3
+
4
+ export function loadRest() {
5
+ const modules = (import.meta as any).glob('../rest/*.ts', { eager: true })
6
+
7
+ for (const path in modules) {
8
+ const mod = modules[path] as Record<string, RestEndpoint>
9
+ for (const key in mod) {
10
+ registerRestEndpoint(key, mod[key])
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,54 @@
1
+ type OperationMap = Record<string, any>
2
+
3
+ const queries: OperationMap = {}
4
+ const mutations: OperationMap = {}
5
+ const subscriptions: OperationMap = {}
6
+
7
+ function setNested(map: OperationMap, ns: string, value: any) {
8
+ const parts = ns.split('.')
9
+ let current = map
10
+
11
+ for (let i = 0; i < parts.length - 1; i++) {
12
+ const part = parts[i]
13
+ current[part] = current[part] || {}
14
+ current = current[part]
15
+ }
16
+
17
+ current[parts[parts.length - 1]] = value
18
+ }
19
+
20
+ function getNested(map: OperationMap, ns: string) {
21
+ const parts = ns.split('.')
22
+ let current: any = map
23
+
24
+ for (const part of parts) {
25
+ current = current?.[part]
26
+ if (!current) return undefined
27
+ }
28
+
29
+ return current
30
+ }
31
+
32
+ export function registerNamespacedQuery(namespace: string, name: string, op: any) {
33
+ setNested(queries, `${namespace}.${name}`, op)
34
+ }
35
+
36
+ export function registerNamespacedMutation(namespace: string, name: string, op: any) {
37
+ setNested(mutations, `${namespace}.${name}`, op)
38
+ }
39
+
40
+ export function registerNamespacedSubscription(namespace: string, name: string, op: any) {
41
+ setNested(subscriptions, `${namespace}.${name}`, op)
42
+ }
43
+
44
+ export function getQuery(ns: string) {
45
+ return getNested(queries, ns)
46
+ }
47
+
48
+ export function getMutation(ns: string) {
49
+ return getNested(mutations, ns)
50
+ }
51
+
52
+ export function getSubscription(ns: string) {
53
+ return getNested(subscriptions, ns)
54
+ }
@@ -0,0 +1,27 @@
1
+ const queries: Record<string, any> = {}
2
+ const mutations: Record<string, any> = {}
3
+ const subscriptions: Record<string, any> = {}
4
+
5
+ export function registerQuery(name: string, operation: any) {
6
+ queries[name] = operation
7
+ }
8
+
9
+ export function registerMutation(name: string, operation: any) {
10
+ mutations[name] = operation
11
+ }
12
+
13
+ export function registerSubscription(name: string, operation: any) {
14
+ subscriptions[name] = operation
15
+ }
16
+
17
+ export function getQuery(name: string) {
18
+ return queries[name]
19
+ }
20
+
21
+ export function getMutation(name: string) {
22
+ return mutations[name]
23
+ }
24
+
25
+ export function getSubscription(name: string) {
26
+ return subscriptions[name]
27
+ }
@@ -0,0 +1,13 @@
1
+ import type { ApiPluginContext } from './types'
2
+ import { registerFetcher, setActiveFetcher } from '../fetcher/fetcherRegistry'
3
+
4
+ export type ApiPlugin = (ctx: ApiPluginContext) => void
5
+
6
+ export function defineApiPlugin(plugin: ApiPlugin) {
7
+ const ctx: ApiPluginContext = {
8
+ registerFetcher,
9
+ setActiveFetcher
10
+ }
11
+
12
+ plugin(ctx)
13
+ }
@@ -0,0 +1,5 @@
1
+ export interface ApiPluginContext {
2
+ registerFetcher: (name: string, fetcher: any) => void
3
+ setActiveFetcher: (name: string) => void
4
+ // you can add more hooks later (registerRestEndpoint, etc.)
5
+ }
@@ -0,0 +1,21 @@
1
+ import { getApolloClient } from '../client/apollo'
2
+ import { GET_PRODUCT, LIST_PRODUCTS } from '../graphql/queries/products'
3
+
4
+ export const MeeoviApiCommerceProvider = {
5
+ async getProduct(id: string) {
6
+ const client = getApolloClient()
7
+ const { data } = await client.query({
8
+ query: GET_PRODUCT,
9
+ variables: { id }
10
+ })
11
+ return data.product
12
+ },
13
+
14
+ async listProducts() {
15
+ const client = getApolloClient()
16
+ const { data } = await client.query({
17
+ query: LIST_PRODUCTS
18
+ })
19
+ return data.products
20
+ }
21
+ }
File without changes
File without changes
@@ -0,0 +1,11 @@
1
+ import { RestEndpoint } from "./types"
2
+
3
+ const restEndpoints: Record<string, RestEndpoint> = {}
4
+
5
+ export function registerRestEndpoint(name: string, endpoint: RestEndpoint) {
6
+ restEndpoints[name] = endpoint
7
+ }
8
+
9
+ export function getRestEndpoint(name: string): RestEndpoint | undefined {
10
+ return restEndpoints[name]
11
+ }
@@ -0,0 +1,5 @@
1
+ export interface RestEndpoint {
2
+ url: string
3
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
4
+ headers?: Record<string, string>
5
+ }
@@ -0,0 +1,6 @@
1
+ export class ApiError extends Error {
2
+ constructor(message: string, public status?: number) {
3
+ super(message)
4
+ this.name = 'ApiError'
5
+ }
6
+ }
@@ -0,0 +1,17 @@
1
+
2
+ import { getFetcher } from '../fetcher/registry'
3
+ import { FetcherRequest, FetcherResponse } from '../fetcher/types'
4
+
5
+ export async function fetcher<T = any>(
6
+ operation: string,
7
+ variables?: Record<string, any>,
8
+ options?: Record<string, any>
9
+ ): Promise<FetcherResponse<T>> {
10
+ const activeFetcher = getFetcher()
11
+
12
+ return activeFetcher.execute<T>({
13
+ operation,
14
+ variables,
15
+ options
16
+ })
17
+ }