-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): adds default service to Application
- Loading branch information
Showing
15 changed files
with
204 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { | ||
CollectionTreeImplementation, | ||
ApplicationResolve, | ||
Application, | ||
ApplicationServices | ||
} from '~/types'; | ||
import { validate, traverse, isElementService, atPath } from '~/inspect'; | ||
import { addInterceptResponse } from './helpers/intercept-response'; | ||
import { mergeIntercepts } from './helpers/merge-intercepts'; | ||
import { handleChildren } from './helpers/handle-children'; | ||
import { getRoutes } from './helpers/get-routes'; | ||
import { toDeclaration } from '~/transform'; | ||
import { ApplicationCreateOptions } from './types'; | ||
import { createDefaults } from './defaults'; | ||
import { mergeDefault } from './helpers/merge-default'; | ||
|
||
/** | ||
* Validates and prepares a collection to be used: | ||
* - Merges service intercepts into each route resolver. | ||
* - Ensures services fail with a `PublicError` and resolve with `null` for empty responses. | ||
* - Ensures `ServerError` and `ClientError` error types exist on the collection declaration. | ||
* - Names and lifts inline types to the collection root if they have children services. | ||
*/ | ||
export function application<T extends CollectionTreeImplementation>( | ||
collection: T, | ||
options?: ApplicationCreateOptions | ||
): Application { | ||
const opts = Object.assign(createDefaults(), options); | ||
|
||
const merge = mergeDefault(collection, opts.default, opts.map); | ||
|
||
let tree: CollectionTreeImplementation = merge.collection; | ||
if (opts.validate) validate(tree, { as: 'implementation' }); | ||
tree = handleChildren(tree, opts.children ? 'lift' : 'remove'); | ||
tree = addInterceptResponse(tree); | ||
tree = mergeIntercepts(tree); | ||
|
||
const declaration = toDeclaration(tree); | ||
const routes = getRoutes(tree, opts.map); | ||
|
||
return { | ||
declaration, | ||
default: merge.default, | ||
routes, | ||
flatten(delimiter: string): ApplicationServices { | ||
if (!/[^\w]/.exec(delimiter)) { | ||
throw Error( | ||
`Delimiter must include at least a non wod character: ${delimiter}` | ||
); | ||
} | ||
|
||
const services: ApplicationServices = {}; | ||
traverse(declaration, (element, info, next) => { | ||
next(); | ||
if (isElementService(element)) { | ||
services[info.route.join(delimiter)] = { | ||
declaration: element, | ||
resolve: atPath( | ||
routes, | ||
info.route, | ||
(x: any): x is ApplicationResolve => typeof x === 'function' | ||
) | ||
}; | ||
} | ||
}); | ||
|
||
return services; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { ApplicationCreateOptions } from './types'; | ||
import { query, error } from '~/create'; | ||
import { PublicError } from '~/errors'; | ||
import { | ||
ServiceImplementation, | ||
ElementInfo, | ||
ApplicationResolve | ||
} from '~/types'; | ||
import { Observable } from 'rxjs'; | ||
|
||
export function createDefaults(): Required<ApplicationCreateOptions> { | ||
return { | ||
validate: true, | ||
children: true, | ||
default: query({ | ||
types: { | ||
errors: { | ||
NotFoundError: error({ label: 'ClientNotFound' }) | ||
} | ||
}, | ||
async resolve() { | ||
throw new PublicError( | ||
'NotFoundError', | ||
'ClientNotFound', | ||
null, | ||
null, | ||
true | ||
); | ||
} | ||
}), | ||
map(service: ServiceImplementation, info: ElementInfo): ApplicationResolve { | ||
return (data: any, context: any): Promise<any> | Observable<any> => { | ||
return service.resolve(data, context, info); | ||
}; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { | ||
CollectionTreeImplementation, | ||
UnaryApplicationResolve, | ||
QueryServiceImplementation | ||
} from '~/types'; | ||
import { services, collections } from '~/create'; | ||
import { normalize } from '~/transform'; | ||
import { ApplicationCreateMapFn } from '../types'; | ||
import { getRoutes } from './get-routes'; | ||
import { atPath } from '~/inspect'; | ||
|
||
export interface MergeDefault { | ||
collection: CollectionTreeImplementation; | ||
default: UnaryApplicationResolve; | ||
} | ||
|
||
export function mergeDefault( | ||
collection: CollectionTreeImplementation, | ||
service: QueryServiceImplementation, | ||
map: ApplicationCreateMapFn | ||
): MergeDefault { | ||
if (service.kind !== 'query') { | ||
throw Error(`Default service must be a query service`); | ||
} | ||
|
||
const normal = normalize(services({ default: service }), { | ||
skipReferences: Object.keys(collection.types) | ||
}); | ||
|
||
return { | ||
collection: collections(collection, { | ||
...normal, | ||
services: {} | ||
}), | ||
default: atPath( | ||
getRoutes(normal, map), | ||
['default'], | ||
(x: any): x is UnaryApplicationResolve => typeof x === 'function' | ||
) | ||
}; | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,97 +1,2 @@ | ||
import { | ||
CollectionTreeImplementation, | ||
ApplicationResolve, | ||
ServiceImplementation, | ||
ElementInfo, | ||
Application, | ||
ApplicationServices | ||
} from '~/types'; | ||
import { validate, traverse, isElementService, atPath } from '~/inspect'; | ||
import { addInterceptResponse } from './intercept-response'; | ||
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: | ||
* - Merges service intercepts into each route resolver. | ||
* - Ensures services fail with a `PublicError` and resolve with `null` for empty responses. | ||
* - Ensures `ServerError` and `ClientError` error types exist on the collection declaration. | ||
* - Names and lifts inline types to the collection root if they have children services. | ||
*/ | ||
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 = addInterceptResponse(tree); | ||
tree = mergeIntercepts(tree); | ||
|
||
const declaration = toDeclaration(tree); | ||
const routes = getRoutes(tree, opts.map); | ||
|
||
return { | ||
declaration, | ||
routes, | ||
flatten(delimiter: string): ApplicationServices { | ||
if (!/[^\w]/.exec(delimiter)) { | ||
throw Error( | ||
`Delimiter must include at least a non wod character: ${delimiter}` | ||
); | ||
} | ||
|
||
const services: ApplicationServices = {}; | ||
traverse(declaration, (element, info, next) => { | ||
next(); | ||
if (isElementService(element)) { | ||
services[info.route.join(delimiter)] = { | ||
declaration: element, | ||
resolve: atPath( | ||
routes, | ||
info.route, | ||
(x: any): x is ApplicationResolve => typeof x === 'function' | ||
) | ||
}; | ||
} | ||
}); | ||
|
||
return services; | ||
} | ||
}; | ||
} | ||
export * from './application'; | ||
export * from './types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { | ||
ServiceImplementation, | ||
ElementInfo, | ||
ApplicationResolve, | ||
QueryServiceImplementation | ||
} from '~/types'; | ||
|
||
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; | ||
/** | ||
* A default service for adapters to use when the route is non existent. | ||
* Defaults to a `ClientNotFound` error throwing service. | ||
*/ | ||
default?: QueryServiceImplementation; | ||
} | ||
|
||
export type ApplicationCreateMapFn<I = any, O = any, C = any> = ( | ||
service: ServiceImplementation<I, O, C>, | ||
info: ElementInfo | ||
) => ApplicationResolve<I, O, C>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.