-
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): rewrites a large part of karmic's core
- Loading branch information
Showing
43 changed files
with
1,299 additions
and
991 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,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 }; | ||
} |
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,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}`); | ||
} | ||
} | ||
} |
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,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) | ||
}; | ||
} |
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,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 } | ||
); | ||
} |
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 was deleted.
Oops, something went wrong.
Oops, something went wrong.