@@ -57,15 +57,17 @@ It also adds support for standardized validation for request arguments, query st | |||
With NPM | |||
``` | |||
npm i -S @ojizero/portal | |||
npm i -S got @ojizero/portal | |||
``` | |||
Or if you're into Yarn | |||
``` | |||
yarn add @ojizero/portal | |||
yarn add got @ojizero/portal | |||
``` | |||
We separate installation of `got` and `portal` to prepare for later support of multiple internal clients, mainly to support browsers using `ky` without introducing breaking changes. | |||
## Usage | |||
Aimed to be used as a building block for API client libraries | |||
@@ -274,7 +276,7 @@ export interface Response { | |||
}, | |||
body: any, | |||
headers: IncomingHttpHeaders, | |||
_rawResponse: RawResponse, | |||
[Symbol.for('portal:symbols:raw-response')]: RawResponse, | |||
} | |||
``` | |||
@@ -4,10 +4,6 @@ This is a sample client build for Elasticsearch using Portal, is provides an API | |||
## Usage | |||
``` | |||
npm start # starts a docker container with elasticsearch instance | |||
node ./index.js # run the sample code | |||
npm stop # stop and remove the docker container | |||
```bash | |||
npm test # starts a docker image for elasticsearch, runs index.js then kill the container | |||
``` |
@@ -55,6 +55,6 @@ if (require.main === module) { | |||
{"a":{"test":"bulkoperation"}} | |||
` | |||
}) | |||
console.log({ getDocument: { stringifiedResponse: JSON.stringify(response) } }) | |||
console.log({ bulkOperation: { stringifiedResponse: JSON.stringify(response) } }) | |||
})() | |||
} |
@@ -6,7 +6,8 @@ | |||
"main": "index.js", | |||
"scripts": { | |||
"start": "docker run --publish '9200:9200' --name elastic_portal_demo --detach elasticsearch:6.5.4 && while ! curl localhost:9200; do sleep 10; done", | |||
"stop": "docker kill elastic_portal_demo && docker rm elastic_portal_demo" | |||
"stop": "docker kill elastic_portal_demo && docker rm elastic_portal_demo", | |||
"test": "npm start && node index.js && npm stop" | |||
}, | |||
"license": "MIT", | |||
"dependencies": { |
@@ -0,0 +1,3 @@ | |||
# Portal sample client - Generic rest API | |||
> TODO: |
@@ -1,23 +1,55 @@ | |||
import PortalClient, { Client, Config } from './client' | |||
import PortalClient, { Client, ClientFn, Config as ClientConfig } from './client' | |||
import methodGenerator, { MethodFactory } from './method' | |||
import resrouceGenerator, { ResourceFactory } from './resource' | |||
import got from 'got' | |||
let got: ClientFn | |||
try { | |||
got = require('got') | |||
} catch (_) {} | |||
function forNode (clientType: ClientType | undefined): clientType is NodeClient { | |||
return typeof clientType === 'undefined' || (clientType.type === 'node' || clientType.type === 'got') | |||
} | |||
function getClient (config: PortalConfig): ClientFn { | |||
if (forNode(config.client)) return got | |||
throw new Error(`Unspported client config ${config.client}`) | |||
} | |||
export interface ClientType { | |||
type: 'node' | 'got', // | 'browser' | 'ky' | 'custom', | |||
} | |||
export interface NodeClient extends ClientType { | |||
type: 'node' | 'got', | |||
} | |||
export interface PortalConfig { | |||
client?: NodeClient, | |||
} | |||
export type Config = ClientConfig & PortalConfig | |||
export const clientSymbol = Symbol.for('portal:symbols:client') | |||
export interface Portal { | |||
route: MethodFactory, | |||
resource: ResourceFactory, | |||
_client: Client, | |||
[clientSymbol]: Client, | |||
} | |||
export function createPortalClient (config: Config): Portal { | |||
const client = new PortalClient(got, config) | |||
const clientFn = getClient(config) | |||
const client = new PortalClient(clientFn, config) | |||
const portal: Portal = { | |||
route: methodGenerator(client), | |||
resource: resrouceGenerator(client), | |||
_client: client, | |||
[clientSymbol]: client, | |||
} | |||
return portal |
@@ -66,7 +66,7 @@ export class Resource { | |||
Object.defineProperty(this, route, { | |||
value: routeMethod, | |||
writable: false, | |||
enumerable: true, // TODO: is it okay to have them enumerable ? | |||
enumerable: true, | |||
}) | |||
// Add is aliased as set | |||
@@ -74,7 +74,7 @@ export class Resource { | |||
Object.defineProperty(this, 'set', { | |||
value: routeMethod, | |||
writable: false, | |||
enumerable: true, // TODO: is it okay to have them enumerable ? | |||
enumerable: true, | |||
}) | |||
} | |||
// Del is aliased as delete | |||
@@ -82,7 +82,7 @@ export class Resource { | |||
Object.defineProperty(this, 'delete', { | |||
value: routeMethod, | |||
writable: false, | |||
enumerable: true, // TODO: is it okay to have them enumerable ? | |||
enumerable: true, | |||
}) | |||
} | |||
}) | |||
@@ -108,18 +108,24 @@ export class Resource { | |||
return method(this.client) | |||
} | |||
setExtraMethods (extraMethods?: { [k:string]: any }) { | |||
setExtraMethods (extraMethods?: { [k:string]: MethodSpec }) { | |||
if (!extraMethods) return | |||
Object.entries(extraMethods) | |||
.forEach(([method, spec]) => { | |||
// TODO: should it be prefixed by basePath ? | |||
const sep = spec.path.startsWith('/') ? '' : '/' | |||
spec = { | |||
...spec, | |||
path: `${this.baseRoute}${sep}${spec.path}`, | |||
} | |||
const fn = this.methodFactory(spec) | |||
Object.defineProperty(this, method, { | |||
value: fn, | |||
writable: false, | |||
enumerable: true, // TODO: is it okay to have them enumerable ? | |||
enumerable: true, | |||
}) | |||
}) | |||
} |
@@ -12,6 +12,10 @@ const JOI_MAPPING: { [k: string]: SchemaLike } = { | |||
// array: Joi.array(), | |||
} | |||
const isNotRequiredRegex = /notrequired/i | |||
const isNotRequired = isNotRequiredRegex.test.bind(isNotRequiredRegex) | |||
function isObject (value: any): value is { [k: string]: any } { | |||
return !!value && typeof value === 'object' | |||
} | |||
@@ -45,15 +49,13 @@ export function transformSchema (schema: any): SchemaLike { | |||
const [schemaName, schemaOptions = ''] = (schema as string).split('|', 2) | |||
const isNotRequired = /notrequired/i | |||
schema = JOI_MAPPING[schemaName] | |||
if (typeof schema === 'undefined') { | |||
throw new Error(`Requested Joi validator ${schema} unsupported or undefined.`) | |||
} | |||
return isNotRequired.test(schemaOptions) ? schema : schema.required() | |||
return isNotRequired(schemaOptions) ? schema : schema.required() | |||
} | |||
export default transformSchema |