Skip to content

Commit

Permalink
feat(core): rewrites a large part of karmic's core
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Oct 20, 2019
1 parent 9a7918c commit 66f33e4
Show file tree
Hide file tree
Showing 43 changed files with 1,299 additions and 991 deletions.
52 changes: 52 additions & 0 deletions packages/core/src/application/get-routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
CollectionTreeImplementation,
ApplicationRoutes,
ServiceImplementation
} from '~/types';
import { replace } from '~/transform';
import {
isElementService,
isElementType,
isTypeResponse,
isElementTree
} from '~/inspect';
import { ApplicationCreateMapFn } from './index';

export function getRoutes(
collection: CollectionTreeImplementation,
map: ApplicationCreateMapFn
): ApplicationRoutes {
const responses: ApplicationRoutes = {};
const routes: any = replace(collection, (element, info, next): any => {
if (isElementService(element)) {
return map(element as ServiceImplementation, info);
}
if (isElementType(element)) {
if (!isTypeResponse(element) || !element.children) return element;

const entries = Object.entries(element.children);
if (!entries.length) return element;

if (info.route.length > 1) {
throw Error(`Type with children not at root: ${info.path}`);
}
const name = info.route[info.route.length - 1];
const children: ApplicationRoutes = {};
for (const [key, service] of entries) {
children[key] = map(service as ServiceImplementation, {
route: info.route.concat([key]),
path: info.path.concat(['children', key])
});
}
responses[name] = { ...children };
return element;
}

element = next(element);
return isElementTree(element)
? { ...element.scopes, ...element.services }
: element;
});

return { ...responses, ...routes };
}
36 changes: 36 additions & 0 deletions packages/core/src/application/handle-children.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { normalize, replace } from '~/transform';
import { isTypeResponse, isElementType } from '~/inspect';
import { CollectionTreeImplementation } from '~/types';

export function handleChildren(
collection: CollectionTreeImplementation,
mode: 'lift' | 'remove'
): CollectionTreeImplementation {
switch (mode) {
case 'lift': {
return normalize(collection, {
liftInlineType(type) {
if (!isTypeResponse(type) || !type.children) return false;
return Object.keys(type.children).length > 0;
}
});
}
case 'remove': {
return replace(collection, (element, info, next) => {
element = next(element);
if (
isElementType(element) &&
isTypeResponse(element) &&
element.children
) {
const { children, ...other } = element;
return other;
}
return element;
}) as CollectionTreeImplementation;
}
default: {
throw Error(`Expect "lift" or "remove" as mode: ${mode}`);
}
}
}
68 changes: 68 additions & 0 deletions packages/core/src/application/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
CollectionTreeImplementation,
ApplicationResolve,
ServiceImplementation,
ElementInfo,
Application
} from '~/types';
import { validate } from '~/inspect';
import { addInterceptErrors } from './intercept-errors';
import { mergeIntercepts } from './merge-intercepts';
import { handleChildren } from './handle-children';
import { getRoutes } from './get-routes';
import { toDeclaration } from '~/transform';

export interface ApplicationCreateOptions {
/**
* Whether the collection should be validated - see `validate`. Default: `true`.
*/
validate?: boolean;
/**
* Whether to include response types with children as routes. Default: `true`.
*/
children?: boolean;
/**
* Maps a service to its route resolver.
*/
map?: ApplicationCreateMapFn;
}

export type ApplicationCreateMapFn<I = any, O = any, C = any> = (
service: ServiceImplementation<I, O, C>,
info: ElementInfo
) => ApplicationResolve<I, O, C>;

/**
* Validates and prepares a collection to be used:
* - Adds `ServerError` and `ClientError` error types, if non existent, to the collection declaration.
* - Handles children adequately according to whether they have children services.
* - Merges service intercepts into each route resolver, and makes them fail with `PublicError`s, if they don't already do.
*/
export function application<T extends CollectionTreeImplementation>(
collection: T,
options?: ApplicationCreateOptions
): Application {
const opts = Object.assign(
{
validate: true,
children: true,
map(service: ServiceImplementation, info: ElementInfo) {
return (data: any, context: any) => {
return service.resolve(data, context, info);
};
}
},
options
);

let tree: CollectionTreeImplementation = collection;
if (opts.validate) validate(tree, { as: 'implementation' });
tree = handleChildren(tree, opts.children ? 'lift' : 'remove');
tree = addInterceptErrors(tree);
tree = mergeIntercepts(tree);

return {
declaration: toDeclaration(tree),
routes: getRoutes(tree, opts.map)
};
}
43 changes: 43 additions & 0 deletions packages/core/src/application/intercept-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CollectionTreeImplementation } from '~/types';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { PublicError, CollectionError } from '~/errors';
import { error, intercepts, intercept } from '~/create';

export function addInterceptErrors(
collection: CollectionTreeImplementation
): CollectionTreeImplementation {
const errors = {
ServerError: error({ code: 'ServerError' }),
ClientError: error({ code: 'ClientError' })
};

const tree = {
...collection,
types: { ...errors, ...collection.types }
};

return intercepts(
tree,
[
intercept({
errors: Object.keys(errors).reduce(
(acc, key) => Object.assign(acc, { [key]: key }),
{}
),
factory: () => (data, context, info, next) => {
return next(data).pipe(
catchError((err) =>
throwError(
err instanceof PublicError
? err
: new CollectionError(tree, 'ServerError')
)
)
);
}
})
],
{ prepend: true }
);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import { ServiceImplementation, Type, CollectionTree } from '~/types';
import {
ServiceImplementation,
Type,
CollectionTree,
CollectionTreeImplementation
} from '~/types';
import { mergeServiceErrors } from '~/utils';
import { Observable, from } from 'rxjs';
import { allof } from '../intercepts';
import { isTypeRequest, isTypeResponse } from '~/inspect';
import {
isTypeRequest,
isTypeResponse,
isElementService,
isServiceImplementation
} from '~/inspect';
import { replace } from '~/transform';
import { allof } from '~/create';

export default function serviceIntercepts(
export function mergeIntercepts(
collection: CollectionTreeImplementation
): CollectionTreeImplementation {
return replace(collection, (element, info, next) => {
element = next(element);
return isElementService(element) && isServiceImplementation(element)
? serviceIntercepts(element, collection)
: element;
}) as CollectionTreeImplementation;
}

export function serviceIntercepts(
service: ServiceImplementation,
collection: CollectionTree
): ServiceImplementation {
Expand Down Expand Up @@ -40,9 +62,9 @@ export default function serviceIntercepts(
...service.types,
errors: mergeServiceErrors(service.types.errors, intercept.errors)
},
resolve(data: any, context: any): Promise<any> {
return interceptFn(data, context, (data: any) => {
return from(resolve.call(this, data, context));
resolve(data: any, context, info): Promise<any> {
return interceptFn(data, context, info, (data: any) => {
return from(resolve.call(this, data, context, info));
}).toPromise();
}
};
Expand All @@ -55,9 +77,9 @@ export default function serviceIntercepts(
...service.types,
errors: mergeServiceErrors(service.types.errors, intercept.errors)
},
resolve(data: any, context: any): Observable<any> {
return interceptFn(data, context, (data: any) => {
return resolve.call(this, data, context);
resolve(data: any, context, info): Observable<any> {
return interceptFn(data, context, info, (data: any) => {
return resolve.call(this, data, context, info);
});
}
};
Expand Down
87 changes: 0 additions & 87 deletions packages/core/src/create/application/index.ts

This file was deleted.

0 comments on commit 66f33e4

Please sign in to comment.