@@ -14,7 +14,12 @@ import { env } from 'node:process'
1414import { fileURLToPath } from 'node:url'
1515import { v4 } from 'uuid'
1616import { LocalServer } from './local-server.js'
17- import { loadFunction , type FunctionInvocationOptions } from './lambda-helpers.mjs'
17+ import {
18+ type InvokeFunctionResult ,
19+ loadFunction ,
20+ type LoadFunctionOptions ,
21+ type FunctionInvocationOptions ,
22+ } from './lambda-helpers.mjs'
1823
1924import { glob } from 'fast-glob'
2025import {
@@ -24,6 +29,7 @@ import {
2429} from '../../src/build/plugin-context.js'
2530import { BLOB_TOKEN } from './constants.mjs'
2631import { type FixtureTestContext } from './contexts.js'
32+ // import { createBlobContext } from './helpers.js'
2733import { setNextVersionInFixture } from './next-version-helpers.mjs'
2834
2935const bootstrapURL = 'https://edge.netlify.com/bootstrap/index-combined.ts'
@@ -405,48 +411,140 @@ export async function invokeEdgeFunction(
405411 } )
406412}
407413
408- export async function invokeSandboxedFunction (
414+ /**
415+ * Load function in child process and allow for multiple invocations
416+ */
417+ export async function loadSandboxedFunction (
409418 ctx : FixtureTestContext ,
410- options : Parameters < typeof invokeFunction > [ 1 ] = { } ,
419+ options : LoadFunctionOptions = { } ,
411420) {
412- return new Promise < ReturnType < typeof invokeFunction > > ( ( resolve , reject ) => {
413- const childProcess = spawn ( process . execPath , [ import . meta. dirname + '/sandbox-child.mjs' ] , {
414- stdio : [ 'pipe' , 'pipe' , 'pipe' , 'ipc' ] ,
415- cwd : process . cwd ( ) ,
416- } )
421+ const childProcess = spawn ( process . execPath , [ import . meta. dirname + '/sandbox-child.mjs' ] , {
422+ stdio : [ 'pipe' , 'pipe' , 'pipe' , 'ipc' ] ,
423+ cwd : join ( ctx . functionDist , SERVER_HANDLER_NAME ) ,
424+ env : {
425+ ...process . env ,
426+ ...( options . env || { } ) ,
427+ } ,
428+ } )
417429
418- childProcess . stdout ?. on ( 'data' , ( data ) => {
419- console . log ( data . toString ( ) )
420- } )
430+ let isRunning = true
431+ let operationCounter = 1
421432
422- childProcess . stderr ?. on ( 'data' , ( data ) => {
423- console . error ( data . toString ( ) )
424- } )
433+ childProcess . stdout ?. on ( 'data' , ( data ) => {
434+ console . log ( data . toString ( ) )
435+ } )
425436
426- childProcess . on ( 'message' , ( msg : any ) => {
427- if ( msg ?. action === 'invokeFunctionResult' ) {
428- resolve ( msg . result )
429- childProcess . send ( { action : 'exit' } )
430- }
431- } )
437+ childProcess . stderr ?. on ( 'data' , ( data ) => {
438+ console . error ( data . toString ( ) )
439+ } )
440+
441+ const onGoingOperationsMap = new Map <
442+ number ,
443+ {
444+ resolve : ( value ?: any ) => void
445+ reject : ( reason ?: any ) => void
446+ }
447+ > ( )
448+
449+ function createOperation < T > ( ) {
450+ const operationId = operationCounter
451+ operationCounter += 1
432452
433- childProcess . on ( 'exit' , ( ) => {
434- reject ( new Error ( 'worker exited before returning result' ) )
453+ let promiseResolve , promiseReject
454+ const promise = new Promise < T > ( ( innerResolve , innerReject ) => {
455+ promiseResolve = innerResolve
456+ promiseReject = innerReject
435457 } )
436458
459+ function resolve ( value : T ) {
460+ onGoingOperationsMap . delete ( operationId )
461+ promiseResolve ?.( value )
462+ }
463+ function reject ( reason ) {
464+ onGoingOperationsMap . delete ( operationId )
465+ promiseReject ?.( reason )
466+ }
467+
468+ onGoingOperationsMap . set ( operationId , { resolve, reject } )
469+ return { operationId, promise, resolve, reject }
470+ }
471+
472+ childProcess . on ( 'exit' , ( ) => {
473+ isRunning = false
474+
475+ const error = new Error ( 'worker exited before returning result' )
476+
477+ for ( const { reject } of onGoingOperationsMap . values ( ) ) {
478+ reject ( error )
479+ }
480+ } )
481+
482+ function exit ( ) {
483+ if ( isRunning ) {
484+ childProcess . send ( { action : 'exit' } )
485+ }
486+ }
487+
488+ // make sure to exit the child process when the test is done just in case
489+ ctx . cleanup ?. push ( async ( ) => exit ( ) )
490+
491+ const { promise : loadPromise , resolve : loadResolve } = createOperation < void > ( )
492+
493+ childProcess . on ( 'message' , ( msg : any ) => {
494+ if ( msg ?. action === 'invokeFunctionResult' ) {
495+ onGoingOperationsMap . get ( msg . operationId ) ?. resolve ( msg . result )
496+ } else if ( msg ?. action === 'loadedFunction' ) {
497+ loadResolve ( )
498+ }
499+ } )
500+
501+ // context object is not serializable so we create serializable object
502+ // containing required properties to invoke lambda
503+ const serializableCtx = {
504+ functionDist : ctx . functionDist ,
505+ blobStoreHost : ctx . blobStoreHost ,
506+ siteID : ctx . siteID ,
507+ deployID : ctx . deployID ,
508+ }
509+
510+ childProcess . send ( {
511+ action : 'loadFunction' ,
512+ args : [ serializableCtx ] ,
513+ } )
514+
515+ await loadPromise
516+
517+ function invokeFunction ( options : FunctionInvocationOptions ) : InvokeFunctionResult {
518+ if ( ! isRunning ) {
519+ throw new Error ( 'worker is not running anymore' )
520+ }
521+
522+ const { operationId, promise } = createOperation < Awaited < InvokeFunctionResult > > ( )
523+
437524 childProcess . send ( {
438525 action : 'invokeFunction' ,
439- args : [
440- // context object is not serializable so we create serializable object
441- // containing required properties to invoke lambda
442- {
443- functionDist : ctx . functionDist ,
444- blobStoreHost : ctx . blobStoreHost ,
445- siteID : ctx . siteID ,
446- deployID : ctx . deployID ,
447- } ,
448- options ,
449- ] ,
526+ operationId,
527+ args : [ serializableCtx , options ] ,
450528 } )
451- } )
529+
530+ return promise
531+ }
532+
533+ return {
534+ invokeFunction,
535+ exit,
536+ }
537+ }
538+
539+ /**
540+ * Load function in child process and execute single invocation
541+ */
542+ export async function invokeSandboxedFunction (
543+ ctx : FixtureTestContext ,
544+ options : FunctionInvocationOptions = { } ,
545+ ) {
546+ const { invokeFunction, exit } = await loadSandboxedFunction ( ctx , options )
547+ const result = await invokeFunction ( options )
548+ exit ( )
549+ return result
452550}
0 commit comments