Skip to content

Commit

Permalink
feat(core): adds flow control via next for replace and traverse
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Oct 15, 2019
1 parent 1b0228e commit e434dbb
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 272 deletions.
47 changes: 25 additions & 22 deletions packages/core/src/create/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { PublicError, CollectionError } from '~/errors';
import { error } from '../types';
import { traverse, isElementType, isElementService } from '~/inspect';
import {
traverse,
isElementType,
isElementService,
isElementTree
} from '~/inspect';

// TODO: validate collection object (ajv) + check schemas are valid
// TODO: adapters rely on resolve() existing on all services. Separate normalization from application?
Expand Down Expand Up @@ -102,30 +107,28 @@ export function application(
};

collection = clone(collection);
traverse(
collection,
(element, { path }) => {
const name = opts.transform(path.slice(-1)[0], true);
traverse(collection, (element, next, { path }) => {
if (isElementTree(element)) return next();

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 if (isElementService(element)) {
const fullName =
opts.prefixScope && path[path.length - 3]
? opts.transform(path[path.length - 3], false) + name
: name;
const name = opts.transform(path.slice(-1)[0], true);

serviceIntercepts(fullName, element, types.source);
mergeServiceTypes(fullName, element, types, opts);
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);
}
},
{ deep: true, children: false, inline: false }
);
} else if (isElementService(element)) {
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,
Expand Down
29 changes: 15 additions & 14 deletions packages/core/src/create/intercepts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { isElementService, isServiceImplementation } from '~/inspect';
import { replace } from '~/transform';

export interface InterceptsCreateOptions {
/**
* Whether intercepts should be prepended or apended to the existing ones.
*/
prepend?: boolean;
}

Expand All @@ -24,20 +27,18 @@ export function intercepts<T extends CollectionTree>(
): T {
const opts = Object.assign({ prepend: true }, options);

return replace(
{ ...collection, types: {} },
(element) => {
return !isElementService(element) || !isServiceImplementation(element)
? element
: {
...element,
intercepts: opts.prepend
? intercepts.concat(element.intercepts || [])
: (element.intercepts || []).concat(intercepts)
};
},
{ deep: true, children: true, inline: true }
) as T;
return replace({ ...collection, types: {} }, (element, next) => {
element = next(element);

return !isElementService(element) || !isServiceImplementation(element)
? element
: {
...element,
intercepts: opts.prepend
? intercepts.concat(element.intercepts || [])
: (element.intercepts || []).concat(intercepts)
};
}) as T;
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/inspect/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './traverse';
export * from './is';
export * from './validate';
45 changes: 14 additions & 31 deletions packages/core/src/inspect/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,35 @@ import {
Element,
CollectionTree
} from '~/types';
import { replace } from '~/transform';
import { replace } from '~/transform/replace';

export type TraverseInspectFn<
Q extends QueryService = QueryService,
M extends MutationService = MutationService,
S extends SubscriptionService = SubscriptionService
> = (element: Element<Q, M, S>, data: TraverseInspectData) => void;
> = (
element: Element<Q, M, S>,
next: TraverseInspectNextFn,
data: TraverseInspectData
) => void;

export type TraverseInspectNextFn = () => void;

export interface TraverseInspectData {
path: string[];
route: string[];
}

export interface TraverseInspectOptions {
/**
* Whether to traverse a collection's inner scopes. Default: `true`.
*/
deep?: boolean;
/**
* Whether to traverse type's children services. Default: `false`.
*/
children?: boolean;
/**
* Whether to traverse types within services. Default: `false`.
*/
inline?: boolean;
}

/**
* Performs a collection traversal, with `cb` being called (top-down) for each `Element`.
* Performs a collection traversal. Alternative to `replace`.
*/
export function traverse<
Q extends QueryService = QueryService,
M extends MutationService = MutationService,
S extends SubscriptionService = SubscriptionService
>(
collection: CollectionTree<Q, M, S>,
cb: TraverseInspectFn<Q, M, S>,
options?: TraverseInspectOptions
): void {
replace(
collection,
(element, data) => {
cb(element, data);
return element;
},
options
);
>(collection: CollectionTree<Q, M, S>, cb: TraverseInspectFn<Q, M, S>): void {
replace(collection, (element, next, data) => {
cb(element, () => next(), data);
return element;
});
}
76 changes: 36 additions & 40 deletions packages/core/src/transform/normalize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@ import {
TreeTypes,
QueryService,
SubscriptionService,
CollectionTreeImplementation
NormalCollection
} from '~/types';
import { isElementType, isElementService } from '~/inspect';
import camelcase from 'camelcase';
import { normalizeServiceTypes } from './helpers';
import { replace } from '../replace';

export type NormalizeTransform<
T extends CollectionTree
> = T extends CollectionTreeImplementation
? CollectionTreeImplementation
: CollectionTree;
import { isElementType, isElementService, isElementTree } from '~/inspect/is';

/**
* Extracts all service inline types of a collection to its top level `CollectionTree.types`, naming them according to their scope, service, and kind. It additionally transforms all type names to pascal case. It will throw if a collection:
Expand All @@ -25,7 +19,7 @@ export type NormalizeTransform<
*/
export function normalize<T extends CollectionTree>(
collection: T
): NormalizeTransform<T> {
): NormalCollection<T> {
const transform = (str: string, isExplicit: boolean) => {
return camelcase(str, { pascalCase: true });
};
Expand All @@ -46,41 +40,43 @@ export function normalize<T extends CollectionTree>(
};

return {
...replace(
collection,
(element, { route }) => {
const name = transform(route[route.length - 1], true);
...replace(collection, (element, next, { route }) => {
if (isElementTree(element)) {
return next(element);
}

const name = transform(route[route.length - 1], true);

if (isElementType(element)) {
if (element.kind !== 'response' || !element.children) {
return element;
}
if (isElementType(element)) {
if (element.kind !== 'response' || !element.children) {
return element;
}

const response = { ...element, children: element.children };
for (const [key, service] of Object.entries(element.children)) {
response.children[key] = normalizeServiceTypes(
name + transform(key, false),
service,
types,
transform
) as QueryService | SubscriptionService;
}
return response;
} else if (isElementService(element)) {
return normalizeServiceTypes(
route.length > 1
? transform(route[route.length - 2], false) + name
: name,
element,
const response = { ...element, children: element.children };
for (const [key, service] of Object.entries(element.children)) {
response.children[key] = normalizeServiceTypes(
name + transform(key, false),
service,
types,
transform
);
} else {
return element;
) as QueryService | SubscriptionService;
}
},
{ deep: true, children: false, inline: false }
),
return response;
}

if (isElementService(element)) {
return normalizeServiceTypes(
route.length > 1
? transform(route[route.length - 2], false) + name
: name,
element,
types,
transform
);
}

return element;
}),
types: types.normal
} as NormalizeTransform<T>;
} as NormalCollection<T>;
}

0 comments on commit e434dbb

Please sign in to comment.