-
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 create application and allof for intercepts
- Loading branch information
Showing
16 changed files
with
745 additions
and
105 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,72 @@ | ||
import { | ||
CollectionTreeImplementation, | ||
CollectionTreeApplication, | ||
CollectionTree, | ||
TreeTypes | ||
} from '~/types'; | ||
import clone from 'lodash.clonedeep'; | ||
import { traverse, isElementType } from '~/utils'; | ||
import camelcase from 'camelcase'; | ||
import serviceIntercepts from './service-intercepts'; | ||
import { mergeServiceTypes } from './merge'; | ||
|
||
export default function application( | ||
collection: CollectionTree | CollectionTreeImplementation, | ||
options?: { | ||
prefixScope?: boolean; | ||
prefixInlineError?: boolean; | ||
transform?: (str: string, explicit: boolean) => string; | ||
} | ||
): CollectionTreeApplication { | ||
collection = clone(collection); | ||
|
||
const opts = Object.assign( | ||
{ | ||
prefixScope: true, | ||
prefixInlineError: false, | ||
transform: (str: string) => camelcase(str, { pascalCase: true }) | ||
}, | ||
options | ||
); | ||
|
||
const types = { | ||
source: collection.types, | ||
application: Object.entries(collection.types).reduce( | ||
(acc: TreeTypes, [name, type]) => { | ||
const pascal = opts.transform(name, true); | ||
if (Object.hasOwnProperty.call(acc, pascal)) { | ||
throw Error(`Type name collision: ${pascal}`); | ||
} | ||
acc[pascal] = type; | ||
return acc; | ||
}, | ||
{} | ||
) | ||
}; | ||
|
||
traverse(collection, { children: false, inline: false }, (element, path) => { | ||
const name = opts.transform(path.slice(-1)[0], true); | ||
|
||
if (isElementType(element)) { | ||
if (element.kind !== 'response' || !element.children) return; | ||
for (const [key, service] of Object.entries(element.children)) { | ||
const fullName = name + opts.transform(key, false); | ||
serviceIntercepts(fullName, service, types.source); | ||
mergeServiceTypes(fullName, service, types, opts); | ||
} | ||
} else { | ||
const fullName = | ||
opts.prefixScope && path[path.length - 3] | ||
? opts.transform(path[path.length - 3], false) + name | ||
: name; | ||
|
||
serviceIntercepts(fullName, element, types.source); | ||
mergeServiceTypes(fullName, element, types, opts); | ||
} | ||
}); | ||
|
||
return { | ||
...collection, | ||
types: types.application | ||
} as CollectionTreeApplication; | ||
} |
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,108 @@ | ||
import { TreeTypes, Service, ServiceErrors, Type } from '~/types'; | ||
|
||
export function mergeServiceTypes( | ||
name: string, | ||
service: Service, | ||
types: { source: TreeTypes; application: TreeTypes }, | ||
options: { | ||
prefixInlineError: boolean; | ||
transform: (str: string, explicit: boolean) => string; | ||
} | ||
): void { | ||
const { prefixInlineError, transform } = options; | ||
|
||
for (const kind of ['request', 'response'] as ['request', 'response']) { | ||
const type = service.types[kind]; | ||
if (typeof type === 'string') { | ||
service.types[kind] = checkSourceType(kind, type, types, options); | ||
} else { | ||
const pascal = name + transform('R' + kind.slice(1), false); | ||
mergeServiceType(kind, pascal, type, types, options); | ||
service.types[kind] = pascal; | ||
} | ||
} | ||
|
||
const errors: ServiceErrors = {}; | ||
for (const [key, error] of Object.entries(service.types.errors)) { | ||
if (typeof error === 'string') { | ||
let id = checkSourceType('error', error, types, options); | ||
if (key !== error) { | ||
id = (prefixInlineError ? name : '') + transform(key, true); | ||
mergeServiceType('error', id, types.source[error], types, options); | ||
} | ||
errors[id] = id; | ||
} else { | ||
const id = (prefixInlineError ? name : '') + transform(key, true); | ||
mergeServiceType('error', id, error, types, options); | ||
errors[id] = id; | ||
} | ||
} | ||
service.types.errors = errors; | ||
} | ||
|
||
export function mergeServiceType( | ||
kind: string, | ||
name: string, | ||
type: Type, | ||
types: { source: TreeTypes; application: TreeTypes }, | ||
options: { | ||
prefixInlineError: boolean; | ||
transform: (str: string, explicit: boolean) => string; | ||
} | ||
): void { | ||
if (type.kind !== kind) { | ||
throw Error(`Invalid inline type kind: ${name}`); | ||
} | ||
|
||
switch (type.kind) { | ||
case 'error': | ||
case 'request': { | ||
if (Object.hasOwnProperty.call(types.application, name)) { | ||
throw Error(`Inline type name collision: ${name}`); | ||
} | ||
types.application[name] = type; | ||
return; | ||
} | ||
case 'response': { | ||
if (Object.hasOwnProperty.call(types.application, name)) { | ||
throw Error(`Inline type name collision: ${name}`); | ||
} | ||
types.application[name] = type; | ||
|
||
if (type.children) { | ||
for (const [key, service] of Object.entries(type.children)) { | ||
mergeServiceTypes( | ||
name + options.transform(key, false), | ||
service, | ||
types, | ||
options | ||
); | ||
} | ||
} | ||
return; | ||
} | ||
default: { | ||
throw Error(`Invalid kind for type: ${name}`); | ||
} | ||
} | ||
} | ||
|
||
export function checkSourceType( | ||
kind: string, | ||
name: string, | ||
types: { source: TreeTypes; application: TreeTypes }, | ||
options: { transform: (str: string, explicit: boolean) => string } | ||
): string { | ||
const pascal = options.transform(name, true); | ||
if ( | ||
!Object.hasOwnProperty.call(types.source, name) || | ||
!Object.hasOwnProperty.call(types.application, pascal) | ||
) { | ||
throw Error(`Collection lacks referenced type: ${name}`); | ||
} | ||
if (types.application[pascal].kind !== kind) { | ||
throw Error(`Invalid type kind reference: ${pascal}`); | ||
} | ||
|
||
return pascal; | ||
} |
87 changes: 87 additions & 0 deletions
87
packages/core/src/create/application/service-intercepts.ts
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,87 @@ | ||
import { | ||
Service, | ||
ServiceImplementation, | ||
TreeTypes, | ||
Type, | ||
Observable | ||
} from '~/types'; | ||
import { | ||
isServiceImplementation, | ||
isTypeRequest, | ||
isTypeResponse, | ||
mergeServiceErrors | ||
} from '~/utils'; | ||
import { from } from 'rxjs'; | ||
import { allof } from '../intercepts'; | ||
|
||
export default function serviceIntercepts( | ||
name: string, | ||
service: Service | ServiceImplementation, | ||
types: TreeTypes | ||
): void { | ||
if (!isServiceImplementation(service)) return; | ||
|
||
const intercepts = service.intercepts; | ||
delete service.intercepts; | ||
if (!intercepts || !intercepts.length) return; | ||
|
||
const request: Type | undefined = | ||
typeof service.types.request === 'string' | ||
? types[service.types.request] | ||
: service.types.request; | ||
const response: Type | undefined = | ||
typeof service.types.response === 'string' | ||
? types[service.types.response] | ||
: service.types.response; | ||
if ( | ||
!request || | ||
!response || | ||
!isTypeRequest(request) || | ||
!isTypeResponse(response) | ||
) { | ||
throw Error(`Invalid type kind for service: ${name}`); | ||
} | ||
|
||
const intercept = allof(intercepts); | ||
const interceptFn = intercept.factory({ | ||
request: request.schema, | ||
response: response.schema | ||
}); | ||
|
||
switch (service.kind) { | ||
case 'query': | ||
case 'mutation': { | ||
const resolve = service.resolve; | ||
Object.assign(service, { | ||
types: { | ||
...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)); | ||
}).toPromise(); | ||
} | ||
}); | ||
return; | ||
} | ||
case 'subscription': { | ||
const resolve = service.resolve; | ||
Object.assign(service, { | ||
types: { | ||
...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); | ||
}); | ||
} | ||
}); | ||
return; | ||
} | ||
default: { | ||
throw Error(`Invalid kind for type`); | ||
} | ||
} | ||
} |
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.