@google-cloud/functions-framework
Advanced tools
Comparing version 3.2.1 to 3.3.0
@@ -1,6 +0,6 @@ | ||
import { HttpFunction, CloudEventFunction, HandlerFunction } from './functions'; | ||
import { HttpFunction, CloudEventFunction, HandlerFunction, TypedFunction } from './functions'; | ||
import { SignatureType } from './types'; | ||
interface RegisteredFunction<T> { | ||
interface RegisteredFunction<T, U> { | ||
signatureType: SignatureType; | ||
userFunction: HandlerFunction<T>; | ||
userFunction: HandlerFunction<T, U>; | ||
} | ||
@@ -23,3 +23,3 @@ /** | ||
*/ | ||
export declare const getRegisteredFunction: (functionName: string) => RegisteredFunction<any> | undefined; | ||
export declare const getRegisteredFunction: (functionName: string) => RegisteredFunction<any, any> | undefined; | ||
/** | ||
@@ -39,3 +39,9 @@ * Register a function that responds to HTTP requests. | ||
export declare const cloudEvent: <T = unknown>(functionName: string, handler: CloudEventFunction<T>) => void; | ||
/** | ||
* Register a function that handles strongly typed invocations. | ||
* @param functionName - the name of the function | ||
* @param handler - the function to trigger | ||
*/ | ||
export declare const typed: <T, U>(functionName: string, handler: TypedFunction<T, U> | ((req: T) => U | Promise<U>)) => void; | ||
export {}; | ||
//# sourceMappingURL=function_registry.d.ts.map |
@@ -16,3 +16,4 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.cloudEvent = exports.http = exports.getRegisteredFunction = exports.isValidFunctionName = void 0; | ||
exports.typed = exports.cloudEvent = exports.http = exports.getRegisteredFunction = exports.isValidFunctionName = void 0; | ||
const functions_1 = require("./functions"); | ||
/** | ||
@@ -82,2 +83,17 @@ * Singleton map to hold the registered functions | ||
exports.cloudEvent = cloudEvent; | ||
/** | ||
* Register a function that handles strongly typed invocations. | ||
* @param functionName - the name of the function | ||
* @param handler - the function to trigger | ||
*/ | ||
const typed = (functionName, handler) => { | ||
if (handler instanceof Function) { | ||
handler = { | ||
handler, | ||
format: new functions_1.JsonInvocationFormat(), | ||
}; | ||
} | ||
register(functionName, 'typed', handler); | ||
}; | ||
exports.typed = typed; | ||
//# sourceMappingURL=function_registry.js.map |
@@ -86,4 +86,3 @@ "use strict"; | ||
const d = domain.create(); | ||
// Catch unhandled errors originating from this request. | ||
d.on('error', err => { | ||
const errorHandler = (err) => { | ||
if (res.locals.functionExecutionFinished) { | ||
@@ -96,6 +95,12 @@ console.error(`Exception from a finished function: ${err}`); | ||
} | ||
}); | ||
}; | ||
// Catch unhandled errors originating from this request. | ||
d.on('error', errorHandler); | ||
d.run(() => { | ||
process.nextTick(() => { | ||
execute(req, res); | ||
const ret = execute(req, res); | ||
// Catch rejected promises if the function is async. | ||
if (ret instanceof Promise) { | ||
ret.catch(errorHandler); | ||
} | ||
}); | ||
@@ -149,3 +154,3 @@ }); | ||
/** | ||
* Wraps an callback style event function in an express RequestHandler. | ||
* Wraps a callback style event function in an express RequestHandler. | ||
* @param userFunction User's function. | ||
@@ -163,2 +168,29 @@ * @return An Express hander function that invokes the user function. | ||
/** | ||
* Wraps a typed function in an express style RequestHandler. | ||
* @param userFunction User's function | ||
* @return An Express handler function that invokes the user function | ||
*/ | ||
const wrapTypedFunction = (typedFunction) => { | ||
const typedHandlerWrapper = async (req, res) => { | ||
let reqTyped; | ||
try { | ||
reqTyped = typedFunction.format.deserializeRequest(new InvocationRequestImpl(req)); | ||
} | ||
catch (err) { | ||
(0, logger_1.sendCrashResponse)({ | ||
err, | ||
res, | ||
statusOverride: 400, // 400 Bad Request | ||
}); | ||
return; | ||
} | ||
let resTyped = typedFunction.handler(reqTyped); | ||
if (resTyped instanceof Promise) { | ||
resTyped = await resTyped; | ||
} | ||
typedFunction.format.serializeResponse(new InvocationResponseImpl(res), resTyped); | ||
}; | ||
return wrapHttpFunction(typedHandlerWrapper); | ||
}; | ||
/** | ||
* Wraps a user function with the provided signature type in an express | ||
@@ -175,3 +207,3 @@ * RequestHandler. | ||
// Callback style if user function has more than 2 arguments. | ||
if (userFunction.length > 2) { | ||
if (userFunction instanceof Function && userFunction.length > 2) { | ||
return wrapEventFunctionWithCallback(userFunction); | ||
@@ -181,3 +213,3 @@ } | ||
case 'cloudevent': | ||
if (userFunction.length > 1) { | ||
if (userFunction instanceof Function && userFunction.length > 1) { | ||
// Callback style if user function has more than 1 argument. | ||
@@ -187,5 +219,38 @@ return wrapCloudEventFunctionWithCallback(userFunction); | ||
return wrapCloudEventFunction(userFunction); | ||
case 'typed': | ||
return wrapTypedFunction(userFunction); | ||
} | ||
}; | ||
exports.wrapUserFunction = wrapUserFunction; | ||
/** | ||
* @private | ||
*/ | ||
class InvocationRequestImpl { | ||
constructor(req) { | ||
this.req = req; | ||
} | ||
body() { | ||
return this.req.body; | ||
} | ||
header(header) { | ||
return this.req.header(header); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
class InvocationResponseImpl { | ||
constructor(res) { | ||
this.res = res; | ||
} | ||
setHeader(key, value) { | ||
this.res.set(key, value); | ||
} | ||
write(data) { | ||
this.res.write(data); | ||
} | ||
end(data) { | ||
this.res.end(data); | ||
} | ||
} | ||
//# sourceMappingURL=function_wrappers.js.map |
@@ -57,6 +57,14 @@ /// <reference types="node" /> | ||
/** | ||
* A Typed function handler that may return a value or a promise. | ||
* @public | ||
*/ | ||
export interface TypedFunction<T = unknown, U = unknown> { | ||
handler: (req: T) => U | Promise<U>; | ||
format: InvocationFormat<T, U>; | ||
} | ||
/** | ||
* A function handler. | ||
* @public | ||
*/ | ||
export type HandlerFunction<T = unknown> = HttpFunction | EventFunction | EventFunctionWithCallback | CloudEventFunction<T> | CloudEventFunctionWithCallback<T>; | ||
export type HandlerFunction<T = unknown, U = unknown> = HttpFunction | EventFunction | EventFunctionWithCallback | CloudEventFunction<T> | CloudEventFunctionWithCallback<T> | TypedFunction<T, U>; | ||
/** | ||
@@ -115,2 +123,52 @@ * A legacy event. | ||
export type Context = CloudFunctionsContext | CloudEvent<unknown>; | ||
/** | ||
* InvocationRequest represents the properties of an invocation over HTTP. | ||
* @public | ||
*/ | ||
export interface InvocationRequest { | ||
/** Returns the request body as either a string or a Buffer if the body is binary. */ | ||
body(): string | Buffer; | ||
/** Header returns the value of the specified header */ | ||
header(header: string): string | undefined; | ||
} | ||
/** | ||
* InvocationResponse interface describes the properties that can be set on | ||
* an invocation response. | ||
* @public | ||
*/ | ||
export interface InvocationResponse { | ||
/** Sets a header on the response. */ | ||
setHeader(key: string, value: string): void; | ||
/** Writes a chunk of data to the response. */ | ||
write(data: string | Buffer): void; | ||
/** Ends the response, must be called once at the end of writing. */ | ||
end(data: string | Buffer): void; | ||
} | ||
/** | ||
* The contract for a request deserializer and response serializer. | ||
* @public | ||
*/ | ||
export interface InvocationFormat<T, U> { | ||
/** | ||
* Creates an instance of the request type from an invocation request. | ||
* | ||
* @param request the request body as raw bytes | ||
*/ | ||
deserializeRequest(request: InvocationRequest): T | Promise<T>; | ||
/** | ||
* Writes the response type to the invocation result. | ||
* | ||
* @param responseWriter interface for writing to the invocation result | ||
* @param response the response object | ||
*/ | ||
serializeResponse(responseWriter: InvocationResponse, response: U): void | Promise<void>; | ||
} | ||
/** | ||
* Default invocation format for JSON requests. | ||
* @public | ||
*/ | ||
export declare class JsonInvocationFormat<T, U> implements InvocationFormat<T, U> { | ||
deserializeRequest(request: InvocationRequest): T; | ||
serializeResponse(responseWriter: InvocationResponse, response: U): void; | ||
} | ||
//# sourceMappingURL=functions.d.ts.map |
@@ -16,2 +16,27 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.JsonInvocationFormat = void 0; | ||
/** | ||
* Default invocation format for JSON requests. | ||
* @public | ||
*/ | ||
class JsonInvocationFormat { | ||
deserializeRequest(request) { | ||
const body = request.body(); | ||
if (typeof body !== 'string') { | ||
throw new Error('Unsupported Content-Type, expected application/json'); | ||
} | ||
try { | ||
return JSON.parse(body); | ||
} | ||
catch (e) { | ||
throw new Error('Failed to parse malformatted JSON in request: ' + | ||
e.message); | ||
} | ||
} | ||
serializeResponse(responseWriter, response) { | ||
responseWriter.setHeader('content-type', 'application/json'); | ||
responseWriter.end(JSON.stringify(response)); | ||
} | ||
} | ||
exports.JsonInvocationFormat = JsonInvocationFormat; | ||
//# sourceMappingURL=functions.js.map |
@@ -8,3 +8,3 @@ /** | ||
*/ | ||
export { http, cloudEvent } from './function_registry'; | ||
export { http, cloudEvent, typed } from './function_registry'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -30,3 +30,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.cloudEvent = exports.http = void 0; | ||
exports.typed = exports.cloudEvent = exports.http = void 0; | ||
/** | ||
@@ -42,2 +42,3 @@ * @public | ||
Object.defineProperty(exports, "cloudEvent", { enumerable: true, get: function () { return function_registry_1.cloudEvent; } }); | ||
Object.defineProperty(exports, "typed", { enumerable: true, get: function () { return function_registry_1.typed; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -9,3 +9,3 @@ import * as express from 'express'; | ||
*/ | ||
export declare function sendCrashResponse({ err, res, callback, silent, statusHeader, }: { | ||
export declare function sendCrashResponse({ err, res, callback, silent, statusHeader, statusOverride, }: { | ||
err: Error | any; | ||
@@ -16,3 +16,4 @@ res: express.Response | null; | ||
statusHeader?: string; | ||
statusOverride?: number; | ||
}): void; | ||
//# sourceMappingURL=logger.d.ts.map |
@@ -25,3 +25,3 @@ "use strict"; | ||
*/ | ||
function sendCrashResponse({ err, res, callback, silent = false, statusHeader = 'crash', }) { | ||
function sendCrashResponse({ err, res, callback, silent = false, statusHeader = 'crash', statusOverride = 500, }) { | ||
if (!silent) { | ||
@@ -37,7 +37,7 @@ console.error(err.stack || err); | ||
if (process.env.NODE_ENV !== 'production') { | ||
res.status(500); | ||
res.status(statusOverride); | ||
res.send((err.message || err) + ''); | ||
} | ||
else { | ||
res.sendStatus(500); | ||
res.sendStatus(statusOverride); | ||
} | ||
@@ -44,0 +44,0 @@ } |
@@ -78,4 +78,14 @@ "use strict"; | ||
// Apply middleware | ||
app.use(bodyParser.json(cloudEventsBodySavingOptions)); | ||
app.use(bodyParser.json(defaultBodySavingOptions)); | ||
if (functionSignatureType !== 'typed') { | ||
// If the function is not typed then JSON parsing can be done automatically, otherwise the | ||
// functions format must determine deserialization. | ||
app.use(bodyParser.json(cloudEventsBodySavingOptions)); | ||
app.use(bodyParser.json(defaultBodySavingOptions)); | ||
} | ||
else { | ||
const jsonParserOptions = Object.assign({}, defaultBodySavingOptions, { | ||
type: 'application/json', | ||
}); | ||
app.use(bodyParser.text(jsonParserOptions)); | ||
} | ||
app.use(bodyParser.text(defaultBodySavingOptions)); | ||
@@ -82,0 +92,0 @@ app.use(bodyParser.urlencoded(urlEncodedOptions)); |
@@ -5,3 +5,3 @@ export declare const FUNCTION_STATUS_HEADER_FIELD = "X-Google-Status"; | ||
*/ | ||
export declare const SignatureType: readonly ["http", "event", "cloudevent"]; | ||
export declare const SignatureType: readonly ["http", "event", "cloudevent", "typed"]; | ||
/** | ||
@@ -16,3 +16,3 @@ * Union type of all valid function SignatureType values. | ||
*/ | ||
export declare const isValidSignatureType: (x: any) => x is "event" | "http" | "cloudevent"; | ||
export declare const isValidSignatureType: (x: any) => x is "event" | "http" | "cloudevent" | "typed"; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -23,3 +23,3 @@ "use strict"; | ||
*/ | ||
exports.SignatureType = ['http', 'event', 'cloudevent']; | ||
exports.SignatureType = ['http', 'event', 'cloudevent', 'typed']; | ||
/** | ||
@@ -26,0 +26,0 @@ * Type guard to test if a provided value is valid SignatureType |
{ | ||
"name": "@google-cloud/functions-framework", | ||
"version": "3.2.1", | ||
"version": "3.3.0", | ||
"description": "FaaS (Function as a service) framework for writing portable Node.js functions", | ||
@@ -59,3 +59,3 @@ "engines": { | ||
"@types/mocha": "9.1.1", | ||
"@types/node": "14.18.43", | ||
"@types/node": "14.18.52", | ||
"@types/on-finished": "2.3.1", | ||
@@ -62,0 +62,0 @@ "@types/semver": "^7.3.6", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
108211
2153