import xs , { Stream } from 'xstream' ; import { adapt } from '@cycle/run/lib/adapt' ; export type Component < So , Si > = ( sources : So , ... rest : Array < any > ) => Si ; export type FirstArg < T extends ( r : any , ... args : Array < any > ) => any > = T extends ( r : infer R , ... args : Array < any > ) => any ? R : any ; export type IsolateableSource < A = any , B = any > = { isolateSource ( source , scope ) : IsolateableSource < A , B > ; isolateSink ( sink , scope ) : B ; } ; export type Sources = { [ name ] : IsolateableSource ; } ; export type WildcardScope = { [ '*' ] ? : string ; } ; export type ScopesPerChannel < So > = { [ K in keyof So ] : any ; } ; export type Scopes < So > = ( Partial < ScopesPerChannel < So > > & WildcardScope ) | string ; function checkIsolateArgs < So , Si > ( dataflowComponent , scope ) { if ( typeof dataflowComponent !== `function` ) { throw new Error ( `First argument given to isolate() must be a ` + `'dataflowComponent' function` ) ; } if ( scope === null ) { throw new Error ( `Second argument given to isolate() must not be null` ) ; } } function normalizeScopes < So > ( sources , scopes , randomScope ) { const perChannel = { } as ScopesPerChannel < So > ; Object . keys ( sources ) . forEach ( channel => { if ( typeof scopes === 'string' ) { perChannel [ channel ] = scopes ; return ; } const candidate = ( scopes as ScopesPerChannel < So > ) [ channel ] ; if ( typeof candidate !== 'undefined' ) { perChannel [ channel ] = candidate ; return ; } const wildcard = ( scopes as WildcardScope ) [ '*' ] ; if ( typeof wildcard !== 'undefined' ) { perChannel [ channel ] = wildcard ; return ; } perChannel [ channel ] = randomScope ; } ) ; return perChannel ; } function isolateAllSources < So extends Sources > ( outerSources , scopes ) { const innerSources = { } as So ; for ( const channel in outerSources ) { const outerSource = outerSources [ channel ] as IsolateableSource ; if ( outerSources . hasOwnProperty ( channel ) && outerSource && scopes [ channel ] !== null && typeof outerSource . isolateSource === 'function' ) { innerSources [ channel ] = outerSource . isolateSource ( outerSource , scopes [ channel ] ) as any ; } else if ( outerSources . hasOwnProperty ( channel ) ) { innerSources [ channel ] = outerSources [ channel ] ; } } return innerSources ; } function isolateAllSinks < So extends Sources , Si > ( sources , innerSinks , scopes ) { const outerSinks = { } as Si ; for ( const channel in innerSinks ) { const source = sources [ channel ] as IsolateableSource ; const innerSink = innerSinks [ channel ] ; if ( innerSinks . hasOwnProperty ( channel ) && source && scopes [ channel ] !== null && typeof source . isolateSink === 'function' ) { outerSinks [ channel ] = adapt ( source . isolateSink ( xs . fromObservable ( innerSink as any ) , scopes [ channel ] ) ) ; } else if ( innerSinks . hasOwnProperty ( channel ) ) { outerSinks [ channel ] = innerSinks [ channel ] ; } } return outerSinks ; } /**
 * `isolate` takes a small component as input, and returns a big component.
 * A "small" component is a component that operates in a deeper scope.
 * A "big" component is a component that operates on a scope that
 * includes/wraps/nests the small component's scope. This is specially true for
 * isolation contexts such as onionify.
 *
 * Notice that we type BigSo/BigSi as any. This is unfortunate, since ideally
 * these would be generics in `isolate`. TypeScript's inference isn't strong
 * enough yet for us to automatically provide the typings that would make
 * `isolate` return a big component. However, we still keep these aliases here
 * in case TypeScript's inference becomes better, then we know how to proceed
 * to provide proper types.
 */ export type OuterSo < ISo > = { [ K in keyof ISo ] : ISo [ K ] extends IsolateableSource ? FirstArg < IsolateableSource [ 'isolateSource' ] > : ISo [ K ] ; } ; export type OuterSi < ISo , ISi > = { [ K in keyof ISo & keyof ISi ] : ISo [ K ] extends IsolateableSource ? ( ReturnType < ISo [ K ] [ 'isolateSink' ] > extends Stream < infer T > ? Stream < T > : ( ReturnType < ISo [ K ] [ 'isolateSink' ] > extends Stream < any > ? Stream < unknown > : unknown ) ) : ISi [ K ] ; } & { [ K in Exclude < keyof ISi , keyof ISo > ] : ISi [ K ] ; } ; let counter = 0 ; function newScope ( ) { return `cycle${ ++ counter }` ; } param { Function } component param { String } scope return { Function } function function isolate < InnerSo , InnerSi > ( component , scope = newScope ( ) ) { checkIsolateArgs ( component , scope ) ; const randomScope = typeof scope === 'object' ? newScope ( ) : '' ; const scopes = typeof scope === 'string' || typeof scope === 'object' ? scope : scope . toString ( ) ; return function wrappedComponent ( outerSources , ... rest ) : OuterSi < InnerSo , InnerSi > { const scopesPerChannel = normalizeScopes ( outerSources , scopes , randomScope ) ; const innerSources = isolateAllSources ( outerSources as any , scopesPerChannel ) ; const innerSinks = component ( innerSources , ... rest ) ; const outerSinks = isolateAllSinks ( outerSources as any , innerSinks , scopesPerChannel ) ; return outerSinks as any ; } ; } ( isolate as any ) . reset = ( ) => ( counter = 0 ) ; export default isolate ; export function toIsolated < InnerSo , InnerSi > ( scope = newScope ( ) ) { return component => isolate ( component , scope ) ; }