diff --git a/src/api.ts b/src/api.ts deleted file mode 100644 index dae8c58..0000000 --- a/src/api.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { makeRequest } from './http'; - -export const showAllDbs = makeRequest('get', '/databases'); -export const showAllSchemas = makeRequest('get', '/schemas'); -export const showAllTables = makeRequest('get', '/tables'); -export const showAllTablesBySchema = makeRequest('get', '/{{db}}/{{schema}}'); diff --git a/src/entity/Api.ts b/src/entity/Api.ts new file mode 100644 index 0000000..b4f40b0 --- /dev/null +++ b/src/entity/Api.ts @@ -0,0 +1,62 @@ +import Request from './Request'; +import TableConnector from './TableConnector'; + +type Database = { + datname: string; +}; + +type Schema = { + schema_name: string; +}; + +type Table = { + schema: string; + name: string; + type: string; + owner: string; +}; + +type TableDescriptionItem = { + table_schema: string; + table_name: string; + position: number; + column_name: string; + data_type: string; + max_length: number; + is_nullable: string; + is_generated: string; + is_updatable: string; + default_value: string; +}; + +export class Api extends Request { + private prepareConnectionPath(entries: string[]) { + return '/' + (entries.length === 1 ? entries[0].replace(/\./g, '/') : entries.join('/')); + } + + databases(): Promise { + return this.call('get', '/databases'); + } + + schemas(): Promise { + return this.call('get', '/schemas'); + } + + tables(): Promise { + return this.call('get', '/tables'); + } + + tablesByDBInSchema(...entries: string[]): Promise { + return this.call('get', `${this.prepareConnectionPath(entries)}`); + } + + show(...entries: string[]): Promise { + return this.call('get', `/show${this.prepareConnectionPath(entries)}`); + } + + tableConnection(...entries: string[]): TableConnector { + return new TableConnector(this.baseUrl, this.prepareConnectionPath(entries)); + } +} + +export default Api; diff --git a/src/entity/Request.ts b/src/entity/Request.ts new file mode 100644 index 0000000..c9827a2 --- /dev/null +++ b/src/entity/Request.ts @@ -0,0 +1,15 @@ +import axios, { Method } from 'axios'; + +export class Request { + protected baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + public call(method: Method, path: string): Promise { + return axios[method](this.baseUrl + path).then(({ data }) => data); + } +} + +export default Request; diff --git a/src/entity/TableConnector.ts b/src/entity/TableConnector.ts new file mode 100644 index 0000000..f898458 --- /dev/null +++ b/src/entity/TableConnector.ts @@ -0,0 +1,16 @@ +import Request from './Request'; + +export class TableConnector extends Request { + private dbPath: string; + + constructor(baseUrl: string, dbPath: string) { + super(baseUrl); + this.dbPath = dbPath; + } + + query(): Promise { + return this.call('get', this.dbPath); + } +} + +export default TableConnector; diff --git a/src/http.ts b/src/http.ts deleted file mode 100644 index 5c807ad..0000000 --- a/src/http.ts +++ /dev/null @@ -1,30 +0,0 @@ -import axios from 'axios'; -import * as qs from 'querystring'; -import { getOptions } from './store'; - -const formatURL = (path) => { - const { baseUrl, ...opts } = getOptions(); - - return (baseUrl + path).replace(/{{[^{}]+}}/gi, (m) => { - return opts[m.replace(/[{}]+/gi, '')] || ''; - }); -}; - -const getResponseData = (promise) => promise.then((response) => response.data); - -/** - * This is a core function that create a request with native http/https module - */ -export const makeRequest = (method: string, path: string) => ( - data?: InputType, -): Promise => { - const uri = formatURL(path); - const fn = axios[method]; - - if (['post', 'put'].indexOf(method) > -1) { - return getResponseData(fn(uri, data || {})); - } - - const qstring = data ? '?' + qs.stringify((data as unknown) as qs.ParsedUrlQueryInput) : ''; - return getResponseData(fn(uri + qstring)); -}; diff --git a/src/index.ts b/src/index.ts index 68d8c1c..02583a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,4 @@ -export { getOptions, getOption, setOption } from './store'; -export * from './api'; +import Api from './entity/Api'; + +export const PRestAPI = Api; +export default Api; diff --git a/src/store.ts b/src/store.ts deleted file mode 100644 index eaf2394..0000000 --- a/src/store.ts +++ /dev/null @@ -1,63 +0,0 @@ -const globalOptions: PRestGlobalOptions = { - baseUrl: 'http://localhost:3000', -}; - -/** - * This function create a function that access a private object with PRest client options - * - * @param store - */ -export const accessStore = (store: T): AccessStoreFunction => () => store; - -/** - * This function create a function that acting as a getter for a private object - * - * @param store - */ -export const getFromStore = (store: T): GetFromStoreFunction => (key) => store[key]; - -/** - * This function create a function that acting as a setter for a private object - * - * @param store - */ -export const setInStore = (store: T): SetInStoreFunction => (key, value) => (store[key] = value); - -/** - * This function access `globalOptions` object and be able to return the entire object - * - * **Example** - * ``` - * import { getOptions } from 'prest-node'; - * - * const opts = getOptions('requestProtocol'); - * // opts.requestProtocol - * ``` - * - * @function - */ -export const getOptions = accessStore(globalOptions); - -/** - * This function get a property inside `globalOptions` object - * - * **Example** - * ``` - * import { getOptions } from 'prest-node'; - * - * const requestProtocol = getOptions('requestProtocol'); - * ``` - */ -export const getOption = getFromStore(globalOptions); - -/** - * This function set a property inside `globalOptions` object - * - * **Example** - * ``` - * import { setOption } from 'prest-node'; - * - * setOption('requestProtocol', 'https'); - * ``` - */ -export const setOption = setInStore(globalOptions); diff --git a/src/types/http.d.ts b/src/types/http.d.ts deleted file mode 100644 index a6bee7c..0000000 --- a/src/types/http.d.ts +++ /dev/null @@ -1 +0,0 @@ -type AxiosHttpMethods = 'get' | 'post' | 'delete' | 'put'; diff --git a/src/types/store.d.ts b/src/types/store.d.ts deleted file mode 100644 index d205da1..0000000 --- a/src/types/store.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -type PRestGlobalOptions = { - baseUrl: string; - db?: string; - schema?: string; -}; - -type AccessStoreFunction = () => T; -type GetFromStoreFunction = (x: keyof T) => T[keyof T]; -type SetInStoreFunction = (x: keyof T, y: T[keyof T]) => T[keyof T]; diff --git a/tests/unit/api.spec.ts b/tests/unit/api.spec.ts deleted file mode 100644 index 3ef597b..0000000 --- a/tests/unit/api.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -jest.mock('../../src/http', () => ({ - makeRequest: jest.fn().mockImplementation((method, path) => ({ method, path })), -})); - -import { showAllDbs, showAllSchemas, showAllTables, showAllTablesBySchema } from '~/api'; - -describe('src/api', () => { - it('should be call makeRequest for showAllDbs with correctly params', () => { - expect(showAllDbs).toHaveProperty('method', 'get'); - expect(showAllDbs).toHaveProperty('path', '/databases'); - }); - - it('should be call makeRequest for showAllSchemas with correctly params', () => { - expect(showAllSchemas).toHaveProperty('method', 'get'); - expect(showAllSchemas).toHaveProperty('path', '/schemas'); - }); - - it('should be call makeRequest for showAllTables with correctly params', () => { - expect(showAllTables).toHaveProperty('method', 'get'); - expect(showAllTables).toHaveProperty('path', '/tables'); - }); - - it('should be call makeRequest for showAllTablesBySchema with correctly params', () => { - expect(showAllTablesBySchema).toHaveProperty('method', 'get'); - expect(showAllTablesBySchema).toHaveProperty('path', '/{{db}}/{{schema}}'); - }); -}); diff --git a/tests/unit/entity/Api.spec.ts b/tests/unit/entity/Api.spec.ts new file mode 100644 index 0000000..6602c64 --- /dev/null +++ b/tests/unit/entity/Api.spec.ts @@ -0,0 +1,110 @@ +jest.mock('../../../src/entity/TableConnector'); + +import Api from '~/entity/Api'; +import TableConnector from '~/entity/TableConnector'; + +describe('entity/Api', () => { + const baseUrl = 'foo.bar'; + const api = new Api(baseUrl); + const fakeResponse = 'foo'; + const database = 'prest'; + const schema = 'public'; + const table = 'foo'; + const expectedUrl = `/${database}/${schema}/${table}`; + + beforeEach(() => { + api.call = jest.fn(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should call .database() method correctly', async () => { + (api.call as jest.Mock).mockResolvedValue(fakeResponse); + + const resp = await api.databases(); + expect(resp).toBe(fakeResponse); + + expect(api.call).toHaveBeenCalledTimes(1); + expect(api.call).toHaveBeenCalledWith('get', '/databases'); + }); + + it('should call .schemas() method correctly', async () => { + (api.call as jest.Mock).mockResolvedValue(fakeResponse); + + const resp = await api.schemas(); + expect(resp).toBe(fakeResponse); + + expect(api.call).toHaveBeenCalledTimes(1); + expect(api.call).toHaveBeenCalledWith('get', '/schemas'); + }); + + it('should call .tables() method correctly', async () => { + (api.call as jest.Mock).mockResolvedValue(fakeResponse); + + const resp = await api.tables(); + expect(resp).toBe(fakeResponse); + + expect(api.call).toHaveBeenCalledTimes(1); + expect(api.call).toHaveBeenCalledWith('get', '/tables'); + }); + + describe('.tableConnection', () => { + it('should call correctly for string param', () => { + (TableConnector as jest.Mock).mockImplementation(() => ({ fakeResponse })); + + expect(api.tableConnection(`${database}.${schema}.${table}`)).toEqual({ fakeResponse }); + expect(TableConnector).toHaveBeenCalledWith(baseUrl, expectedUrl); + }); + + it('should call correctly for splited params', () => { + (TableConnector as jest.Mock).mockImplementation(() => ({ fakeResponse })); + + expect(api.tableConnection(database, schema, table)).toEqual({ fakeResponse }); + expect(TableConnector).toHaveBeenCalledWith(baseUrl, expectedUrl); + }); + }); + + describe('.tablesByDBInSchema()', () => { + it('should call correctly for string param', async () => { + (api.call as jest.Mock).mockResolvedValue(fakeResponse); + + const resp = await api.tablesByDBInSchema(`${database}.${schema}`); + + expect(resp).toBe(fakeResponse); + expect(api.call).toHaveBeenCalledWith('get', `/${database}/${schema}`); + }); + + it('should call correctly for splited params', async () => { + (api.call as jest.Mock).mockResolvedValue(fakeResponse); + + const resp = await api.tablesByDBInSchema(database, schema); + + expect(resp).toBe(fakeResponse); + expect(api.call).toHaveBeenCalledWith('get', `/${database}/${schema}`); + }); + }); + + describe('.show()', () => { + it('should call correctly for string param', async () => { + (api.call as jest.Mock).mockResolvedValue(fakeResponse); + + const resp = await api.show(`${database}.${schema}.${table}`); + expect(resp).toBe(fakeResponse); + + expect(api.call).toHaveBeenCalledTimes(1); + expect(api.call).toHaveBeenCalledWith('get', '/show' + expectedUrl); + }); + + it('should call correctly for splited param', async () => { + (api.call as jest.Mock).mockResolvedValue(fakeResponse); + + const resp = await api.show(database, schema, table); + expect(resp).toBe(fakeResponse); + + expect(api.call).toHaveBeenCalledTimes(1); + expect(api.call).toHaveBeenCalledWith('get', '/show' + expectedUrl); + }); + }); +}); diff --git a/tests/unit/entity/Request.spec.ts b/tests/unit/entity/Request.spec.ts new file mode 100644 index 0000000..97ce63d --- /dev/null +++ b/tests/unit/entity/Request.spec.ts @@ -0,0 +1,19 @@ +jest.mock('axios'); + +import Request from '~/entity/Request'; +import axios from 'axios'; + +describe('entity/Request', () => { + it('should execute call and get the response', async () => { + const data = 'foo'; + const baseUrl = 'bar'; + (axios.get as jest.Mock).mockResolvedValue({ data }); + + const req = new Request(baseUrl); + const resp = await req.call('get', ''); + + expect(resp).toBe(data); + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith(baseUrl); + }); +}); diff --git a/tests/unit/entity/TableConnector.spec.ts b/tests/unit/entity/TableConnector.spec.ts new file mode 100644 index 0000000..863fe14 --- /dev/null +++ b/tests/unit/entity/TableConnector.spec.ts @@ -0,0 +1,24 @@ +import TableConnector from '~/entity/TableConnector'; + +describe('entity/TableConnector', () => { + const baseUrl = 'foo.bar'; + const dbPath = '/prest/public/foo'; + const conn = new TableConnector(baseUrl, dbPath); + const fakeResponse = 'foo'; + + beforeEach(() => { + conn.call = jest.fn().mockReturnValue(fakeResponse); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should execute .query() correctly', async () => { + const resp = await conn.query(); + + expect(resp).toBe(fakeResponse); + expect(conn.call).toHaveBeenCalledTimes(1); + expect(conn.call).toHaveBeenCalledWith('get', dbPath); + }); +}); diff --git a/tests/unit/http.spec.ts b/tests/unit/http.spec.ts deleted file mode 100644 index ec0c0bc..0000000 --- a/tests/unit/http.spec.ts +++ /dev/null @@ -1,132 +0,0 @@ -jest.mock('axios'); -jest.mock('../../src/store'); - -import axios from 'axios'; -import { makeRequest } from '~/http'; -import { getOptions } from '~/store'; - -describe('src/http/makeRequest', () => { - const fakeResponse = 'foo'; - const fakeUrl = '/bar'; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - ['post', 'put'].forEach((method) => { - it(`should makeRequest return a ${method} method`, async () => { - const path = '/test'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue({ baseUrl: fakeUrl }); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + path, {}); - expect(response).toBe(fakeResponse); - }); - - it(`should makeRequest return a ${method} with optin url method`, async () => { - const opts = { baseUrl: fakeUrl, x: 1 }; - const path = '/{{x}}'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue(opts); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + `/${opts.x}`, {}); - expect(response).toBe(fakeResponse); - }); - - it(`should makeRequest return a ${method} with optin url method without opt setted`, async () => { - const opts = { baseUrl: fakeUrl }; - const path = '/{{x}}'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue(opts); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + '/', {}); - expect(response).toBe(fakeResponse); - }); - - it(`should makeRequest return a ${method} with data`, async () => { - const opts = { baseUrl: fakeUrl, db: 'fizz' }; - const data = { fake: 1 }; - const path = '/{{db}}'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue(opts); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(data); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + `/${opts.db}`, data); - expect(response).toBe(fakeResponse); - }); - }); - - ['get', 'delete'].forEach((method) => { - it(`should makeRequest return a ${method} method`, async () => { - const path = '/test'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue({ baseUrl: fakeUrl }); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + path); - expect(response).toBe(fakeResponse); - }); - - it(`should makeRequest return a ${method} with optin url method`, async () => { - const opts = { baseUrl: fakeUrl, x: 1 }; - const path = '/{{x}}'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue(opts); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + `/${opts.x}`); - expect(response).toBe(fakeResponse); - }); - - it(`should makeRequest return a ${method} with optin url method without opt setted`, async () => { - const opts = { baseUrl: fakeUrl }; - const path = '/{{x}}'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue(opts); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + '/'); - expect(response).toBe(fakeResponse); - }); - - it(`should makeRequest return a ${method} with data`, async () => { - const opts = { baseUrl: fakeUrl, db: 'fizz' }; - const dataKey = 'fake'; - const data = { [dataKey]: 1 }; - const path = '/{{db}}'; - const req = makeRequest(method, path); - - (getOptions as jest.Mock).mockReturnValue(opts); - (axios[method] as jest.Mock).mockResolvedValue({ data: fakeResponse }); - const response = await req(data); - - expect(axios[method]).toHaveBeenCalledTimes(1); - expect(axios[method]).toHaveBeenCalledWith(fakeUrl + `/${opts.db}?${dataKey}=${data[dataKey]}`); - expect(response).toBe(fakeResponse); - }); - }); -}); diff --git a/tests/unit/store.spec.ts b/tests/unit/store.spec.ts deleted file mode 100644 index 814bf09..0000000 --- a/tests/unit/store.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { accessStore, getFromStore, setInStore } from '~/store'; - -describe('src/store', () => { - const requestProtocol = 'http'; - const opts: PRestGlobalOptions = { requestProtocol }; - - describe('accessStore', () => { - it('should acessStore for options', () => { - const getter = accessStore(opts); - expect(getter()).toEqual(opts); - }); - }); - - describe('getFromStore', () => { - it('should get a single parameter from options store', () => { - const getter = getFromStore(opts); - expect(getter('requestProtocol')).toBe(requestProtocol); - }); - }); - - describe('setInStore', () => { - it('should get a single parameter from options store', () => { - const setter = setInStore(opts); - const newVal = 'https'; - - expect(setter('requestProtocol', newVal)).toBe(newVal); - expect(opts).toHaveProperty('requestProtocol', newVal); - }); - }); - - describe('setInStore', () => { - it('should get a single parameter from options store', () => { - const setter = setInStore(opts); - const newVal = 'https'; - - expect(setter('requestProtocol', newVal)).toBe(newVal); - expect(opts).toHaveProperty('requestProtocol', newVal); - }); - }); -});