1- import { Node } from '@stencila/schema'
1+ import { Node , isPrimitive , nodeType } from '@stencila/schema'
22import { JSONSchema7Definition } from 'json-schema'
33import Ajv from 'ajv'
4- import Client , { ClientType } from './Client'
4+ import { ClientType } from './Client'
55import Server from './Server'
66import { Address , Transport } from './Transports'
77import { getLogger } from '@stencila/logga'
@@ -43,16 +43,22 @@ export interface Addresses {
4343 * etc
4444 */
4545export interface Manifest {
46+ /**
47+ * The actual in-process `Executor`, or
48+ * it's id i it is ou-of-process
49+ */
50+ executor ?: Executor | string
51+
4652 /**
4753 * The capabilities of the executor
4854 */
49- capabilities : Capabilities
55+ capabilities ? : Capabilities
5056
5157 /**
5258 * The addresses of servers that can be used
5359 * to communicate with the executor
5460 */
55- addresses : Addresses
61+ addresses ? : Addresses
5662}
5763
5864/**
@@ -150,12 +156,13 @@ export class Peer {
150156 public readonly clientTypes : ClientType [ ]
151157
152158 /**
153- * The client for the peer executor.
159+ * The interface to the peer executor.
154160 *
155- * The type of client e.g. `StdioClient` vs `WebSocketClient`
161+ * May be an in-process `Executor` or a `Client` to an out-of-process
162+ * `Executor`, in which case it's type e.g. `StdioClient` vs `WebSocketClient`
156163 * will depend upon the available transports in `manifest.transports`.
157164 */
158- private client ?: Client
165+ private interface ?: Interface
159166
160167 /**
161168 * Ajv validation functions for each method.
@@ -184,11 +191,16 @@ export class Peer {
184191 public capable ( method : Method , params : { [ key : string ] : unknown } ) : boolean {
185192 let validators = this . validators [ method ]
186193 if ( validators === undefined ) {
194+ // Peer does not have any capabilities defined
195+ if ( this . manifest . capabilities === undefined ) return false
196+
187197 let capabilities = this . manifest . capabilities [ method ]
188- // Peer does not have any capabilities defined for this method
198+ // Peer does not have any capabilities for this method defined
189199 if ( capabilities === undefined ) return false
200+
190201 // Peer defines capability as a single JSON Schema definition
191202 if ( ! Array . isArray ( capabilities ) ) capabilities = [ capabilities ]
203+
192204 // Compile JSON Schema definitions to validation functions
193205 validators = this . validators [ method ] = capabilities . map ( schema =>
194206 ajv . compile ( schema )
@@ -201,14 +213,22 @@ export class Peer {
201213 }
202214
203215 /**
204- * Connect to the remote `Executor`.
216+ * Connect to the `Executor`.
205217 *
206218 * Finds the first client type that the peer
207219 * executor supports.
208220 *
209221 * @returns A client instance or `undefined` if not able to connect
210222 */
211223 public connect ( ) : boolean {
224+ // If the executor is in-process, just use it directly
225+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
226+ if ( this . manifest . executor instanceof Executor ) {
227+ this . interface = this . manifest . executor
228+ return true
229+ }
230+ // Connect to remote executor in order of preference of
231+ // transports
212232 for ( const ClientType of this . clientTypes ) {
213233 // Get the transport for the client type
214234 // There should be a better way to do this
@@ -224,10 +244,11 @@ export class Peer {
224244 if ( transport === undefined )
225245 throw new Error ( 'Wooah! This should not happen!' )
226246
227- // See if the peer has an address the transport
247+ // See if the peer has an address for the transport
248+ if ( this . manifest . addresses === undefined ) return false
228249 const address = this . manifest . addresses [ transport ]
229250 if ( address !== undefined ) {
230- this . client = new ClientType ( address )
251+ this . interface = new ClientType ( address )
231252 return true
232253 }
233254 }
@@ -248,9 +269,9 @@ export class Peer {
248269 method : Method ,
249270 params : { [ key : string ] : any } = { }
250271 ) : Promise < Type > {
251- if ( this . client === undefined )
272+ if ( this . interface === undefined )
252273 throw new Error ( "WTF, no client! You shouldn't be calling this!" )
253- return this . client . call < Type > ( method , params )
274+ return this . interface . call < Type > ( method , params )
254275 }
255276}
256277
@@ -348,6 +369,7 @@ export default class Executor implements Interface {
348369 }
349370 for ( const peer of this . peers ) {
350371 const manifest = peer . manifest
372+ if ( manifest . capabilities === undefined ) continue
351373 for ( const [ method , additional ] of Object . entries (
352374 manifest . capabilities
353375 ) ) {
@@ -392,8 +414,23 @@ export default class Executor implements Interface {
392414 return this . delegate ( Method . build , { node } , async ( ) => node )
393415 }
394416
417+ /**
418+ * Execute a `Node`.
419+ *
420+ * Walks the node tree and attempts to delegate
421+ * execution of certain types of nodes (currently `CodeChunk` and `CodeExpression`).
422+ *
423+ * @param node The node to execute
424+ */
395425 public async execute ( node : Node ) : Promise < Node > {
396- return this . delegate ( Method . execute , { node } , async ( ) => node )
426+ return this . walk ( node , async node => {
427+ switch ( nodeType ( node ) ) {
428+ case 'CodeChunk' :
429+ case 'CodeExpression' :
430+ return this . delegate ( Method . execute , { node } , async ( ) => node )
431+ }
432+ return node
433+ } )
397434 }
398435
399436 public async call (
@@ -414,6 +451,26 @@ export default class Executor implements Interface {
414451 }
415452 }
416453
454+ private async walk (
455+ node : Node ,
456+ transformer : ( node : Node ) => Promise < Node >
457+ ) : Promise < Node > {
458+ return walk ( node )
459+ async function walk ( node : Node ) : Promise < Node > {
460+ const transformed = await transformer ( node )
461+ if ( transformed === undefined || isPrimitive ( transformed ) )
462+ return transformed
463+ if ( Array . isArray ( transformed ) ) return Promise . all ( transformed . map ( walk ) )
464+ return Object . entries ( transformed ) . reduce (
465+ async ( prev , [ key , child ] ) => ( {
466+ ...( await prev ) ,
467+ ...{ [ key ] : await walk ( child ) }
468+ } ) ,
469+ Promise . resolve ( { } )
470+ )
471+ }
472+ }
473+
417474 private async delegate < Type > (
418475 method : Method ,
419476 params : { [ key : string ] : any } ,
@@ -431,7 +488,7 @@ export default class Executor implements Interface {
431488 }
432489
433490 // No peer has necessary capability so resort to fallback
434- log . debug ( `Not able to ${ JSON . stringify ( params ) } ` )
491+ log . debug ( `Unable to delegate node: ${ JSON . stringify ( params ) } ` )
435492 return fallback ( )
436493 }
437494}
0 commit comments