Skip to content

Commit

Permalink
feat: add crud methods, typescript support & graphql method
Browse files Browse the repository at this point in the history
  • Loading branch information
Stun3R committed May 23, 2021
1 parent 4d72526 commit cbe3b47
Show file tree
Hide file tree
Showing 11 changed files with 766 additions and 15 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Expand Up @@ -2,6 +2,7 @@ dist/
bin/
test/
docs/
example/
.eslintrc.js
.commitlint.config.js
.husky.config.js
Expand Down
12 changes: 11 additions & 1 deletion .eslintrc.js
Expand Up @@ -11,6 +11,16 @@ module.exports = {
sourceType: "module",
},
rules: {
// Special ESLint rules or overrides go here.
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-this-alias": [
"error",
{
allowDestructuring: true, // Allow `const { props, state } = this`; false by default
allowedNames: ["self"], // Allow `const self = this`; `[]` by default
},
],
},
};
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -43,6 +43,7 @@ bower_components
build/Release
build/
dist/
bin/


# Dependency directories
Expand Down
6 changes: 3 additions & 3 deletions jest.config.js
@@ -1,6 +1,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
preset: "ts-jest",
testEnvironment: "node",
collectCoverage: true,
modulePathIgnorePatterns: ['examples', 'docs', 'dist'],
modulePathIgnorePatterns: ["bin", "docs", "dist", "example"],
};
9 changes: 7 additions & 2 deletions package.json
Expand Up @@ -78,17 +78,22 @@
"@graphql-tools/load": "^6.2.8",
"@graphql-tools/url-loader": "^6.10.1",
"@types/fs-extra": "^9.0.11",
"@types/qs": "^6.9.6",
"axios": "^0.21.1",
"chalk": "^4.1.1",
"commander": "^7.2.0",
"defu": "^5.0.0",
"enquirer": "^2.3.6",
"fs-extra": "^10.0.0",
"graphql": "^15.5.0"
"graphql": "^15.5.0",
"qs": "^6.10.1"
},
"devDependencies": {
"@commitlint/cli": "12.1.4",
"@commitlint/config-conventional": "12.1.4",
"@release-it/conventional-changelog": "2.0.1",
"@types/jest": "26.0.23",
"@types/sinon": "^10.0.0",
"@typescript-eslint/eslint-plugin": "4.24.0",
"@typescript-eslint/parser": "4.24.0",
"commitizen": "4.2.4",
Expand All @@ -100,7 +105,7 @@
"lint-staged": "11.0.0",
"npm-run-all": "4.1.5",
"release-it": "14.6.2",
"sinon": "10.0.0",
"sinon": "^10.0.0",
"siroc": "0.10.1",
"standard-version": "9.3.0",
"ts-jest": "26.5.6",
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
@@ -1,3 +1,3 @@
export const sum = (a: number, b: number): number => {
return a + b;
};
import Strapi from "./lib/strapi";

export default Strapi;
266 changes: 266 additions & 0 deletions src/lib/strapi.ts
@@ -0,0 +1,266 @@
// Module dependencies & types
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
Method,
} from "axios";
import defu from "defu";
import qs from "qs";

// Load custom types
import type { StrapiOptions } from "./types";

// Strapi options' default values
const defaults: StrapiOptions = {
url: process.env.STRAPI_URL || "http://localhost:1337",
contentTypes: [],
axiosOptions: {},
};

export default class Strapi {
public axios: AxiosInstance;
public options: StrapiOptions;

/**
* Strapi SDK Constructor
*
* @param {StrapiOptions} options? - Options in order to configure API URL, list your Content Types & extend the axios configuration
* @param {string} options.url? - Your Strapi API URL, Default: process.env.STRAPI_URL || http://localhost::1337
* @param {string[] | StrapiContentType[]} options.contentTypes? - The list of your Content type on your Strapi API
* @param {AxiosRequestConfig} options.axiosOptions? - The list of your Content type on your Strapi API
*/
constructor(options?: StrapiOptions) {
// merge given options with default values
this.options = defu(options || {}, defaults);

// create axios instance
this.axios = axios.create({
baseURL: this.options.url,
paramsSerializer: qs.stringify,
...this.options.axiosOptions,
});

// Generate scoped methods for
for (let contentType of this.options.contentTypes || []) {
let type: "collection" | "single" = "collection";
let key: string;
if (typeof contentType === "object") {
key = contentType.name;
type = contentType.type;
contentType = contentType.name;
} else {
key = contentType;
type = "collection";
}

if (Strapi.prototype.hasOwnProperty(key)) return;

Object.defineProperty(Strapi.prototype, key, {
get() {
const self = this;
return {
single: {
find(...args: never[]) {
return self.find(contentType, ...args);
},
update(...args: never[]) {
return self.update(contentType, ...args);
},
delete(...args: never[]) {
return self.delete(contentType, ...args);
},
},
collection: {
find(...args: never[]) {
return self.find(contentType, ...args);
},
findOne(...args: never[]) {
return self.findOne(contentType, ...args);
},
count(...args: never[]) {
return self.count(contentType, ...args);
},
create(...args: never[]) {
return self.create(contentType, ...args);
},
update(...args: never[]) {
return self.update(contentType, ...args);
},
delete(...args: never[]) {
return self.delete(contentType, ...args);
},
},
}[type];
},
});
}
}

/**
* Basic axios request
*
* @param {Method} method - HTTP method
* @param {string} url - Custom or Strapi API URL
* @param {AxiosRequestConfig} axiosConfig? - Custom Axios config
* @returns Promise<AxiosResponse<T>>
*/
public async request<T>(
method: Method,
url: string,
axiosConfig?: AxiosRequestConfig
): Promise<AxiosResponse<T>> {
try {
const response: AxiosResponse<T> = await this.axios.request<T>({
method,
url,
...axiosConfig,
});
return response;
} catch (error) {
// Strapi error or not
if (!error.response) {
throw {
isStrapi: false,
response: error.message,
};
} else {
const {
status,
statusText,
headers,
config,
request,
data,
}: AxiosResponse = error.response;

// format error message
let message;
if (Array.isArray(data.message)) {
if (data.message[0].hasOwnProperty("messages")) {
message = data.message[0].messages[0];
} else {
message = data.message[0];
}
} else {
message = data.message;
}

throw {
isStrapi: true,
response: {
status,
statusText,
message,
original: data,
headers,
config,
request,
},
};
}
}
}

/**
* Get a list of {content-type} entries
*
* @param {string} contentType - Content type's name pluralized
* @param {AxiosRequestConfig["params"]} params? - Filter and order queries
* @returns Promise<AxiosResponse<T>>
*/
public find<T>(
contentType: string,
params?: AxiosRequestConfig["params"]
): Promise<AxiosResponse<T>> {
return this.request<T>("get", `/${contentType}`, { params });
}

/**
* Get a specific {content-type} entry
*
* @param {string} contentType - Content type's name pluralized
* @param {string|number} id - ID of entry
* @returns Promise<AxiosResponse<T>>
*/
public findOne<T>(
contentType: string,
id: string | number
): Promise<AxiosResponse<T>> {
return this.request<T>("get", `/${contentType}/${id}`);
}

/**
* Count {content-type} entries
*
* @param {string} contentType - Content type's name pluralized
* @param {AxiosRequestConfig["params"]} params? - Filter and order queries
* @returns Promise<AxiosResponse<T>>
*/
public count<T>(
contentType: string,
params?: AxiosRequestConfig["params"]
): Promise<AxiosResponse<T>> {
return this.request<T>("get", `/${contentType}/count`, { params });
}

/**
* Create a {content-type} entry
*
* @param {string} contentType - Content type's name pluralized
* @param {AxiosRequestConfig["data"]} data - New entry
* @returns Promise<AxiosResponse<T>>
*/
public create<T>(
contentType: string,
data: AxiosRequestConfig["data"]
): Promise<AxiosResponse<T>> {
return this.request<T>("post", `/${contentType}`, { data });
}

/**
* Update a specific entry
*
* @param {string} contentType - Content type's name pluralized
* @param {string|number} id - ID of entry to be updated
* @param {AxiosRequestConfig["data"]} data - New entry data
* @returns Promise<AxiosResponse<T>>
*/
public update<T>(
contentType: string,
id: string | number,
data: AxiosRequestConfig["data"]
): Promise<AxiosResponse<T>> {
return this.request<T>("put", `/${contentType}/${id}`, { data });
}

/**
* Delete en entry
*
* @param {string} contentType - Content type's name pluralized
* @param {string|number} id - ID of entry to be deleted
* @returns Promise<AxiosResponse<T>>
*/
public delete<T>(
contentType: string,
id: string | number
): Promise<AxiosResponse<T>> {
return this.request<T>("delete", `/${contentType}/${id}`);
}

/**
* Fetch Strapi API through graphQL
*
* @param {AxiosRequestConfig["data"]} query - GraphQL Query
* @returns Promise<AxiosResponse<T>>
*/
public async graphql<T>(
query: AxiosRequestConfig["data"]
): Promise<AxiosResponse<T>> {
const response: AxiosResponse = await this.request("post", "/graphql", {
data: query,
});
response.data = Object.values(response.data.data)[0];
return response;
}
}
12 changes: 12 additions & 0 deletions src/lib/types.ts
@@ -0,0 +1,12 @@
import type { AxiosRequestConfig } from "axios";

export interface StrapiContentType {
name: string;
type: "collection" | "single";
}

export interface StrapiOptions {
url?: string;
contentTypes?: string[] | StrapiContentType[];
axiosOptions?: AxiosRequestConfig;
}

0 comments on commit cbe3b47

Please sign in to comment.