Skip to content

Commit

Permalink
feat(core): rewrites Types as Exception, Schema, and Children
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Nov 23, 2019
1 parent 39faca6 commit b373082
Show file tree
Hide file tree
Showing 57 changed files with 1,621 additions and 1,343 deletions.
28 changes: 28 additions & 0 deletions packages/core/src/PublicError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ExceptionLabel } from '~/types';

export class PublicError extends Error {
public id: string;
public label: ExceptionLabel;
public source?: Error;
public constructor(
id: string,
label: ExceptionLabel,
source?: Error | null,
message?: string | null,
clear?: boolean
) {
super(message || '');
this.id = id;
this.label = label;
this.source = source || undefined;
if (clear) this.stack = `${this.name}: ${this.message}`;
}
public get name(): string {
return 'PublicError';
}
public get root(): PublicError {
return this.source instanceof PublicError && this.source !== this
? this.source.root
: this;
}
}
1 change: 1 addition & 0 deletions packages/core/src/classes/Application/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export class Application {
Collection.create(this.declaration).traverse((element, info, next) => {
next();
if (isElementService(element)) {
if (!info.route) throw Error(`Expected route on path: ${info.path}`);
services[info.route.join(delimiter)] = {
declaration: element as any,
resolve: atPath(
Expand Down
12 changes: 7 additions & 5 deletions packages/core/src/classes/Application/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { PublicError } from '~/errors';
import { ServiceElementImplementation, ElementInfo } from '~/types';
import { ElementInfo, ServiceImplementation } from '~/types';
import { Observable } from 'rxjs';
import { ApplicationCreateOptions, ApplicationResolve } from './definitions';
import { Type } from '../Type';
import { Service } from '../Service';
import { item } from '~/utils/item';
import { Exception } from '../Exception';
import { PublicError } from '~/PublicError';

export function createDefaults(): Required<ApplicationCreateOptions> {
return {
validate: true,
children: true,
fallback: Service.query({
errors: [item('NotFoundError', Type.error({ label: 'ClientNotFound' }))],
exceptions: [
item('NotFoundError', Exception.create({ label: 'ClientNotFound' }))
],
async resolve() {
throw new PublicError(
'NotFoundError',
Expand All @@ -27,7 +29,7 @@ export function createDefaults(): Required<ApplicationCreateOptions> {
}

export function defaultMap(
service: ServiceElementImplementation,
service: ServiceImplementation,
info: ElementInfo
): ApplicationResolve {
return (data: any, context: any): Promise<any> | Observable<any> => {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/classes/Application/definitions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Observable } from 'rxjs';
import {
CollectionTreeDeclaration,
ServiceElementDeclaration,
ServiceDeclaration,
QueryServiceImplementation,
ServiceElementImplementation,
ServiceImplementation,
ElementInfo
} from '~/types';

Expand Down Expand Up @@ -36,8 +36,8 @@ export interface ApplicationCreateOptions {
}

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

/* Types */
Expand All @@ -50,7 +50,7 @@ export type ApplicationServices = Record<string, ApplicationService>;
export interface ApplicationService<
R extends ApplicationResolve = ApplicationResolve
> {
declaration: ServiceElementDeclaration;
declaration: ServiceDeclaration;
resolve: R;
}

Expand Down
62 changes: 25 additions & 37 deletions packages/core/src/classes/Application/helpers/get-routes.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,42 @@
import {
CollectionTreeImplementation,
ServiceElementImplementation
} from '~/types';
import { CollectionTreeImplementation, ServiceImplementation } from '~/types';
import {
ApplicationCreateOptionsMapFn,
ApplicationRoutes
} from '../definitions';
import { Collection } from '../../Collection';
import {
isElementService,
isElementType,
isTypeResponse,
isElementTree
isElementTree,
isTreeCollection
} from '~/inspect/is';

export function getRoutes(
collection: Collection<CollectionTreeImplementation>,
map: ApplicationCreateOptionsMapFn
): ApplicationRoutes {
const responses: ApplicationRoutes = {};
const routes: any = collection.replace((element, info, next): any => {
if (isElementService(element)) {
return map(element as ServiceElementImplementation, 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 routes: any = collection.replace(
(element, { path, route }, next): any => {
if (isElementService(element)) {
if (!route) {
throw Error(`Expected route on path: ${path}`);
}
return map(element as ServiceImplementation, { path, route });
}
const name = info.route[info.route.length - 1];
const children: ApplicationRoutes = {};
for (const [key, service] of entries) {
children[key] = map(service as ServiceElementImplementation, {
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;
});
element = next(element);
if (!isElementTree(element)) return element;
return isTreeCollection(element)
? {
...element.scopes,
...element.services,
...element.children
}
: {
...element.scopes,
...element.services
};
}
);

return { ...responses, ...routes };
return routes;
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
import { CollectionTreeImplementation } from '~/types';
import { catchError, map } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { PublicError, CollectionError } from '~/errors';
import { Type } from '../../Type';
import { Collection } from '../../Collection';
import { Intercept } from '../../Intercept';
import { Exception } from '../../Exception';
import { PublicError } from '~/PublicError';

export function addInterceptResponse(
collection: Collection<CollectionTreeImplementation>
): Collection<CollectionTreeImplementation> {
const errors = {
ServerError: Type.error({ label: 'ServerError' }),
ClientError: Type.error({ label: 'ClientError' })
ServerError: Exception.create({ label: 'ServerError' }),
ClientError: Exception.create({ label: 'ClientError' })
};

const tree = Collection.create({
...collection,
types: { ...errors, ...collection.types }
});

const tree = Collection.merge(collection, Collection.exceptions(errors));
return tree.intercept(
[
Intercept.create({
errors: Object.keys(errors),
exceptions: Object.keys(errors),
factory: () => (data, context, info, next) => {
return next(data).pipe(
map((value) => (value === undefined ? null : value)),
catchError((err) =>
throwError(
err instanceof PublicError
? err
: new CollectionError(tree, 'ServerError')
err instanceof PublicError ? err : tree.error('ServerError')
)
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export function mergeFallback(
}

const lifted = Collection.services({ fallback }).lift({
skipReferences: Object.keys(collection.types)
skipReferences: Object.keys(collection.exceptions).concat(
Object.keys(collection.schemas)
)
});

return {
Expand Down
20 changes: 13 additions & 7 deletions packages/core/src/classes/Application/helpers/merge-intercepts.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
ServiceElementImplementation,
ServiceImplementation,
CollectionTreeUnion,
CollectionTreeImplementation
} from '~/types';
import { Observable, from } from 'rxjs';
import { subscribe } from 'promist';
import { isElementService, isServiceImplementation } from '~/inspect/is';
import { schemas } from '~/inspect/schemas';
import { mergeServiceErrors } from '~/transform/merge';
import { mergeServiceExceptions } from '~/transform/merge';
import { Collection } from '../../Collection';
import { Intercept } from '../../Intercept';

Expand All @@ -23,23 +23,26 @@ export function mergeIntercepts(
}

export function serviceIntercepts(
service: ServiceElementImplementation,
service: ServiceImplementation,
collection: CollectionTreeUnion
): ServiceElementImplementation {
): ServiceImplementation {
const intercepts = service.intercepts;
delete service.intercepts;
if (!intercepts || !intercepts.length) return service;

const intercept = Intercept.allOf(intercepts);
const interceptFn = intercept.factory(schemas(service, collection));
const interceptFn = intercept.factory(schemas(collection, service));

switch (service.kind) {
case 'query':
case 'mutation': {
const resolve = service.resolve;
return {
...service,
errors: mergeServiceErrors(service.errors, intercept.errors),
exceptions: mergeServiceExceptions(
service.exceptions,
intercept.exceptions
),
resolve(data: any, context, info): Promise<any> {
return subscribe(
interceptFn(
Expand All @@ -58,7 +61,10 @@ export function serviceIntercepts(
const resolve = service.resolve;
return {
...service,
errors: mergeServiceErrors(service.errors, intercept.errors),
exceptions: mergeServiceExceptions(
service.exceptions,
intercept.exceptions
),
resolve(data: any, context, info): Observable<any> {
return interceptFn(
data,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import { CollectionTreeImplementation } from '~/types';
import { Collection } from '../../Collection';
import { isElementType, isTypeResponse } from '~/inspect/is';

export function removeChildren(
collection: Collection<CollectionTreeImplementation>
): Collection<CollectionTreeImplementation> {
return collection.replace((element, info, next) => {
element = next(element);
if (isElementType(element) && isTypeResponse(element) && element.children) {
const { children, ...other } = element;
return other;
}
return element;
});
return new Collection({ ...collection, children: {} });
}
34 changes: 34 additions & 0 deletions packages/core/src/classes/Children.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ChildrenUnion, ChildrenServicesUnion } from '~/types';
import { Element } from './Element';

export type ChildrenCreateInput<A extends ChildrenServicesUnion> = Omit<
ChildrenUnion<A>,
'kind'
>;

export class Children<T extends ChildrenUnion = ChildrenUnion> extends Element<
T
> {
public static create<A extends ChildrenServicesUnion>(
children: ChildrenCreateInput<A>
): Children<ChildrenUnion<A>> {
return new Children({
kind: 'children',
...children
});
}
public readonly schemas: T['schemas'];
public readonly services: T['services'];
public constructor(exception: T) {
super(exception.kind);
this.schemas = exception.schemas;
this.services = exception.services;
}
public element(): T {
return {
kind: this.kind,
schemas: this.schemas,
services: this.services
} as T;
}
}

0 comments on commit b373082

Please sign in to comment.