diff --git a/docs/generated/api.json b/docs/generated/api.json index 07d5e57f..77b0e5eb 100644 --- a/docs/generated/api.json +++ b/docs/generated/api.json @@ -1,7 +1,7 @@ { "metadata": { "toolPackage": "@microsoft/api-extractor", - "toolVersion": "7.34.4", + "toolVersion": "7.43.1", "schemaVersion": 1011, "oldestForwardsCompatibleVersion": 1001, "tsdocConfig": { @@ -173,17 +173,29 @@ "preserveMemberOrder": false, "members": [ { - "kind": "Variable", - "canonicalReference": "@google-cloud/functions-framework!cloudEvent:var", + "kind": "Function", + "canonicalReference": "@google-cloud/functions-framework!cloudEvent:function(1)", "docComment": "/**\n * Register a function that handles CloudEvents.\n *\n * @param functionName - the name of the function\n *\n * @param handler - the function to trigger when handling CloudEvents\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "cloudEvent: " + "text": "cloudEvent: (functionName: string, handler: " + "text": "unknown" + }, + { + "kind": "Content", + "text": ">(functionName: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ", handler: " }, { "kind": "Reference", @@ -192,17 +204,56 @@ }, { "kind": "Content", - "text": ") => void" + "text": "" + }, + { + "kind": "Content", + "text": ") => " + }, + { + "kind": "Content", + "text": "void" } ], "fileUrlPath": "src/function_registry.ts", - "isReadonly": true, + "returnTypeTokenRange": { + "startIndex": 8, + "endIndex": 9 + }, "releaseTag": "Public", - "name": "cloudEvent", - "variableTypeTokenRange": { - "startIndex": 1, - "endIndex": 4 - } + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "functionName", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + }, + { + "parameterName": "handler", + "parameterTypeTokenRange": { + "startIndex": 5, + "endIndex": 7 + }, + "isOptional": false + } + ], + "typeParameters": [ + { + "typeParameterName": "T", + "constraintTokenRange": { + "startIndex": 0, + "endIndex": 0 + }, + "defaultTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + } + ], + "name": "cloudEvent" }, { "kind": "Interface", @@ -896,17 +947,21 @@ } }, { - "kind": "Variable", - "canonicalReference": "@google-cloud/functions-framework!http:var", + "kind": "Function", + "canonicalReference": "@google-cloud/functions-framework!http:function(1)", "docComment": "/**\n * Register a function that responds to HTTP requests.\n *\n * @param functionName - the name of the function\n *\n * @param handler - the function to invoke when handling HTTP requests\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "http: " + "text": "http: (functionName: " }, { "kind": "Content", - "text": "(functionName: string, handler: " + "text": "string" + }, + { + "kind": "Content", + "text": ", handler: " }, { "kind": "Reference", @@ -915,17 +970,39 @@ }, { "kind": "Content", - "text": ") => void" + "text": ") => " + }, + { + "kind": "Content", + "text": "void" } ], "fileUrlPath": "src/function_registry.ts", - "isReadonly": true, + "returnTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, "releaseTag": "Public", - "name": "http", - "variableTypeTokenRange": { - "startIndex": 1, - "endIndex": 4 - } + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "functionName", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "handler", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + } + ], + "name": "http" }, { "kind": "Interface", @@ -1757,6 +1834,34 @@ "name": "Request_2", "preserveMemberOrder": false, "members": [ + { + "kind": "PropertySignature", + "canonicalReference": "@google-cloud/functions-framework!Request_2#abortController:member", + "docComment": "/**\n * An AbortController used to signal cancellation of a function invocation (e.g. in case of time out).\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "abortController?: " + }, + { + "kind": "Reference", + "text": "AbortController", + "canonicalReference": "!AbortController:interface" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": true, + "releaseTag": "Public", + "name": "abortController", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, { "kind": "PropertySignature", "canonicalReference": "@google-cloud/functions-framework!Request_2#executionId:member", @@ -1874,47 +1979,6 @@ } ] }, - { - "kind": "Variable", - "canonicalReference": "@google-cloud/functions-framework!typed:var", - "docComment": "/**\n * Register a function that handles strongly typed invocations.\n *\n * @param functionName - the name of the function\n *\n * @param handler - the function to trigger\n */\n", - "excerptTokens": [ - { - "kind": "Content", - "text": "typed: " - }, - { - "kind": "Content", - "text": "(functionName: string, handler: " - }, - { - "kind": "Reference", - "text": "TypedFunction", - "canonicalReference": "@google-cloud/functions-framework!TypedFunction:interface" - }, - { - "kind": "Content", - "text": " | ((req: T) => U | " - }, - { - "kind": "Reference", - "text": "Promise", - "canonicalReference": "!Promise:interface" - }, - { - "kind": "Content", - "text": ")) => void" - } - ], - "fileUrlPath": "src/function_registry.ts", - "isReadonly": true, - "releaseTag": "Public", - "name": "typed", - "variableTypeTokenRange": { - "startIndex": 1, - "endIndex": 6 - } - }, { "kind": "Interface", "canonicalReference": "@google-cloud/functions-framework!TypedFunction:interface", diff --git a/docs/generated/api.md b/docs/generated/api.md index 56ea685d..105c8516 100644 --- a/docs/generated/api.md +++ b/docs/generated/api.md @@ -112,6 +112,7 @@ export interface LegacyEvent { // @public (undocumented) interface Request_2 extends Request_3 { + abortController?: AbortController; executionId?: string; rawBody?: Buffer; spanId?: string; @@ -121,7 +122,9 @@ export { Request_2 as Request } export { Response_2 as Response } -// @public +// Warning: (ae-internal-missing-underscore) The name "typed" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal export const typed: (functionName: string, handler: TypedFunction | ((req: T) => U | Promise)) => void; // @public diff --git a/package-lock.json b/package-lock.json index 18c898e6..4f380025 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "functions-framework-nodejs": "build/src/main.js" }, "devDependencies": { - "@microsoft/api-extractor": "^7.18.20", + "@microsoft/api-extractor": "^7.43.1", "@types/body-parser": "1.19.5", "@types/minimist": "1.2.5", "@types/mocha": "9.1.1", @@ -220,43 +220,56 @@ "dev": true }, "node_modules/@microsoft/api-extractor": { - "version": "7.34.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.34.4.tgz", - "integrity": "sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==", + "version": "7.43.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.43.1.tgz", + "integrity": "sha512-ohg40SsvFFgzHFAtYq5wKJc8ZDyY46bphjtnSvhSSlXpPTG7GHwyyXkn48UZiUCBwr2WC7TRC1Jfwz7nreuiyQ==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.26.4", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2", - "@rushstack/rig-package": "0.3.18", - "@rushstack/ts-command-line": "4.13.2", - "colors": "~1.2.1", + "@rushstack/node-core-library": "4.1.0", + "@rushstack/rig-package": "0.5.2", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "lodash": "~4.17.15", + "minimatch": "~3.0.3", "resolve": "~1.22.1", - "semver": "~7.3.0", + "semver": "~7.5.4", "source-map": "~0.6.1", - "typescript": "~4.8.4" + "typescript": "5.4.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.26.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.26.4.tgz", - "integrity": "sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==", + "version": "7.28.14", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.14.tgz", + "integrity": "sha512-Bery/c8A8SsKPSvA82cTTuy/+OcxZbLRmKhPkk91/AJOQzxZsShcrmHFAGeiEqSIrv1nPZ3tKq9kfMLdCHmsqg==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2" + "@rushstack/node-core-library": "4.1.0" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/@microsoft/api-extractor/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -269,16 +282,16 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/@microsoft/tsdoc": { @@ -360,17 +373,16 @@ } }, "node_modules/@rushstack/node-core-library": { - "version": "3.55.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.55.2.tgz", - "integrity": "sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.1.0.tgz", + "integrity": "sha512-qz4JFBZJCf1YN5cAXa1dP6Mki/HrsQxc/oYGAGx29dF2cwF2YMxHoly0FBhMw3IEnxo5fMj0boVfoHVBkpkx/w==", "dev": true, "dependencies": { - "colors": "~1.2.1", "fs-extra": "~7.0.1", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", - "semver": "~7.3.0", + "semver": "~7.5.4", "z-schema": "~5.0.2" }, "peerDependencies": { @@ -383,9 +395,9 @@ } }, "node_modules/@rushstack/node-core-library/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -398,24 +410,66 @@ } }, "node_modules/@rushstack/rig-package": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.18.tgz", - "integrity": "sha512-SGEwNTwNq9bI3pkdd01yCaH+gAsHqs0uxfGvtw9b0LJXH52qooWXnrFTRRLG1aL9pf+M2CARdrA9HLHJys3jiQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", + "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", "dev": true, "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, + "node_modules/@rushstack/terminal": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.1.tgz", + "integrity": "sha512-C6Vi/m/84IYJTkfzmXr1+W8Wi3MmBjVF/q3za91Gb3VYjKbpALHVxY6FgH625AnDe5Z0Kh4MHKWA3Z7bqgAezA==", + "dev": true, + "dependencies": { + "@rushstack/node-core-library": "4.1.0", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/terminal/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@rushstack/terminal/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/@rushstack/ts-command-line": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.13.2.tgz", - "integrity": "sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.2.tgz", + "integrity": "sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==", "dev": true, "dependencies": { + "@rushstack/terminal": "0.10.1", "@types/argparse": "1.0.38", "argparse": "~1.0.9", - "colors": "~1.2.1", "string-argv": "~0.3.1" } }, @@ -1374,15 +1428,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "node_modules/colors": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", - "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2656,9 +2701,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/graphemer": { @@ -5073,7 +5118,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "node_modules/statuses": { @@ -5085,9 +5130,9 @@ } }, "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "engines": { "node": ">=0.6.19" @@ -5517,9 +5562,9 @@ } }, "node_modules/validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", "dev": true, "engines": { "node": ">= 0.10" @@ -5905,51 +5950,61 @@ "dev": true }, "@microsoft/api-extractor": { - "version": "7.34.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.34.4.tgz", - "integrity": "sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==", + "version": "7.43.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.43.1.tgz", + "integrity": "sha512-ohg40SsvFFgzHFAtYq5wKJc8ZDyY46bphjtnSvhSSlXpPTG7GHwyyXkn48UZiUCBwr2WC7TRC1Jfwz7nreuiyQ==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.26.4", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2", - "@rushstack/rig-package": "0.3.18", - "@rushstack/ts-command-line": "4.13.2", - "colors": "~1.2.1", + "@rushstack/node-core-library": "4.1.0", + "@rushstack/rig-package": "0.5.2", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "lodash": "~4.17.15", + "minimatch": "~3.0.3", "resolve": "~1.22.1", - "semver": "~7.3.0", + "semver": "~7.5.4", "source-map": "~0.6.1", - "typescript": "~4.8.4" + "typescript": "5.4.2" }, "dependencies": { + "minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true } } }, "@microsoft/api-extractor-model": { - "version": "7.26.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.26.4.tgz", - "integrity": "sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==", + "version": "7.28.14", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.14.tgz", + "integrity": "sha512-Bery/c8A8SsKPSvA82cTTuy/+OcxZbLRmKhPkk91/AJOQzxZsShcrmHFAGeiEqSIrv1nPZ3tKq9kfMLdCHmsqg==", "dev": true, "requires": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2" + "@rushstack/node-core-library": "4.1.0" } }, "@microsoft/tsdoc": { @@ -6015,24 +6070,23 @@ "dev": true }, "@rushstack/node-core-library": { - "version": "3.55.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.55.2.tgz", - "integrity": "sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.1.0.tgz", + "integrity": "sha512-qz4JFBZJCf1YN5cAXa1dP6Mki/HrsQxc/oYGAGx29dF2cwF2YMxHoly0FBhMw3IEnxo5fMj0boVfoHVBkpkx/w==", "dev": true, "requires": { - "colors": "~1.2.1", "fs-extra": "~7.0.1", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", - "semver": "~7.3.0", + "semver": "~7.5.4", "z-schema": "~5.0.2" }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -6041,24 +6095,51 @@ } }, "@rushstack/rig-package": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.18.tgz", - "integrity": "sha512-SGEwNTwNq9bI3pkdd01yCaH+gAsHqs0uxfGvtw9b0LJXH52qooWXnrFTRRLG1aL9pf+M2CARdrA9HLHJys3jiQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", + "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", "dev": true, "requires": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, + "@rushstack/terminal": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.1.tgz", + "integrity": "sha512-C6Vi/m/84IYJTkfzmXr1+W8Wi3MmBjVF/q3za91Gb3VYjKbpALHVxY6FgH625AnDe5Z0Kh4MHKWA3Z7bqgAezA==", + "dev": true, + "requires": { + "@rushstack/node-core-library": "4.1.0", + "supports-color": "~8.1.1" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@rushstack/ts-command-line": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.13.2.tgz", - "integrity": "sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.2.tgz", + "integrity": "sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==", "dev": true, "requires": { + "@rushstack/terminal": "0.10.1", "@types/argparse": "1.0.38", "argparse": "~1.0.9", - "colors": "~1.2.1", "string-argv": "~0.3.1" } }, @@ -6791,12 +6872,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "colors": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", - "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==", - "dev": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -7728,9 +7803,9 @@ } }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "graphemer": { @@ -9477,7 +9552,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "statuses": { @@ -9486,9 +9561,9 @@ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true }, "string-width": { @@ -9805,9 +9880,9 @@ } }, "validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", "dev": true }, "vary": { diff --git a/package.json b/package.json index 7092bfc9..f460a687 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "author": "Google Inc.", "license": "Apache-2.0", "devDependencies": { - "@microsoft/api-extractor": "^7.18.20", + "@microsoft/api-extractor": "^7.43.1", "@types/body-parser": "1.19.5", "@types/minimist": "1.2.5", "@types/mocha": "9.1.1", diff --git a/src/function_registry.ts b/src/function_registry.ts index 60261d6c..cc7038e4 100644 --- a/src/function_registry.ts +++ b/src/function_registry.ts @@ -105,6 +105,7 @@ export const cloudEvent = ( * Register a function that handles strongly typed invocations. * @param functionName - the name of the function * @param handler - the function to trigger + * @internal */ export const typed = ( functionName: string, diff --git a/src/functions.ts b/src/functions.ts index cd37b8f4..c6f1e040 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -44,6 +44,10 @@ export interface Request extends ExpressRequest { * Cloud Trace span ID. */ spanId?: string; + /** + * An AbortController used to signal cancellation of a function invocation (e.g. in case of time out). + */ + abortController?: AbortController; } /** @@ -194,7 +198,7 @@ export interface InvocationFormat { /** * Creates an instance of the request type from an invocation request. * - * @param request the request body as raw bytes + * @param request - the request body as raw bytes */ deserializeRequest(request: InvocationRequest): T | Promise; diff --git a/src/main.ts b/src/main.ts index 3dd98398..2f1684c7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,12 +49,12 @@ export const main = async () => { // eslint-disable-next-line no-process-exit process.exit(1); } + const {userFunction, signatureType} = loadedFunction; - const server = getServer( - userFunction!, - signatureType, - options.enableExecutionId - ); + // It is possible to overwrite the configured signature type in code so we + // reset it here based on what we loaded. + options.signatureType = signatureType; + const server = getServer(userFunction!, options); const errorHandler = new ErrorHandler(server); server .listen(options.port, () => { diff --git a/src/middleware/timeout.ts b/src/middleware/timeout.ts new file mode 100644 index 00000000..8cf202a8 --- /dev/null +++ b/src/middleware/timeout.ts @@ -0,0 +1,37 @@ +import {Request, Response} from '../functions'; +import {NextFunction} from 'express'; + +export const timeoutMiddleware = (timeoutMilliseconds: number) => { + return (req: Request, res: Response, next: NextFunction) => { + // In modern versions of Node.js that support the AbortController API we add one to + // signal function timeout. + if (timeoutMilliseconds > 0 && 'AbortController' in global) { + req.abortController = new AbortController(); + req.setTimeout(timeoutMilliseconds); + let executionComplete = false; + res.on('timeout', () => { + // This event is triggered when the underlying socket times out due to inactivity. + if (!executionComplete) { + executionComplete = true; + req.abortController?.abort('timeout'); + } + }); + req.on('close', () => { + // This event is triggered when the underlying HTTP connection is closed. This can + // happen if the data plane times out the request, the client disconnects or the + // response is complete. + if (!executionComplete) { + executionComplete = true; + req.abortController?.abort('request closed'); + } + }); + req.on('end', () => { + // This event is triggered when the function execution completes and we + // write an HTTP response. + executionComplete = true; + }); + } + // Always call next to continue middleware processing. + next(); + }; +}; diff --git a/src/options.ts b/src/options.ts index d761b86f..7f1cd7e3 100644 --- a/src/options.ts +++ b/src/options.ts @@ -52,6 +52,10 @@ export interface FrameworkOptions { * Whether or not to enable execution id support. */ enableExecutionId: boolean; + /** + * The request timeout. + */ + timeoutMilliseconds: number; } /** @@ -112,6 +116,20 @@ const SignatureOption = new ConfigurableOption( ); } ); +const TimeoutOption = new ConfigurableOption( + 'timeout', + 'CLOUD_RUN_TIMEOUT_SECONDS', + 0, + (x: string | number) => { + if (typeof x === 'string') { + x = parseInt(x, 10); + } + if (isNaN(x) || x < 0) { + throw new OptionsError('Timeout must be a positive integer'); + } + return x * 1000; + } +); export const requiredNodeJsVersionForLogExecutionID = '13.0.0'; const ExecutionIdOption = new ConfigurableOption( @@ -158,6 +176,7 @@ export const parseOptions = ( FunctionTargetOption.cliOption, SignatureOption.cliOption, SourceLocationOption.cliOption, + TimeoutOption.cliOption, ], }); return { @@ -165,6 +184,7 @@ export const parseOptions = ( target: FunctionTargetOption.parse(argv, envVars), sourceLocation: SourceLocationOption.parse(argv, envVars), signatureType: SignatureOption.parse(argv, envVars), + timeoutMilliseconds: TimeoutOption.parse(argv, envVars), printHelp: cliArgs[2] === '-h' || cliArgs[2] === '--help', enableExecutionId: ExecutionIdOption.parse(argv, envVars), }; diff --git a/src/server.ts b/src/server.ts index e4080d5c..9ce9396a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -17,27 +17,27 @@ import * as express from 'express'; import * as http from 'http'; import * as onFinished from 'on-finished'; import {HandlerFunction, Request, Response} from './functions'; -import {SignatureType} from './types'; import {setLatestRes} from './invoker'; import {legacyPubSubEventMiddleware} from './pubsub_middleware'; import {cloudEventToBackgroundEventMiddleware} from './middleware/cloud_event_to_background_event'; import {backgroundEventToCloudEventMiddleware} from './middleware/background_event_to_cloud_event'; +import {timeoutMiddleware} from './middleware/timeout'; import {wrapUserFunction} from './function_wrappers'; import {asyncLocalStorageMiddleware} from './async_local_storage'; import {executionContextMiddleware} from './execution_context'; import {errorHandler} from './logger'; +import {FrameworkOptions} from './options'; /** * Creates and configures an Express application and returns an HTTP server * which will run it. * @param userFunction User's function. - * @param functionSignatureType Type of user's function signature. + * @param options the configured Function Framework options. * @return HTTP server. */ export function getServer( userFunction: HandlerFunction, - functionSignatureType: SignatureType, - enableExecutionId: boolean + options: FrameworkOptions ): http.Server { // App to use for function executions. const app = express(); @@ -89,7 +89,7 @@ export function getServer( }; // Apply middleware - if (functionSignatureType !== 'typed') { + if (options.signatureType !== '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)); @@ -120,8 +120,8 @@ export function getServer( app.use(asyncLocalStorageMiddleware); if ( - functionSignatureType === 'event' || - functionSignatureType === 'cloudevent' + options.signatureType === 'event' || + options.signatureType === 'cloudevent' ) { // If a Pub/Sub subscription is configured to invoke a user's function directly, the request body // needs to be marshalled into the structure that wrapEventFunction expects. This unblocks local @@ -129,14 +129,14 @@ export function getServer( app.use(legacyPubSubEventMiddleware); } - if (functionSignatureType === 'event') { + if (options.signatureType === 'event') { app.use(cloudEventToBackgroundEventMiddleware); } - if (functionSignatureType === 'cloudevent') { + if (options.signatureType === 'cloudevent') { app.use(backgroundEventToCloudEventMiddleware); } - if (functionSignatureType === 'http') { + if (options.signatureType === 'http') { app.use('/favicon.ico|/robots.txt', (req, res) => { // Neither crawlers nor browsers attempting to pull the icon find the body // contents particularly useful, so we send nothing in the response body. @@ -151,16 +151,18 @@ export function getServer( }); } + app.use(timeoutMiddleware(options.timeoutMilliseconds)); + // Set up the routes for the user's function - const requestHandler = wrapUserFunction(userFunction, functionSignatureType); - if (functionSignatureType === 'http') { + const requestHandler = wrapUserFunction(userFunction, options.signatureType); + if (options.signatureType === 'http') { app.all('/*', requestHandler); } else { app.post('/*', requestHandler); } // Error Handler - if (enableExecutionId) { + if (options.enableExecutionId) { app.use(errorHandler); } diff --git a/src/testing.ts b/src/testing.ts index f66baad7..3360a000 100644 --- a/src/testing.ts +++ b/src/testing.ts @@ -48,9 +48,13 @@ export const getTestServer = (functionName: string): Server => { `The provided function "${functionName}" was not registered. Did you forget to require the module that defined it?` ); } - return getServer( - registeredFunction.userFunction, - registeredFunction.signatureType, - /*enableExecutionId=*/ false - ); + return getServer(registeredFunction.userFunction, { + signatureType: registeredFunction.signatureType, + enableExecutionId: false, + timeoutMilliseconds: 0, + port: '0', + target: '', + sourceLocation: '', + printHelp: false, + }); }; diff --git a/test/integration/legacy_event.ts b/test/integration/legacy_event.ts index 3213acd9..a25250e6 100644 --- a/test/integration/legacy_event.ts +++ b/test/integration/legacy_event.ts @@ -17,6 +17,7 @@ import * as functions from '../../src/functions'; import * as sinon from 'sinon'; import {getServer} from '../../src/server'; import * as supertest from 'supertest'; +import {SignatureType} from '../../src/types'; const TEST_CLOUD_EVENT = { specversion: '1.0', @@ -31,6 +32,16 @@ const TEST_CLOUD_EVENT = { }, }; +const testOptions = { + signatureType: 'event' as SignatureType, + enableExecutionId: false, + timeoutMilliseconds: 0, + port: '0', + target: '', + sourceLocation: '', + printHelp: false, +}; + describe('Event Function', () => { beforeEach(() => { // Prevent log spew from the PubSub emulator request. @@ -181,14 +192,10 @@ describe('Event Function', () => { it(test.name, async () => { let receivedData: {} | null = null; let receivedContext: functions.CloudFunctionsContext | null = null; - const server = getServer( - (data: {}, context: functions.Context) => { - receivedData = data; - receivedContext = context as functions.CloudFunctionsContext; - }, - 'event', - /*enableExecutionId=*/ false - ); + const server = getServer((data: {}, context: functions.Context) => { + receivedData = data; + receivedContext = context as functions.CloudFunctionsContext; + }, testOptions); const requestHeaders = { 'Content-Type': 'application/json', ...test.headers, @@ -204,13 +211,9 @@ describe('Event Function', () => { }); it('returns a 500 if the function throws an exception', async () => { - const server = getServer( - () => { - throw 'I crashed'; - }, - 'event', - /*enableExecutionId=*/ false - ); + const server = getServer(() => { + throw 'I crashed'; + }, testOptions); await supertest(server) .post('/') .send({ diff --git a/test/middleware/timeout.ts b/test/middleware/timeout.ts new file mode 100644 index 00000000..08f1de01 --- /dev/null +++ b/test/middleware/timeout.ts @@ -0,0 +1,58 @@ +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import {NextFunction} from 'express'; +import {Request, Response} from '../../src/functions'; + +import {timeoutMiddleware} from '../../src/middleware/timeout'; + +describe('timeoutMiddleware', () => { + let request: Request; + let response: Response; + let next: NextFunction; + beforeEach(() => { + request = { + setTimeout: sinon.spy(), + on: sinon.spy(), + } as unknown as Request; + response = { + on: sinon.spy(), + } as unknown as Response; + next = sinon.spy(); + }); + + it('calls the next function', () => { + const middleware = timeoutMiddleware(1000); + middleware(request, response, next); + assert.strictEqual((next as sinon.SinonSpy).called, true); + }); + + it('adds an abort controller to the request', function() { + if (!('AbortController' in global)) { + this.skip(); + } + const middleware = timeoutMiddleware(1000); + middleware(request, response, next); + assert.strictEqual(!!request.abortController, true); + }); + + it('adds an abort controller to the request', function() { + if (!('AbortController' in global)) { + this.skip(); + } + const middleware = timeoutMiddleware(1000); + middleware(request, response, next); + assert.strictEqual(!!request.abortController, true); + }); + + it('sets the request timeout', function() { + if (!('AbortController' in global)) { + this.skip(); + } + const middleware = timeoutMiddleware(1000); + middleware(request, response, next); + assert.strictEqual( + (request.setTimeout as sinon.SinonSpy).calledWith(1000), + true + ); + }); +}); diff --git a/test/options.ts b/test/options.ts index e928a0e4..7193f8fc 100644 --- a/test/options.ts +++ b/test/options.ts @@ -59,6 +59,7 @@ describe('parseOptions', () => { signatureType: 'http', printHelp: false, enableExecutionId: false, + timeoutMilliseconds: 0, }, }, { @@ -72,6 +73,8 @@ describe('parseOptions', () => { '--signature-type', 'cloudevent', '--source=/source', + '--timeout', + '6', ], envVars: {}, expectedOptions: { @@ -81,6 +84,7 @@ describe('parseOptions', () => { signatureType: 'cloudevent', printHelp: false, enableExecutionId: false, + timeoutMilliseconds: 6000, }, }, { @@ -91,6 +95,7 @@ describe('parseOptions', () => { FUNCTION_TARGET: 'helloWorld', FUNCTION_SIGNATURE_TYPE: 'cloudevent', FUNCTION_SOURCE: '/source', + CLOUD_RUN_TIMEOUT_SECONDS: '2', }, expectedOptions: { port: '1234', @@ -99,6 +104,7 @@ describe('parseOptions', () => { signatureType: 'cloudevent', printHelp: false, enableExecutionId: false, + timeoutMilliseconds: 2000, }, }, { @@ -112,12 +118,14 @@ describe('parseOptions', () => { '--signature-type', 'cloudevent', '--source=/source', + '--timeout=3', ], envVars: { PORT: '4567', FUNCTION_TARGET: 'fooBar', FUNCTION_SIGNATURE_TYPE: 'event', FUNCTION_SOURCE: '/somewhere/else', + CLOUD_RUN_TIMEOUT_SECONDS: '5', }, expectedOptions: { port: '1234', @@ -126,6 +134,7 @@ describe('parseOptions', () => { signatureType: 'cloudevent', printHelp: false, enableExecutionId: false, + timeoutMilliseconds: 3000, }, }, ]; @@ -197,9 +206,28 @@ describe('parseOptions', () => { }); }); - it('throws an exception for invalid signature types', () => { - assert.throws(() => { - parseOptions(['bin/node', 'index.js', '--signature-type=monkey']); + const validationErrorTestCases: TestData[] = [ + { + name: 'signature type is invalid', + cliOpts: ['bin/node', 'index.js', '--signature-type=monkey'], + envVars: {}, + }, + { + name: 'timeout is not a number', + cliOpts: ['bin/node', '/index.js', '--timeout=foobar'], + envVars: {}, + }, + { + name: 'timeout is a negative number', + cliOpts: ['bin/node', '/index.js', '--timeout=-10'], + envVars: {}, + }, + ]; + validationErrorTestCases.forEach(testCase => { + it('throws an exception when ' + testCase.name, () => { + assert.throws(() => { + parseOptions(testCase.cliOpts, testCase.envVars); + }); }); }); });