Skip to content

Commit

Permalink
feat(core/generate): client generates Application client factory by d…
Browse files Browse the repository at this point in the history
…efault
  • Loading branch information
rafamel committed Oct 24, 2019
1 parent c3538b7 commit f04a018
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 41 deletions.
14 changes: 4 additions & 10 deletions packages/core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
},
"dependencies": {
"@types/json-schema": "^7.0.3",
"ajv": "^6.10.2",
"camelcase": "^5.3.1",
"json-schema-to-typescript": "^7.1.0",
"lodash.isequal": "^4.5.0"
Expand Down
106 changes: 75 additions & 31 deletions packages/core/src/generate/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import {
CollectionTree,
CollectionTreeImplementation,
Service,
ElementInfo
ElementInfo,
RequestType
} from '~/types';
import { typings } from './typings';
import { replace, normalize } from '~/transform';
import { isElementService, isServiceSubscription } from '~/inspect';
import { application } from '~/application';
import Ajv from 'ajv';
import draft04 from 'ajv/lib/refs/json-schema-draft-04.json';

const ajv = new Ajv({ schemaId: 'id', logger: false });
ajv.addMetaSchema(draft04);

export interface ClientGenerateOptions {
/**
Expand All @@ -23,6 +29,9 @@ export interface ClientGenerateOptions {
* Enables head comments disabling *tslint* and *eslint*. Default: `true`.
*/
headComments?: boolean;
}

export interface ClientGenerateCustomize {
/**
* An additional string to include at the top of the file.
*/
Expand All @@ -31,21 +40,50 @@ export interface ClientGenerateOptions {
* Maps collection default export.
*/
mapDefault?: (str: string) => string;
/**
* Maps function bodies for each service.
*/
mapService: (service: Service, info: ElementInfo) => string;
}

export async function client(
collection: CollectionTree | Promise<CollectionTree>,
resolver: (service: Service, info: ElementInfo) => string,
options?: ClientGenerateOptions
options?: ClientGenerateOptions | null,
customize?: ClientGenerateCustomize
): Promise<string> {
const opts = {
typescript: true,
write: null,
headComments: true,
headInclude: '',
mapDefault: (str: string): string => str,
...options
};
const custom = customize
? Object.assign(
{ headInclude: '', mapDefault: (x: string) => x },
customize
)
: {
headInclude: opts.typescript
? `import { Application } from '@karmic/core';`
: undefined,
mapDefault(str: string): string {
let fn = `function createClient(application`;
fn += opts.typescript ? `: Application, ` : `, `;
fn += `getContext`;
if (opts.typescript) fn += `: () => any`;
fn += ` = () => ({})) {\n`;
fn += ` const app = application.flatten(':');\n\n`;
fn += ` return ` + str.replace(/\n/g, '\n ');
fn += `}`;
return fn;
},
mapService(service: Service, info: ElementInfo): string {
let body = `return app['${info.route.join(':')}']`;
body += `.resolve(data, getContext())`;
body += opts.typescript ? ` as any;` : `;`;
return body;
}
};

let content = '';
collection = await collection;
Expand All @@ -63,39 +101,45 @@ export async function client(
if (!isElementService(element)) return element;
return {
...element,
resolve: resolver(element as any, info)
resolve: custom.mapService(element as any, info)
};
});

let importObservable = false;
const [start, end] = [0, 1].map(() =>
(String(Math.random()) + String(Math.random())).replace(/\./g, '')
);
const { routes } = application(
normalize(source) as CollectionTreeImplementation,
{
validate: false,
children: true,
map(service): any {
let resolve = '';
resolve += start + `function resolve(data`;
resolve += opts.typescript ? `: ${service.types.request}` : ``;
resolve += `)`;
if (opts.typescript) {
const isSubscription = isServiceSubscription(service);
if (isSubscription) importObservable = true;
resolve += isSubscription
? `: Observable<${service.types.response}>`
: `: Promise<${service.types.response}>`;
}
resolve += ` { `;
resolve += service.resolve;
resolve += ` }` + end;

return resolve;
const normal = normalize(source) as CollectionTreeImplementation;
const { routes } = application(normal, {
validate: false,
children: true,
map(service): any {
const requestType = normal.types[
service.types.request as string
] as RequestType;
const emptyObjectValid = ajv.compile(requestType.schema)({});

let resolve = '';
resolve += start + `function resolve(data`;
resolve += opts.typescript ? `: ${service.types.request}` : ``;
resolve += emptyObjectValid ? ` = {}` : ``;

resolve += `)`;
if (opts.typescript) {
const isSubscription = isServiceSubscription(service);
if (isSubscription) importObservable = true;
resolve += isSubscription
? `: Observable<${service.types.response}>`
: `: Promise<${service.types.response}>`;
}
resolve += ` { `;
resolve += service.resolve;
resolve += ` }` + end;

return resolve;
}
);
});

let head = '';
if (opts.headComments) {
Expand All @@ -104,13 +148,13 @@ export async function client(
'/* tslint:disable */\n' +
'/* This file was automatically generated. DO NOT MODIFY IT BY HAND. */\n';
}
if (opts.headInclude) head += `\n` + opts.headInclude;
if (custom.headInclude) head += `\n` + custom.headInclude;
if (importObservable) head += `\nimport { Observable } from 'rxjs';`;
head += opts.headInclude || importObservable ? `\n\n` : `\n`;
head += custom.headInclude || importObservable ? `\n\n` : `\n`;

content = head + content;
content += `export default `;
content += opts.mapDefault(
content += custom.mapDefault(
JSON.stringify(routes, null, 2)
.replace(new RegExp('"' + start, 'g'), '')
.replace(new RegExp(end + '"', 'g'), '')
Expand Down

0 comments on commit f04a018

Please sign in to comment.