diff --git a/src/main.ts b/src/main.ts index b6f8b70..6da3239 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,7 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { CloudFunction as CloudFunctionV1 } from 'firebase-functions'; +import { + CloudFunction as CloudFunctionV1, + HttpsFunction, + Runnable, +} from 'firebase-functions'; import { CloudFunction as CloudFunctionV2, @@ -31,6 +35,11 @@ import { wrapV1, WrappedFunction, WrappedScheduledFunction } from './v1'; import { wrapV2, WrappedV2Function } from './v2'; +type HttpsFunctionOrCloudFunctionV1 = U extends HttpsFunction & + Runnable + ? HttpsFunction & Runnable + : CloudFunctionV1; + // Re-exporting V1 (to reduce breakage) export { ContextOptions, @@ -45,20 +54,24 @@ export { // V2 Exports export { WrappedV2Function } from './v2'; +export function wrap( + cloudFunction: HttpsFunction & Runnable +): WrappedFunction>; export function wrap( cloudFunction: CloudFunctionV1 ): WrappedScheduledFunction | WrappedFunction; export function wrap>( cloudFunction: CloudFunctionV2 ): WrappedV2Function; - export function wrap>( cloudFunction: CloudFunctionV1 | CloudFunctionV2 ): WrappedScheduledFunction | WrappedFunction | WrappedV2Function { if (isV2CloudFunction(cloudFunction)) { return wrapV2(cloudFunction as CloudFunctionV2); } - return wrapV1(cloudFunction as CloudFunctionV1); + return wrapV1( + cloudFunction as HttpsFunctionOrCloudFunctionV1 + ); } /** diff --git a/src/v1.ts b/src/v1.ts index 5436ba9..e159e20 100644 --- a/src/v1.ts +++ b/src/v1.ts @@ -30,6 +30,8 @@ import { config, database, firestore, + HttpsFunction, + Runnable, } from 'firebase-functions'; /** Fields of the event context that can be overridden/customized. */ @@ -88,14 +90,16 @@ export type CallableContextOptions = { }; /* Fields for both Event and Callable contexts, checked at runtime */ -export type ContextOptions = EventContextOptions | CallableContextOptions; +export type ContextOptions = T extends HttpsFunction & Runnable + ? CallableContextOptions + : EventContextOptions; /** A function that can be called with test data and optional override values for the event context. * It will subsequently invoke the cloud function it wraps with the provided test data and a generated event context. */ -export type WrappedFunction = ( +export type WrappedFunction = ( data: T, - options?: ContextOptions + options?: ContextOptions ) => any | Promise; /** A scheduled function that can be called with optional override values for the event context. @@ -106,9 +110,12 @@ export type WrappedScheduledFunction = ( ) => any | Promise; /** Takes a cloud function to be tested, and returns a WrappedFunction which can be called in test code. */ +export function wrapV1( + cloudFunction: HttpsFunction & Runnable +): WrappedFunction>; export function wrapV1( cloudFunction: CloudFunction -): WrappedScheduledFunction | WrappedFunction { +): WrappedScheduledFunction | WrappedFunction> { if (!has(cloudFunction, '__trigger')) { throw new Error( 'Wrap can only be called on functions written with the firebase-functions SDK.' @@ -150,26 +157,26 @@ export function wrapV1( const isCallableFunction = get(cloudFunction, '__trigger.labels.deployment-callable') === 'true'; - let wrapped: WrappedFunction = (data: T, options: ContextOptions) => { + let wrapped: WrappedFunction = (data, options) => { // Although in Typescript we require `options` some of our JS samples do not pass it. - options = options || {}; + const _options = { ...options }; let context; if (isCallableFunction) { _checkOptionValidity( ['app', 'auth', 'instanceIdToken', 'rawRequest'], - options + _options ); - let callableContextOptions = options as CallableContextOptions; + let callableContextOptions = _options as CallableContextOptions; context = { ...callableContextOptions, }; } else { _checkOptionValidity( ['eventId', 'timestamp', 'params', 'auth', 'authType', 'resource'], - options + _options ); - const defaultContext = _makeDefaultContext(cloudFunction, options, data); + const defaultContext = _makeDefaultContext(cloudFunction, _options, data); if ( has(defaultContext, 'eventType') && @@ -179,7 +186,7 @@ export function wrapV1( defaultContext.authType = 'UNAUTHENTICATED'; defaultContext.auth = null; } - context = merge({}, defaultContext, options); + context = merge({}, defaultContext, _options); } return cloudFunction.run(data, context); @@ -226,9 +233,14 @@ function _checkOptionValidity( }); } +function _makeDefaultContext( + cloudFunction: HttpsFunction & Runnable, + options: CallableContextOptions, + triggerData?: T +); function _makeDefaultContext( cloudFunction: CloudFunction, - options: ContextOptions, + options: EventContextOptions, triggerData?: T ): EventContext { let eventContextOptions = options as EventContextOptions;