1
+ // Keep this around until we drop support for Node 14.
2
+ import 'abort-controller/polyfill' ; // eslint-disable-line import/no-unassigned-import
1
3
import { AsyncLocalStorage } from 'node:async_hooks' ;
2
4
import * as grpc from '@grpc/grpc-js' ;
3
5
import type { RPCImpl } from 'protobufjs' ;
@@ -375,7 +377,7 @@ export class Connection {
375
377
} : RPCImplOptions ) : RPCImpl {
376
378
return ( method : { name : string } , requestData : any , callback : grpc . requestCallback < any > ) => {
377
379
const metadataContainer = new grpc . Metadata ( ) ;
378
- const { metadata, deadline } = callContextStorage . getStore ( ) ?? { } ;
380
+ const { metadata, deadline, abortSignal } = callContextStorage . getStore ( ) ?? { } ;
379
381
for ( const [ k , v ] of Object . entries ( staticMetadata ) ) {
380
382
metadataContainer . set ( k , v ) ;
381
383
}
@@ -384,7 +386,7 @@ export class Connection {
384
386
metadataContainer . set ( k , v ) ;
385
387
}
386
388
}
387
- return client . makeUnaryRequest (
389
+ const call = client . makeUnaryRequest (
388
390
`/${ serviceName } /${ method . name } ` ,
389
391
( arg : any ) => arg ,
390
392
( arg : any ) => arg ,
@@ -393,6 +395,11 @@ export class Connection {
393
395
{ interceptors, deadline } ,
394
396
callback
395
397
) ;
398
+ if ( abortSignal != null ) {
399
+ abortSignal . addEventListener ( 'abort' , ( ) => call . cancel ( ) ) ;
400
+ }
401
+
402
+ return call ;
396
403
} ;
397
404
}
398
405
@@ -403,7 +410,27 @@ export class Connection {
403
410
*/
404
411
async withDeadline < ReturnType > ( deadline : number | Date , fn : ( ) => Promise < ReturnType > ) : Promise < ReturnType > {
405
412
const cc = this . callContextStorage . getStore ( ) ;
406
- return await this . callContextStorage . run ( { deadline, metadata : cc ?. metadata } , fn ) ;
413
+ return await this . callContextStorage . run ( { ...cc , deadline } , fn ) ;
414
+ }
415
+
416
+ /**
417
+ * Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
418
+ * cancels any ongoing requests executed in `fn`'s scope.
419
+ *
420
+ * @returns value returned from `fn`
421
+ *
422
+ * @example
423
+ *
424
+ * ```ts
425
+ * const ctrl = new AbortController();
426
+ * setTimeout(() => ctrl.abort(), 10_000);
427
+ * // 👇 throws if incomplete by the timeout.
428
+ * await conn.withAbortSignal(ctrl.signal, () => client.workflow.execute(myWorkflow, options));
429
+ * ```
430
+ */
431
+ async withAbortSignal < ReturnType > ( abortSignal : AbortSignal , fn : ( ) => Promise < ReturnType > ) : Promise < ReturnType > {
432
+ const cc = this . callContextStorage . getStore ( ) ;
433
+ return await this . callContextStorage . run ( { ...cc , abortSignal } , fn ) ;
407
434
}
408
435
409
436
/**
@@ -416,16 +443,15 @@ export class Connection {
416
443
*
417
444
* @example
418
445
*
419
- *```ts
420
- *const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
421
- * conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
422
- *);
423
- *```
446
+ * ```ts
447
+ * const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
448
+ * conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
449
+ * );
450
+ * ```
424
451
*/
425
452
async withMetadata < ReturnType > ( metadata : Metadata , fn : ( ) => Promise < ReturnType > ) : Promise < ReturnType > {
426
453
const cc = this . callContextStorage . getStore ( ) ;
427
- metadata = { ...cc ?. metadata , ...metadata } ;
428
- return await this . callContextStorage . run ( { metadata, deadline : cc ?. deadline } , fn ) ;
454
+ return await this . callContextStorage . run ( { ...cc , metadata : { ...cc ?. metadata , ...metadata } } , fn ) ;
429
455
}
430
456
431
457
/**
0 commit comments