Skip to content
This repository was archived by the owner on Apr 11, 2024. It is now read-only.

[WIP][Admin API Client] Add Admin API client package #1022

Merged
merged 18 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/swift-buckets-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
18 changes: 18 additions & 0 deletions packages/admin-api-client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

node_modules/
dist/

package-lock.json
.vscode/
.DS_Store
.rollup.cache/

# ignore any locally packed packages
*.tgz
!*.d.ts
314 changes: 314 additions & 0 deletions packages/admin-api-client/README.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions packages/admin-api-client/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
["@babel/preset-env"],
["@shopify/babel-preset", {"typescript": true}]
],
"plugins": [
["@babel/plugin-transform-runtime"],
["@babel/plugin-transform-async-to-generator"]
]
}
103 changes: 103 additions & 0 deletions packages/admin-api-client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"name": "@shopify/admin-api-client",
"version": "0.0.1",
"description": "Shopify Admin API Client - A lightweight JS client to interact with Shopify's Admin API",
"repository": {
"type": "git",
"url": "git+https://github.com/Shopify/shopify-api-js.git"
},
"author": "Shopify",
"license": "MIT",
"main": "./dist/admin-api-client.min.js",
"module": "./dist/index.mjs",
"types": "./dist/admin-api-client.d.ts",
"exports": {
".": {
"module": {
"types": "./dist/ts/index.d.ts",
"default": "./dist/index.mjs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to have require entries for the CJS build? I think that would be the right setup here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's happening a few lines down - let me know if I'm missing something!

},
"import": {
"types": "./dist/ts/index.d.ts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/ts/index.d.ts",
"default": "./dist/index.js"
},
"default": "./dist/index.mjs"
}
},
"scripts": {
"lint": "eslint . --ext .js,.ts",
"build": "yarn run rollup",
"test": "jest",
"test:ci": "yarn test",
"rollup": "rollup -c --bundleConfigAsCjs",
"clean": "rimraf dist/*",
"changeset": "changeset",
"version": "changeset version",
"release": "yarn build && changeset publish"
},
"jest": {
"setupFilesAfterEnv": [
"./src/tests/setupTests.ts"
],
"transform": {
".*": "babel-jest"
}
},
"publishConfig": {
"access": "public"
},
"keywords": [
"shopify",
"node",
"graphql",
"admin API"
],
"files": [
"dist/**/*.*"
],
"dependencies": {
"@shopify/graphql-client": "^0.7.0"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/plugin-transform-async-to-generator": "^7.20.7",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.21.0",
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-eslint": "^9.0.3",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-typescript": "^11.0.0",
"@shopify/babel-preset": "^25.0.0",
"@shopify/eslint-plugin": "^42.0.3",
"@shopify/prettier-config": "^1.1.2",
"@shopify/typescript-configs": "^5.1.0",
"@types/jest": "^29.5.0",
"@types/regenerator-runtime": "^0.13.1",
"@typescript-eslint/parser": "^6.7.5",
"babel-jest": "^29.5.0",
"eslint": "^8.51.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.5.0",
"jest-fetch-mock": "^3.0.3",
"prettier": "^2.5.1",
"regenerator-runtime": "^0.13.11",
"rollup": "^3.19.1",
"rollup-plugin-dts": "^5.2.0",
"tslib": "^2.5.0",
"typescript": "^5.2.0"
},
"bugs": {
"url": "https://github.com/Shopify/shopify-api-js/issues"
},
"homepage": "https://github.com/Shopify/shopify-api-js/packages/admin-api-client#readme"
}
64 changes: 64 additions & 0 deletions packages/admin-api-client/rollup.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import dts from "rollup-plugin-dts";
import typescript from "@rollup/plugin-typescript";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import terser from "@rollup/plugin-terser";
import replace from "@rollup/plugin-replace";

import * as pkg from "./package.json";

export const mainSrcInput = "src/index.ts";

export function getPlugins({ tsconfig, minify } = {}) {
return [
replace({
preventAssignment: true,
ROLLUP_REPLACE_CLIENT_VERSION: pkg.version,
}),
resolve(),
commonjs(),
typescript({
tsconfig: tsconfig ? tsconfig : "./tsconfig.build.json",
outDir: "./dist/ts",
}),
...(minify === true ? [terser({ keep_fnames: new RegExp("fetch") })] : []),
];
}

const config = [
{
input: mainSrcInput,
plugins: getPlugins(),
output: [
{
dir: "./dist",
format: "es",
sourcemap: true,
preserveModules: true,
preserveModulesRoot: "src",
entryFileNames: "[name].mjs",
},
],
},
{
input: mainSrcInput,
plugins: getPlugins(),
output: [
{
dir: "./dist",
format: "cjs",
sourcemap: true,
exports: "named",
preserveModules: true,
preserveModulesRoot: "src",
},
],
},
{
input: "./dist/ts/index.d.ts",
output: [{ file: "dist/admin-api-client.d.ts", format: "es" }],
plugins: [dts.default()],
},
];

export default config;
144 changes: 144 additions & 0 deletions packages/admin-api-client/src/admin-api-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {
createGraphQLClient,
getCurrentSupportedApiVersions,
validateApiVersion,
validateDomainAndGetStoreUrl,
generateGetGQLClientParams,
generateGetHeaders,
ApiClientRequestParams,
} from "@shopify/graphql-client";

import {
AdminApiClientOptions,
AdminApiClient,
AdminApiClientConfig,
} from "./types";
import {
DEFAULT_CONTENT_TYPE,
ACCESS_TOKEN_HEADER,
CLIENT,
DEFAULT_CLIENT_VERSION,
} from "./constants";
import {
validateRequiredAccessToken,
validateServerSideUsage,
} from "./validations";

export function createAdminApiClient({
storeDomain,
apiVersion,
accessToken,
userAgentPrefix,
retries = 0,
customFetchApi: clientFetchApi,
logger,
}: AdminApiClientOptions): AdminApiClient {
const currentSupportedApiVersions = getCurrentSupportedApiVersions();

const storeUrl = validateDomainAndGetStoreUrl({
client: CLIENT,
storeDomain,
});

const baseApiVersionValidationParams = {
client: CLIENT,
currentSupportedApiVersions,
logger,
};

validateServerSideUsage();
validateApiVersion({
client: CLIENT,
currentSupportedApiVersions,
apiVersion,
logger,
});
validateRequiredAccessToken(accessToken);

const apiUrlFormatter = generateApiUrlFormatter(
storeUrl,
apiVersion,
baseApiVersionValidationParams
);

const config: AdminApiClientConfig = {
storeDomain: storeUrl,
apiVersion,
accessToken,
headers: {
"Content-Type": DEFAULT_CONTENT_TYPE,
Accept: DEFAULT_CONTENT_TYPE,
[ACCESS_TOKEN_HEADER]: accessToken,
"User-Agent": `${
userAgentPrefix ? `${userAgentPrefix} | ` : ""
}${CLIENT} v${DEFAULT_CLIENT_VERSION}`,
Comment on lines +72 to +74
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a mouthful 😅 is there a cleaner approach?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't think of anything other than pulling the prefix part into its own variable, but that doesn't really change anything. Fine as is IMO.

},
apiUrl: apiUrlFormatter(),
userAgentPrefix,
};

const graphqlClient = createGraphQLClient({
headers: config.headers,
url: config.apiUrl,
retries,
fetchApi: clientFetchApi,
logger,
});

const getHeaders = generateGetHeaders(config);
const getApiUrl = generateGetApiUrl(config, apiUrlFormatter);

const getGQLClientParams = generateGetGQLClientParams({
getHeaders,
getApiUrl,
});

const fetch = (...props: ApiClientRequestParams) => {
return graphqlClient.fetch(...getGQLClientParams(...props));
};

const request = <TData>(...props: ApiClientRequestParams) => {
return graphqlClient.request<TData>(...getGQLClientParams(...props));
};

const client: AdminApiClient = {
config,
getHeaders,
getApiUrl,
fetch,
request,
};

return Object.freeze(client);
}

function generateApiUrlFormatter(
storeUrl: string,
defaultApiVersion: string,
baseApiVersionValidationParams: Omit<
Parameters<typeof validateApiVersion>[0],
"apiVersion"
>
) {
return (apiVersion?: string) => {
if (apiVersion) {
validateApiVersion({
...baseApiVersionValidationParams,
apiVersion,
});
}

const urlApiVersion = (apiVersion ?? defaultApiVersion).trim();

return `${storeUrl}/admin/api/${urlApiVersion}/graphql.json`;
};
}

function generateGetApiUrl(
config: AdminApiClientConfig,
apiUrlFormatter: (version?: string) => string
): AdminApiClient["getApiUrl"] {
return (propApiVersion?: string) => {
return propApiVersion ? apiUrlFormatter(propApiVersion) : config.apiUrl;
};
}
5 changes: 5 additions & 0 deletions packages/admin-api-client/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const DEFAULT_CONTENT_TYPE = "application/json";
// This is value is replaced with package.json version during rollup build process
export const DEFAULT_CLIENT_VERSION = "ROLLUP_REPLACE_CLIENT_VERSION";
export const ACCESS_TOKEN_HEADER = "X-Shopify-Access-Token";
export const CLIENT = "Admin API Client";
1 change: 1 addition & 0 deletions packages/admin-api-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createAdminApiClient } from "./admin-api-client";
Loading