├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── policies │ └── resourceManagement.yml └── workflows │ └── codeql.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── azure-pipelines ├── code-mirror.yml ├── official-build.yml ├── public-build.yml ├── release.yml └── templates │ ├── build.yml │ └── test.yml ├── package-lock.json ├── package.json ├── scripts ├── .eslintrc.json ├── updateVersion.ts └── validateRelease.ts ├── src ├── InvocationContext.ts ├── InvocationModel.ts ├── ProgrammingModel.ts ├── addBindingName.ts ├── app.ts ├── constants.ts ├── converters │ ├── fromRpcBindings.ts │ ├── fromRpcContext.ts │ ├── fromRpcNullable.ts │ ├── fromRpcTriggerMetadata.ts │ ├── fromRpcTypedData.ts │ ├── toCamelCase.ts │ ├── toCoreFunctionMetadata.ts │ ├── toRpcDuration.ts │ ├── toRpcHttp.ts │ ├── toRpcHttpCookie.ts │ ├── toRpcNullable.ts │ └── toRpcTypedData.ts ├── errors.ts ├── hooks │ ├── AppStartContext.ts │ ├── AppTerminateContext.ts │ ├── HookContext.ts │ ├── InvocationHookContext.ts │ ├── LogHookContext.ts │ ├── PostInvocationContext.ts │ ├── PreInvocationContext.ts │ └── registerHook.ts ├── http │ ├── HttpRequest.ts │ ├── HttpResponse.ts │ ├── extractHttpUserFromHeaders.ts │ └── httpProxy.ts ├── index.ts ├── input.ts ├── output.ts ├── setup.ts ├── trigger.ts └── utils │ ├── Disposable.ts │ ├── fallbackLogHandler.ts │ ├── getRandomHexString.ts │ ├── isTrigger.ts │ ├── nonNull.ts │ ├── tryGetCoreApiLazy.ts │ ├── util.ts │ └── workerSystemLog.ts ├── test ├── .eslintrc.json ├── InvocationModel.test.ts ├── Types.test.ts ├── converters │ ├── fromRpcContext.test.ts │ ├── fromRpcTriggerMetadata.test.ts │ ├── fromRpcTypedData.test.ts │ ├── toCamelCase.test.ts │ ├── toCoreFunctionMetadata.test.ts │ ├── toRpcDuration.test.ts │ ├── toRpcHttp.test.ts │ ├── toRpcHttpCookie.test.ts │ ├── toRpcNullable.test.ts │ └── toRpcTypedData.test.ts ├── errors.test.ts ├── hooks.test.ts ├── http │ ├── HttpRequest.test.ts │ ├── HttpResponse.test.ts │ └── extractHttpUserFromHeaders.test.ts ├── index.ts ├── setup.test.ts └── types │ ├── index.test.ts │ └── tsconfig.json ├── tsconfig.json ├── types-core └── index.d.ts ├── types ├── InvocationContext.d.ts ├── app.d.ts ├── cosmosDB.d.ts ├── cosmosDB.v3.d.ts ├── cosmosDB.v4.d.ts ├── eventGrid.d.ts ├── eventHub.d.ts ├── generic.d.ts ├── hooks │ ├── HookContext.d.ts │ ├── appHooks.d.ts │ ├── invocationHooks.d.ts │ ├── logHooks.d.ts │ └── registerHook.d.ts ├── http.d.ts ├── index.d.ts ├── input.d.ts ├── mySql.d.ts ├── output.d.ts ├── serviceBus.d.ts ├── setup.d.ts ├── sql.d.ts ├── storage.d.ts ├── table.d.ts ├── timer.d.ts ├── trigger.d.ts ├── warmup.d.ts └── webpubsub.d.ts └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "header", "deprecation", "simple-import-sort", "import"], 4 | "parserOptions": { 5 | "project": "tsconfig.json", 6 | "sourceType": "module" 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 12 | "plugin:prettier/recommended" 13 | ], 14 | "rules": { 15 | "header/header": [ 16 | 2, 17 | "line", 18 | [" Copyright (c) .NET Foundation. All rights reserved.", " Licensed under the MIT License."], 19 | 2 20 | ], 21 | "deprecation/deprecation": "error", 22 | "@typescript-eslint/no-empty-interface": "off", 23 | "@typescript-eslint/no-explicit-any": "off", 24 | "@typescript-eslint/no-namespace": "off", 25 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "ignoreRestSiblings": true }], 26 | "prefer-const": ["error", { "destructuring": "all" }], 27 | "@typescript-eslint/explicit-member-accessibility": [ 28 | "error", 29 | { 30 | "accessibility": "no-public" 31 | } 32 | ], 33 | "no-return-await": "off", 34 | "@typescript-eslint/return-await": "error", 35 | "eqeqeq": "error", 36 | "@typescript-eslint/no-empty-function": "off", 37 | "simple-import-sort/imports": [ 38 | "error", 39 | { 40 | "groups": [["^\\u0000", "^node:", "^@?\\w", "^", "^\\."]] 41 | } 42 | ], 43 | "simple-import-sort/exports": "error", 44 | "import/first": "error", 45 | "import/newline-after-import": "error", 46 | "import/no-duplicates": "error" 47 | }, 48 | "ignorePatterns": ["**/*.js", "**/*.mjs", "**/*.cjs", "out", "dist"] 49 | } 50 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @azure/azure-functions-nodejs -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | #### Investigative information 7 | 8 | Please provide the following: 9 | 10 | - Timestamp: 11 | - Function App name: 12 | - Function name(s) (as appropriate): 13 | - Invocation ID: 14 | - Region: 15 | 16 | 19 | 20 | #### Repro steps 21 | 22 | Provide the steps required to reproduce the problem: 23 | 24 | 30 | 31 | #### Expected behavior 32 | 33 | Provide a description of the expected behavior. 34 | 35 | 40 | 41 | #### Actual behavior 42 | 43 | Provide a description of the actual behavior observed. 44 | 45 | 50 | 51 | #### Known workarounds 52 | 53 | Provide a description of any known workarounds. 54 | 55 | 60 | 61 | #### Related information 62 | 63 | Provide any related information 64 | 65 | * Programming language used 66 | * Links to source 67 | * Bindings used 68 | 95 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: 'CodeQL' 13 | 14 | on: 15 | push: 16 | branches: ['v4.x', 'v*.x'] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ['v4.x'] 20 | schedule: 21 | - cron: '30 12 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | # Runner size impacts CodeQL analysis time. To learn more, please see: 27 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 28 | # - https://gh.io/supported-runners-and-hardware-resources 29 | # - https://gh.io/using-larger-runners 30 | # Consider using larger runners for possible analysis time improvements. 31 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 32 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 33 | permissions: 34 | actions: read 35 | contents: read 36 | security-events: write 37 | 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | language: ['javascript-typescript'] 42 | # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] 43 | # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both 44 | # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 45 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 46 | 47 | steps: 48 | - name: Checkout repository 49 | uses: actions/checkout@v3 50 | 51 | # Initializes the CodeQL tools for scanning. 52 | - name: Initialize CodeQL 53 | uses: github/codeql-action/init@v2 54 | with: 55 | languages: ${{ matrix.language }} 56 | # If you wish to specify custom queries, you can do so here or in a config file. 57 | # By default, queries listed here will override any specified in a config file. 58 | # Prefix the list here with "+" to use these queries and those in the config file. 59 | 60 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 61 | # queries: security-extended,security-and-quality 62 | 63 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). 64 | # If this step fails, then you should remove it and run the build manually (see below) 65 | - name: Autobuild 66 | uses: github/codeql-action/autobuild@v2 67 | 68 | # ℹ️ Command-line programs to run using the OS shell. 69 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 70 | 71 | # If the Autobuild fails above, remove it and uncomment the following three lines. 72 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 73 | 74 | # - run: | 75 | # echo "Run, Build Application using script" 76 | # ./location_of_script_within_repo/buildscript.sh 77 | 78 | - name: Perform CodeQL Analysis 79 | uses: github/codeql-action/analyze@v2 80 | with: 81 | category: '/language:${{matrix.language}}' 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | azure-functions-language-worker-protobuf/* 39 | 40 | dist 41 | out 42 | pkg 43 | *.tgz 44 | 45 | Azure.Functions.Cli 46 | Azure.Functions.Cli.zip 47 | test/end-to-end/testFunctionApp/bin 48 | test/end-to-end/testFunctionApp/obj 49 | test/end-to-end/Azure.Functions.NodejsWorker.E2E/.vs 50 | test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/bin 51 | test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/obj 52 | **/*-test-results.xml 53 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | 5 | # Exclude markdown until this bug is fixed: https://github.com/prettier/prettier/issues/5019 6 | *.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "printWidth": 120, 5 | "endOfLine": "auto" 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "attach", 7 | "name": "Attach by Process ID", 8 | "processId": "${command:PickProcess}" 9 | }, 10 | { 11 | "name": "Launch Unit Tests", 12 | "runtimeExecutable": "npm", 13 | "runtimeArgs": ["test"], 14 | "request": "launch", 15 | "skipFiles": ["/**"], 16 | "type": "pwa-node" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.showProjectWarning": false, 3 | "editor.codeActionsOnSave": ["source.fixAll"], 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "typescript.tsdk": "node_modules/typescript/lib", 7 | "typescript.preferences.importModuleSpecifier": "relative" 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "npm: watch", 6 | "type": "npm", 7 | "script": "watch", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "problemMatcher": ["$ts-checker-webpack-watch"], 13 | "isBackground": true, 14 | "presentation": { 15 | "reveal": "never" 16 | } 17 | }, 18 | { 19 | "type": "npm", 20 | "script": "lint", 21 | "problemMatcher": "$eslint-stylish", 22 | "label": "npm: lint" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | - Clone the repository locally and open in VS Code 4 | - Run "Extensions: Show Recommended Extensions" from the [command palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) and install all extensions listed under "Workspace Recommendations" 5 | - Run `npm install` 6 | - Run `npm run build` 7 | - Run `npm link` 8 | - Create or open a local function app to test with 9 | - In the local function app: 10 | - Run `npm link @azure/functions`. This will point your app to the local repository for the `@azure/functions` package 11 | - Add the following settings to your "local.settings.json" file or configure them directly as environment variables 12 | - `languageWorkers__node__arguments`: `--inspect` 13 | > 💡 Tip: Set `logging__logLevel__Worker` to `debug` if you want to view worker-specific logs in the output of `func start` 14 | - Start the app (i.e. run `func start` or press F5) 15 | - Back in the framework repository, press F5 and select the process for your running function app 16 | - Before you submit a PR, run `npm test` and fix any issues. If you want to debug the tests, switch your [launch profile](https://code.visualstudio.com/docs/editor/debugging) in VS Code to "Launch Unit Tests" and press F5. 17 | 18 | ## Code of Conduct 19 | 20 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 21 | 22 | ## Contributing to type definitions 23 | 24 | The type definitions are located in the `types` folder. Please make sure to update the tests in `./test/types/index.test.ts` as well. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) .NET Foundation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Functions Node.js Programming Model 2 | 3 | |Branch|Status|Support level|Node.js Versions| 4 | |---|---|---|---| 5 | |v4.x (default)|[![Build Status](https://img.shields.io/azure-devops/build/azfunc/public/514/v4.x)](https://azfunc.visualstudio.com/public/_build/latest?definitionId=514&branchName=v4.x) [![Test Status](https://img.shields.io/azure-devops/tests/azfunc/public/514/v4.x?compact_message)](https://azfunc.visualstudio.com/public/_build/latest?definitionId=514&branchName=v4.x)|GA|20, 18| 6 | |v3.x|[![Build Status](https://img.shields.io/azure-devops/build/azfunc/public/514/v3.x)](https://azfunc.visualstudio.com/public/_build/latest?definitionId=514&branchName=v3.x) [![Test Status](https://img.shields.io/azure-devops/tests/azfunc/public/514/v3.x?compact_message)](https://azfunc.visualstudio.com/public/_build/latest?definitionId=514&branchName=v3.x)|GA|20, 18| 7 | 8 | ## Install 9 | 10 | ```bash 11 | npm install @azure/functions 12 | ``` 13 | 14 | ## Documentation 15 | 16 | - [Azure Functions JavaScript Developer Guide](https://learn.microsoft.com/azure/azure-functions/functions-reference-node?pivots=nodejs-model-v4) 17 | - [Upgrade guide from v3 to v4](https://learn.microsoft.com/azure/azure-functions/functions-node-upgrade-v4) 18 | - [Create your first TypeScript function](https://docs.microsoft.com/azure/azure-functions/create-first-function-vs-code-typescript?pivots=nodejs-model-v4) 19 | - [Create your first JavaScript function](https://docs.microsoft.com/azure/azure-functions/create-first-function-vs-code-node?pivots=nodejs-model-v4) 20 | 21 | ## Considerations 22 | 23 | - The Node.js "programming model" shouldn't be confused with the Azure Functions "runtime". 24 | - _**Programming model**_: Defines how you author your code and is specific to JavaScript and TypeScript. 25 | - _**Runtime**_: Defines underlying behavior of Azure Functions and is shared across all languages. 26 | - The programming model version is strictly tied to the version of the [`@azure/functions`](https://www.npmjs.com/package/@azure/functions) npm package, and is versioned independently of the [runtime](https://learn.microsoft.com/azure/azure-functions/functions-versions?pivots=programming-language-javascript). Both the runtime and the programming model use "4" as their latest major version, but that is purely a coincidence. 27 | - You can't mix the v3 and v4 programming models in the same function app. As soon as you register one v4 function in your app, any v3 functions registered in _function.json_ files are ignored. 28 | 29 | ## Usage 30 | 31 | ### TypeScript 32 | 33 | ```typescript 34 | import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; 35 | 36 | export async function httpTrigger1(request: HttpRequest, context: InvocationContext): Promise { 37 | context.log(`Http function processed request for url "${request.url}"`); 38 | 39 | const name = request.query.get('name') || await request.text() || 'world'; 40 | 41 | return { body: `Hello, ${name}!` }; 42 | }; 43 | 44 | app.http('httpTrigger1', { 45 | methods: ['GET', 'POST'], 46 | authLevel: 'anonymous', 47 | handler: httpTrigger1 48 | }); 49 | ``` 50 | 51 | ### JavaScript 52 | 53 | ```javascript 54 | const { app } = require('@azure/functions'); 55 | 56 | app.http('httpTrigger1', { 57 | methods: ['GET', 'POST'], 58 | authLevel: 'anonymous', 59 | handler: async (request, context) => { 60 | context.log(`Http function processed request for url "${request.url}"`); 61 | 62 | const name = request.query.get('name') || await request.text() || 'world'; 63 | 64 | return { body: `Hello, ${name}!` }; 65 | } 66 | }); 67 | ``` 68 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /azure-pipelines/code-mirror.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - v*.x 5 | 6 | resources: 7 | repositories: 8 | - repository: eng 9 | type: git 10 | name: engineering 11 | ref: refs/tags/release 12 | 13 | variables: 14 | - template: ci/variables/cfs.yml@eng 15 | 16 | extends: 17 | template: ci/code-mirror.yml@eng 18 | -------------------------------------------------------------------------------- /azure-pipelines/official-build.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: IsPrerelease 3 | type: boolean 4 | default: true 5 | 6 | trigger: 7 | batch: true 8 | branches: 9 | include: 10 | - v4.x 11 | 12 | # CI only, does not trigger on PRs. 13 | pr: none 14 | 15 | schedules: 16 | - cron: '30 10 * * *' 17 | displayName: Nightly build 18 | always: true 19 | branches: 20 | include: 21 | - v4.x 22 | 23 | resources: 24 | repositories: 25 | - repository: 1es 26 | type: git 27 | name: 1ESPipelineTemplates/1ESPipelineTemplates 28 | ref: refs/tags/release 29 | 30 | extends: 31 | template: v1/1ES.Official.PipelineTemplate.yml@1es 32 | parameters: 33 | pool: 34 | name: 1es-pool-azfunc 35 | image: 1es-windows-2022 36 | os: windows 37 | 38 | sdl: 39 | codeql: 40 | runSourceLanguagesInSourceAnalysis: true 41 | 42 | stages: 43 | - stage: WindowsUnitTests 44 | dependsOn: [] 45 | jobs: 46 | - template: /azure-pipelines/templates/test.yml@self 47 | 48 | - stage: LinuxUnitTests 49 | dependsOn: [] 50 | jobs: 51 | - template: /azure-pipelines/templates/test.yml@self 52 | pool: 53 | name: 1es-pool-azfunc 54 | image: 1es-ubuntu-22.04 55 | os: linux 56 | 57 | - stage: Build 58 | dependsOn: [] 59 | jobs: 60 | - template: /azure-pipelines/templates/build.yml@self 61 | parameters: 62 | IsPrerelease: ${{ parameters.IsPrerelease }} 63 | -------------------------------------------------------------------------------- /azure-pipelines/public-build.yml: -------------------------------------------------------------------------------- 1 | # This build is used for public PR and CI builds. 2 | 3 | trigger: 4 | batch: true 5 | branches: 6 | include: 7 | - v4.x 8 | 9 | pr: 10 | branches: 11 | include: 12 | - v4.x 13 | 14 | schedules: 15 | - cron: '30 10 * * *' 16 | displayName: Nightly build 17 | always: true 18 | branches: 19 | include: 20 | - v4.x 21 | 22 | resources: 23 | repositories: 24 | - repository: 1es 25 | type: git 26 | name: 1ESPipelineTemplates/1ESPipelineTemplates 27 | ref: refs/tags/release 28 | 29 | extends: 30 | template: v1/1ES.Unofficial.PipelineTemplate.yml@1es 31 | parameters: 32 | pool: 33 | name: 1es-pool-azfunc-public 34 | image: 1es-windows-2022 35 | os: windows 36 | 37 | sdl: 38 | codeql: 39 | compiled: 40 | enabled: true 41 | runSourceLanguagesInSourceAnalysis: true 42 | 43 | settings: 44 | # PR's from forks do not have sufficient permissions to set tags. 45 | skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} 46 | 47 | stages: 48 | - stage: WindowsUnitTests 49 | dependsOn: [] 50 | jobs: 51 | - template: /azure-pipelines/templates/test.yml@self 52 | 53 | - stage: LinuxUnitTests 54 | dependsOn: [] 55 | jobs: 56 | - template: /azure-pipelines/templates/test.yml@self 57 | pool: 58 | name: 1es-pool-azfunc-public 59 | image: 1es-ubuntu-22.04 60 | os: linux 61 | 62 | - stage: Build 63 | dependsOn: [] 64 | jobs: 65 | - template: /azure-pipelines/templates/build.yml@self 66 | parameters: 67 | IsPrerelease: true 68 | -------------------------------------------------------------------------------- /azure-pipelines/release.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: NpmPublishTag 3 | displayName: 'Tag' 4 | type: string 5 | default: 'latest' 6 | - name: NpmPublishDryRun 7 | displayName: 'Dry Run' 8 | type: boolean 9 | default: true 10 | 11 | trigger: none 12 | pr: none 13 | 14 | resources: 15 | repositories: 16 | - repository: 1es 17 | type: git 18 | name: 1ESPipelineTemplates/1ESPipelineTemplates 19 | ref: refs/tags/release 20 | pipelines: 21 | - pipeline: officialBuild 22 | project: internal 23 | source: nodejs-library.official 24 | branch: v4.x 25 | 26 | extends: 27 | template: v1/1ES.Official.PipelineTemplate.yml@1es 28 | parameters: 29 | sdl: 30 | sourceAnalysisPool: 31 | name: 1es-pool-azfunc 32 | image: 1es-windows-2022 33 | os: windows 34 | codeql: 35 | runSourceLanguagesInSourceAnalysis: true 36 | 37 | stages: 38 | - stage: Release 39 | pool: 40 | name: 1es-pool-azfunc 41 | image: 1es-ubuntu-22.04 42 | os: linux 43 | jobs: 44 | - job: Release 45 | steps: 46 | - download: officialBuild 47 | - task: NodeTool@0 48 | displayName: 'Install Node.js' 49 | inputs: 50 | versionSpec: 18.x 51 | - script: npm ci 52 | displayName: 'npm ci' 53 | - script: 'npm run validateRelease -- --publishTag ${{ parameters.NpmPublishTag }} --dropPath "$(Pipeline.Workspace)/officialBuild/drop"' 54 | displayName: 'validate release' 55 | - script: mv *.tgz package.tgz 56 | displayName: 'Rename tgz file' # because the publish command below requires an exact path 57 | workingDirectory: '$(Pipeline.Workspace)/officialBuild/drop' 58 | - task: Npm@1 59 | displayName: 'npm publish' 60 | inputs: 61 | command: custom 62 | workingDir: '$(Pipeline.Workspace)/officialBuild/drop' 63 | verbose: true 64 | customCommand: 'publish package.tgz --tag ${{ parameters.NpmPublishTag }} --dry-run ${{ lower(parameters.NpmPublishDryRun) }}' 65 | customEndpoint: nodejs-library-npm 66 | -------------------------------------------------------------------------------- /azure-pipelines/templates/build.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: 3 | templateContext: 4 | outputs: 5 | - output: pipelineArtifact 6 | path: $(Build.ArtifactStagingDirectory)/dropOutput 7 | artifact: drop 8 | sbomBuildDropPath: '$(Build.ArtifactStagingDirectory)/dropInput' 9 | sbomPackageName: 'Azure Functions Node.js Programming Model' 10 | # The list of components can't be determined from the webpacked file in the staging dir, so reference the original node_modules folder 11 | sbomBuildComponentPath: '$(Build.SourcesDirectory)/node_modules' 12 | steps: 13 | - task: NodeTool@0 14 | inputs: 15 | versionSpec: 20.x 16 | displayName: 'Install Node.js' 17 | - script: npm ci 18 | displayName: 'npm ci' 19 | - script: npm audit --production 20 | displayName: 'Run vulnerability scan' 21 | - script: npm run updateVersion -- --buildNumber $(Build.BuildNumber) 22 | displayName: 'npm run updateVersion' 23 | condition: and(succeeded(), eq(${{ parameters.IsPrerelease }}, true)) 24 | - script: npm run build 25 | displayName: 'npm run build' 26 | - script: npm run minify 27 | displayName: 'npm run minify' 28 | - task: CopyFiles@2 29 | displayName: 'Copy files to staging' 30 | inputs: 31 | sourceFolder: '$(Build.SourcesDirectory)' 32 | contents: | 33 | dist/** 34 | src/** 35 | types/** 36 | LICENSE 37 | package.json 38 | README.md 39 | targetFolder: '$(Build.ArtifactStagingDirectory)/dropInput' 40 | cleanTargetFolder: true 41 | - script: npm prune --production 42 | displayName: 'npm prune --production' # so that only production dependencies are included in SBOM 43 | - script: npm pack 44 | displayName: 'npm pack' 45 | workingDirectory: '$(Build.ArtifactStagingDirectory)/dropInput' 46 | - script: mkdir dropOutput && mv dropInput/*.tgz dropOutput 47 | displayName: 'Move package to dropOutput' 48 | workingDirectory: '$(Build.ArtifactStagingDirectory)' 49 | -------------------------------------------------------------------------------- /azure-pipelines/templates/test.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: UnitTests 3 | 4 | strategy: 5 | matrix: 6 | Node18: 7 | NODE_VERSION: '18.x' 8 | Node20: 9 | NODE_VERSION: '20.x' 10 | Node22: 11 | NODE_VERSION: '22.x' 12 | 13 | steps: 14 | - task: NodeTool@0 15 | inputs: 16 | versionSpec: $(NODE_VERSION) 17 | displayName: 'Install Node.js' 18 | - script: npm ci 19 | displayName: 'npm ci' 20 | - script: npm run build 21 | displayName: 'npm run build' 22 | - script: npm run lint 23 | displayName: 'npm run lint' 24 | - script: npm run updateVersion -- --validate 25 | displayName: 'validate version' 26 | - script: npm test 27 | displayName: 'Run unit tests' 28 | - task: PublishTestResults@2 29 | displayName: 'Publish Unit Test Results' 30 | inputs: 31 | testResultsFiles: 'test/unit-test-results.xml' 32 | testRunTitle: '$(Agent.JobName)' 33 | condition: succeededOrFailed() 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@azure/functions", 3 | "version": "4.7.2", 4 | "description": "Microsoft Azure Functions NodeJS Framework", 5 | "keywords": [ 6 | "azure", 7 | "azure-functions", 8 | "serverless", 9 | "typescript" 10 | ], 11 | "author": "Microsoft", 12 | "license": "MIT", 13 | "homepage": "https://github.com/Azure/azure-functions-nodejs-library", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/Azure/azure-functions-nodejs-library.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/Azure/azure-functions-nodejs-library/issues" 20 | }, 21 | "main": "./dist/azure-functions.js", 22 | "types": "types/index.d.ts", 23 | "files": [ 24 | "dist/", 25 | "src/", 26 | "types/", 27 | "LICENSE", 28 | "README.md" 29 | ], 30 | "engines": { 31 | "node": ">=18.0" 32 | }, 33 | "scripts": { 34 | "build": "webpack --mode development", 35 | "minify": "webpack --mode production", 36 | "test": "ts-node ./test/index.ts", 37 | "format": "prettier . --write", 38 | "lint": "eslint . --fix", 39 | "updateVersion": "ts-node ./scripts/updateVersion.ts", 40 | "validateRelease": "ts-node ./scripts/validateRelease.ts", 41 | "watch": "webpack --watch --mode development" 42 | }, 43 | "dependencies": { 44 | "cookie": "^0.7.0", 45 | "long": "^4.0.0", 46 | "undici": "^5.29.0" 47 | }, 48 | "devDependencies": { 49 | "@types/chai": "^4.2.22", 50 | "@types/chai-as-promised": "^7.1.5", 51 | "@types/cookie": "^0.6.0", 52 | "@types/fs-extra": "^9.0.13", 53 | "@types/long": "^4.0.2", 54 | "@types/minimist": "^1.2.2", 55 | "@types/mocha": "^9.1.1", 56 | "@types/node": "^18.0.0", 57 | "@types/semver": "^7.3.9", 58 | "@typescript-eslint/eslint-plugin": "^5.12.1", 59 | "@typescript-eslint/parser": "^5.12.1", 60 | "chai": "^4.2.0", 61 | "chai-as-promised": "^7.1.1", 62 | "eslint": "^7.32.0", 63 | "eslint-config-prettier": "^8.3.0", 64 | "eslint-plugin-deprecation": "^1.3.2", 65 | "eslint-plugin-header": "^3.1.1", 66 | "eslint-plugin-import": "^2.29.0", 67 | "eslint-plugin-prettier": "^4.0.0", 68 | "eslint-webpack-plugin": "^3.2.0", 69 | "eslint-plugin-simple-import-sort": "^10.0.0", 70 | "fork-ts-checker-webpack-plugin": "^7.2.13", 71 | "fs-extra": "^10.0.1", 72 | "globby": "^11.0.0", 73 | "minimist": "^1.2.6", 74 | "mocha": "^11.1.0", 75 | "mocha-junit-reporter": "^2.0.2", 76 | "mocha-multi-reporters": "^1.5.1", 77 | "prettier": "^2.4.1", 78 | "semver": "^7.3.5", 79 | "ts-loader": "^9.3.1", 80 | "ts-node": "^3.3.0", 81 | "typescript": "^4.5.5", 82 | "typescript4": "npm:typescript@~4.0.0", 83 | "webpack": "^5.74.0", 84 | "webpack-cli": "^4.10.0" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /scripts/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/no-unsafe-argument": "off", 4 | "@typescript-eslint/no-unsafe-assignment": "off", 5 | "@typescript-eslint/no-unsafe-call": "off", 6 | "@typescript-eslint/no-unsafe-member-access": "off", 7 | "@typescript-eslint/no-unsafe-return": "off", 8 | "@typescript-eslint/restrict-template-expressions": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /scripts/updateVersion.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { execSync } from 'child_process'; 5 | import { readFileSync, readJSONSync, writeFileSync } from 'fs-extra'; 6 | import * as parseArgs from 'minimist'; 7 | import * as path from 'path'; 8 | import * as semver from 'semver'; 9 | 10 | const repoRoot = path.join(__dirname, '..'); 11 | const packageJsonPath = path.join(repoRoot, 'package.json'); 12 | const constantsPath = path.join(repoRoot, 'src', 'constants.ts'); 13 | const constantsVersionRegex = /version = '(.*)'/i; 14 | 15 | const args = parseArgs(process.argv.slice(2)); 16 | if (args.validate) { 17 | validateVersion(); 18 | } else if (args.version) { 19 | updateVersion(args.version); 20 | } else if (args.buildNumber) { 21 | const currentVersion = validateVersion(); 22 | const newVersion = currentVersion.includes('alpha') 23 | ? `${currentVersion}.${args.buildNumber}` 24 | : `${currentVersion}-alpha.${args.buildNumber}`; 25 | updateVersion(newVersion); 26 | } else { 27 | console.log(`This script can be used to either update the version of the library or validate that the repo is in a valid state with regards to versioning. 28 | 29 | Example usage: 30 | 31 | npm run updateVersion -- --version 3.3.0 32 | npm run updateVersion -- --buildNumber 20230517.1 33 | npm run updateVersion -- --validate`); 34 | throw new Error('Invalid arguments'); 35 | } 36 | 37 | function validateVersion(): string { 38 | const packageJson = readJSONSync(packageJsonPath); 39 | const packageJsonVersion: string = packageJson.version; 40 | 41 | const constantsVersion = getVersion(constantsPath, constantsVersionRegex); 42 | 43 | console.log('Found the following versions:'); 44 | console.log(`- package.json: ${packageJsonVersion}`); 45 | console.log(`- src/constants.ts: ${constantsVersion}`); 46 | 47 | const parsedVersion = semver.parse(packageJsonVersion); 48 | 49 | if (!packageJsonVersion || !constantsVersion || !parsedVersion) { 50 | throw new Error('Failed to detect valid versions in all expected files'); 51 | } else if (constantsVersion !== packageJsonVersion) { 52 | throw new Error(`Versions do not match.`); 53 | } else { 54 | console.log('Versions match! 🎉'); 55 | return packageJsonVersion; 56 | } 57 | } 58 | 59 | function getVersion(filePath: string, regex: RegExp): string { 60 | const fileContents = readFileSync(filePath).toString(); 61 | const match = fileContents.match(regex); 62 | if (!match || !match[1]) { 63 | throw new Error(`Failed to find match for "${regex.source}".`); 64 | } 65 | return match[1]; 66 | } 67 | 68 | function updateVersion(newVersion: string) { 69 | updatePackageJsonVersion(repoRoot, newVersion); 70 | 71 | updateVersionByRegex(constantsPath, constantsVersionRegex, newVersion); 72 | } 73 | 74 | function updatePackageJsonVersion(cwd: string, newVersion: string) { 75 | execSync(`npm version ${newVersion} --no-git-tag-version --allow-same-version`, { cwd }); 76 | console.log(`Updated ${cwd}/package.json to version ${newVersion}`); 77 | } 78 | 79 | function updateVersionByRegex(filePath: string, regex: RegExp, newVersion: string) { 80 | const oldFileContents = readFileSync(filePath).toString(); 81 | const match = oldFileContents.match(regex); 82 | if (!match || !match[0] || !match[1]) { 83 | throw new Error(`Failed to find match for "${regex.source}".`); 84 | } 85 | const [oldLine, oldVersion] = match; 86 | const newLine = oldLine.replace(oldVersion, newVersion); 87 | const newFileContents = oldFileContents.replace(oldLine, newLine); 88 | writeFileSync(filePath, newFileContents); 89 | console.log(`Updated ${filePath} from ${oldVersion} to version ${newVersion}`); 90 | } 91 | -------------------------------------------------------------------------------- /scripts/validateRelease.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { readdirSync } from 'fs-extra'; 5 | import * as parseArgs from 'minimist'; 6 | 7 | const args = parseArgs(process.argv.slice(2)); 8 | if (args.publishTag && args.dropPath) { 9 | validateRelease(args.publishTag, args.dropPath); 10 | } else { 11 | console.log(`This script can be used to validate that a release tag and version are in an expected format 12 | 13 | Example usage: 14 | 15 | npm run validateRelease -- --publishTag preview --dropPath /example/path/`); 16 | throw new Error('Invalid arguments'); 17 | } 18 | 19 | function validateRelease(publishTag: string, dropPath: string): void { 20 | const files = readdirSync(dropPath).filter((f) => f.endsWith('.tgz')); 21 | if (files.length !== 1) { 22 | throw new Error('Drop path should have one tgz file'); 23 | } 24 | 25 | const match = files[0]?.match(/^azure-functions-(.*)\.tgz$/); 26 | if (!match || !match[1]) { 27 | throw new Error(`Unrecognized tgz file name "${files[0]}"`); 28 | } 29 | 30 | const versionNumber = match[1]; 31 | let regex: RegExp; 32 | let expectedFormat: string; 33 | switch (publishTag) { 34 | case 'preview': 35 | regex = /^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$/; 36 | expectedFormat = 'x.x.x-alpha.x'; 37 | break; 38 | case 'latest': 39 | case 'legacy': 40 | regex = /^[0-9]+\.[0-9]+\.[0-9]+$/; 41 | expectedFormat = 'x.x.x'; 42 | break; 43 | default: 44 | throw new Error(`Unrecognized publish tag "${publishTag}"`); 45 | } 46 | if (!regex.test(versionNumber)) { 47 | throw new Error( 48 | `Version number for tag "${publishTag}" should be in format "${expectedFormat}". Instead got "${versionNumber}"` 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/InvocationContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { 6 | EffectiveFunctionOptions, 7 | InvocationContextInit, 8 | LogHandler, 9 | RetryContext, 10 | TraceContext, 11 | TriggerMetadata, 12 | } from '@azure/functions'; 13 | import { fallbackLogHandler } from './utils/fallbackLogHandler'; 14 | 15 | export class InvocationContext implements types.InvocationContext { 16 | invocationId: string; 17 | functionName: string; 18 | extraInputs: InvocationContextExtraInputs; 19 | extraOutputs: InvocationContextExtraOutputs; 20 | retryContext?: RetryContext; 21 | traceContext?: TraceContext; 22 | triggerMetadata?: TriggerMetadata; 23 | options: EffectiveFunctionOptions; 24 | #userLogHandler: LogHandler; 25 | 26 | constructor(init?: InvocationContextInit) { 27 | init = init || {}; 28 | const fallbackString = 'unknown'; 29 | this.invocationId = init.invocationId || fallbackString; 30 | this.functionName = init.functionName || fallbackString; 31 | this.extraInputs = new InvocationContextExtraInputs(); 32 | this.extraOutputs = new InvocationContextExtraOutputs(); 33 | this.retryContext = init.retryContext; 34 | this.traceContext = init.traceContext; 35 | this.triggerMetadata = init.triggerMetadata; 36 | this.options = { 37 | trigger: init.options?.trigger || { 38 | name: fallbackString, 39 | type: fallbackString, 40 | }, 41 | return: init.options?.return, 42 | extraInputs: init.options?.extraInputs || [], 43 | extraOutputs: init.options?.extraOutputs || [], 44 | }; 45 | this.#userLogHandler = init.logHandler || fallbackLogHandler; 46 | } 47 | 48 | log(...args: unknown[]): void { 49 | this.#userLogHandler('information', ...args); 50 | } 51 | 52 | trace(...args: unknown[]): void { 53 | this.#userLogHandler('trace', ...args); 54 | } 55 | 56 | debug(...args: unknown[]): void { 57 | this.#userLogHandler('debug', ...args); 58 | } 59 | 60 | info(...args: unknown[]): void { 61 | this.#userLogHandler('information', ...args); 62 | } 63 | 64 | warn(...args: unknown[]): void { 65 | this.#userLogHandler('warning', ...args); 66 | } 67 | 68 | error(...args: unknown[]): void { 69 | this.#userLogHandler('error', ...args); 70 | } 71 | } 72 | 73 | class InvocationContextExtraInputs implements types.InvocationContextExtraInputs { 74 | #inputs: Record = {}; 75 | get(inputOrName: types.FunctionInput | string): any { 76 | const name = typeof inputOrName === 'string' ? inputOrName : inputOrName.name; 77 | return this.#inputs[name]; 78 | } 79 | set(inputOrName: types.FunctionInput | string, value: unknown): void { 80 | const name = typeof inputOrName === 'string' ? inputOrName : inputOrName.name; 81 | this.#inputs[name] = value; 82 | } 83 | } 84 | 85 | class InvocationContextExtraOutputs implements types.InvocationContextExtraOutputs { 86 | #outputs: Record = {}; 87 | get(outputOrName: types.FunctionOutput | string): unknown { 88 | const name = typeof outputOrName === 'string' ? outputOrName : outputOrName.name; 89 | return this.#outputs[name]; 90 | } 91 | set(outputOrName: types.FunctionOutput | string, value: unknown): void { 92 | const name = typeof outputOrName === 'string' ? outputOrName : outputOrName.name; 93 | this.#outputs[name] = value; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/ProgrammingModel.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as coreTypes from '@azure/functions-core'; 5 | import { CoreInvocationContext, WorkerCapabilities } from '@azure/functions-core'; 6 | import { version } from './constants'; 7 | import { setupHttpProxy } from './http/httpProxy'; 8 | import { InvocationModel } from './InvocationModel'; 9 | import { capabilities as libraryCapabilities, enableHttpStream, lockSetup } from './setup'; 10 | 11 | export class ProgrammingModel implements coreTypes.ProgrammingModel { 12 | name = '@azure/functions'; 13 | version = version; 14 | 15 | getInvocationModel(coreCtx: CoreInvocationContext): InvocationModel { 16 | return new InvocationModel(coreCtx); 17 | } 18 | 19 | async getCapabilities(workerCapabilities: WorkerCapabilities): Promise { 20 | lockSetup(); 21 | 22 | if (enableHttpStream) { 23 | const httpUri = await setupHttpProxy(); 24 | workerCapabilities.HttpUri = httpUri; 25 | } 26 | 27 | Object.assign(workerCapabilities, libraryCapabilities); 28 | 29 | return workerCapabilities; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/addBindingName.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { getStringHash } from './utils/getRandomHexString'; 5 | 6 | /** 7 | * If the host spawns multiple workers, it expects the metadata (including binding name) to be the same across workers. 8 | * That means we need to generate binding names in a deterministic fashion, so we'll do that using a string hash of the binding data 9 | * A few considerations: 10 | * 1. We will include the binding type in the name to make it more readable 11 | * 2. Users can manually specify the name themselves and we will respect that 12 | * 3. The only time the hash should cause a conflict is if a single function has duplicate bindings. Not sure why someone would do that, but we will throw an error at function registration time 13 | * More info here: https://github.com/Azure/azure-functions-nodejs-worker/issues/638 14 | */ 15 | export function addBindingName( 16 | binding: T, 17 | suffix: string 18 | ): T & { name: string } { 19 | if (!binding.name) { 20 | let bindingType = binding.type; 21 | if (!bindingType.toLowerCase().endsWith(suffix.toLowerCase())) { 22 | bindingType += suffix; 23 | } 24 | binding.name = bindingType + getStringHash(JSON.stringify(binding)); 25 | } 26 | return binding; 27 | } 28 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export const version = '4.7.2'; 5 | 6 | export const returnBindingKey = '$return'; 7 | -------------------------------------------------------------------------------- /src/converters/fromRpcBindings.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { EffectiveFunctionOptions, FunctionInput, FunctionOutput, FunctionTrigger } from '@azure/functions'; 5 | import { RpcBindingInfo } from '@azure/functions-core'; 6 | import { returnBindingKey } from '../constants'; 7 | import { isTrigger } from '../utils/isTrigger'; 8 | import { nonNullProp, nonNullValue } from '../utils/nonNull'; 9 | 10 | export function fromRpcBindings(bindings: Record | null | undefined): EffectiveFunctionOptions { 11 | let trigger: FunctionTrigger | undefined; 12 | let returnBinding: FunctionOutput | undefined; 13 | const extraInputs: FunctionInput[] = []; 14 | const extraOutputs: FunctionOutput[] = []; 15 | for (const [name, binding] of Object.entries(nonNullValue(bindings, 'bindings'))) { 16 | if (isTrigger(binding.type)) { 17 | trigger = fromRpcBinding(name, binding); 18 | } else if (name === returnBindingKey) { 19 | returnBinding = fromRpcBinding(name, binding); 20 | } else if (binding.direction === 'in') { 21 | extraInputs.push(fromRpcBinding(name, binding)); 22 | } else if (binding.direction === 'out') { 23 | extraOutputs.push(fromRpcBinding(name, binding)); 24 | } 25 | } 26 | return { 27 | trigger: nonNullValue(trigger, 'trigger'), 28 | return: returnBinding, 29 | extraInputs, 30 | extraOutputs, 31 | }; 32 | } 33 | 34 | function fromRpcBinding(name: string, binding: RpcBindingInfo): FunctionTrigger | FunctionInput | FunctionOutput { 35 | return { 36 | ...binding, 37 | type: nonNullProp(binding, 'type'), 38 | name, 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/converters/fromRpcContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { Exception, RetryContext, TraceContext } from '@azure/functions'; 5 | import { RpcException, RpcRetryContext, RpcTraceContext } from '@azure/functions-core'; 6 | import { copyPropIfDefined, nonNullProp } from '../utils/nonNull'; 7 | 8 | export function fromRpcRetryContext(retryContext: RpcRetryContext | null | undefined): RetryContext | undefined { 9 | if (!retryContext) { 10 | return undefined; 11 | } else { 12 | const result: RetryContext = { 13 | retryCount: nonNullProp(retryContext, 'retryCount'), 14 | maxRetryCount: nonNullProp(retryContext, 'maxRetryCount'), 15 | }; 16 | if (retryContext.exception) { 17 | result.exception = fromRpcException(retryContext.exception); 18 | } 19 | return result; 20 | } 21 | } 22 | 23 | function fromRpcException(exception: RpcException): Exception { 24 | const result: Exception = {}; 25 | copyPropIfDefined(exception, result, 'message'); 26 | copyPropIfDefined(exception, result, 'source'); 27 | copyPropIfDefined(exception, result, 'stackTrace'); 28 | return result; 29 | } 30 | 31 | export function fromRpcTraceContext(traceContext: RpcTraceContext | null | undefined): TraceContext | undefined { 32 | if (!traceContext) { 33 | return undefined; 34 | } else { 35 | const result: TraceContext = {}; 36 | copyPropIfDefined(traceContext, result, 'traceParent'); 37 | copyPropIfDefined(traceContext, result, 'traceState'); 38 | if (traceContext.attributes) { 39 | result.attributes = traceContext.attributes; 40 | } 41 | return result; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/converters/fromRpcNullable.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { RpcNullableString } from '@azure/functions-core'; 5 | 6 | export function fromNullableMapping( 7 | nullableMapping: Record | null | undefined, 8 | originalMapping?: Record | null 9 | ): Record { 10 | let converted: Record = {}; 11 | if (nullableMapping && Object.keys(nullableMapping).length > 0) { 12 | for (const key in nullableMapping) { 13 | converted[key] = nullableMapping[key]?.value || ''; 14 | } 15 | } else if (originalMapping && Object.keys(originalMapping).length > 0) { 16 | converted = originalMapping; 17 | } 18 | return converted; 19 | } 20 | -------------------------------------------------------------------------------- /src/converters/fromRpcTriggerMetadata.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { TriggerMetadata } from '@azure/functions'; 5 | import { RpcTypedData } from '@azure/functions-core'; 6 | import { isHttpTrigger, isTimerTrigger } from '../utils/isTrigger'; 7 | import { fromRpcTypedData } from './fromRpcTypedData'; 8 | import { toCamelCaseKey, toCamelCaseValue } from './toCamelCase'; 9 | 10 | export function fromRpcTriggerMetadata( 11 | triggerMetadata: Record | null | undefined, 12 | triggerType: string 13 | ): TriggerMetadata | undefined { 14 | // For http and timer triggers, we will avoid using `triggerMetadata` for a few reasons: 15 | // 1. It uses `toCamelCase` methods, which can lead to weird casing bugs 16 | // 2. It's generally a large medley of properties that is difficult for us to document/type 17 | // 3. We can represent that information on the request & timer objects instead 18 | if (!triggerMetadata || isHttpTrigger(triggerType) || isTimerTrigger(triggerType)) { 19 | return undefined; 20 | } else { 21 | const result: TriggerMetadata = {}; 22 | for (const [key, value] of Object.entries(triggerMetadata)) { 23 | result[toCamelCaseKey(key)] = toCamelCaseValue(fromRpcTypedData(value)); 24 | } 25 | return result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/converters/fromRpcTypedData.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { RpcTypedData } from '@azure/functions-core'; 5 | import { HttpRequest } from '../http/HttpRequest'; 6 | import { isDefined } from '../utils/nonNull'; 7 | 8 | export function fromRpcTypedData(data: RpcTypedData | null | undefined): unknown { 9 | if (!data) { 10 | return undefined; 11 | } else if (isDefined(data.string)) { 12 | return tryJsonParse(data.string); 13 | } else if (isDefined(data.json)) { 14 | return JSON.parse(data.json); 15 | } else if (isDefined(data.bytes)) { 16 | return Buffer.from(data.bytes); 17 | } else if (isDefined(data.stream)) { 18 | return Buffer.from(data.stream); 19 | } else if (isDefined(data.http)) { 20 | return new HttpRequest(data.http); 21 | } else if (isDefined(data.int)) { 22 | return data.int; 23 | } else if (isDefined(data.double)) { 24 | return data.double; 25 | } else if (data.collectionBytes && isDefined(data.collectionBytes.bytes)) { 26 | return data.collectionBytes.bytes.map((d) => Buffer.from(d)); 27 | } else if (data.collectionString && isDefined(data.collectionString.string)) { 28 | return data.collectionString.string.map(tryJsonParse); 29 | } else if (data.collectionDouble && isDefined(data.collectionDouble.double)) { 30 | return data.collectionDouble.double; 31 | } else if (data.collectionSint64 && isDefined(data.collectionSint64.sint64)) { 32 | return data.collectionSint64.sint64; 33 | } else { 34 | return undefined; 35 | } 36 | } 37 | 38 | function tryJsonParse(data: string): unknown { 39 | try { 40 | return JSON.parse(data); 41 | } catch { 42 | return data; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/converters/toCamelCase.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export function toCamelCaseValue(data: unknown): unknown { 5 | if (typeof data !== 'object' || data === null) { 6 | return data; 7 | } else if (Array.isArray(data)) { 8 | return data.map(toCamelCaseValue); 9 | } else { 10 | const result: Record = {}; 11 | for (const [key, value] of Object.entries(data)) { 12 | result[toCamelCaseKey(key)] = toCamelCaseValue(value); 13 | } 14 | return result; 15 | } 16 | } 17 | 18 | export function toCamelCaseKey(key: string): string { 19 | return key.charAt(0).toLowerCase() + key.slice(1); 20 | } 21 | -------------------------------------------------------------------------------- /src/converters/toCoreFunctionMetadata.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { ExponentialBackoffRetryOptions, FixedDelayRetryOptions, GenericFunctionOptions } from '@azure/functions'; 5 | import * as coreTypes from '@azure/functions-core'; 6 | import { returnBindingKey } from '../constants'; 7 | import { AzFuncSystemError } from '../errors'; 8 | import { isTrigger } from '../utils/isTrigger'; 9 | import { toRpcDuration } from './toRpcDuration'; 10 | 11 | export function toCoreFunctionMetadata(name: string, options: GenericFunctionOptions): coreTypes.FunctionMetadata { 12 | const bindings: Record = {}; 13 | const bindingNames: string[] = []; 14 | 15 | const trigger = options.trigger; 16 | bindings[trigger.name] = { 17 | ...trigger, 18 | direction: 'in', 19 | type: isTrigger(trigger.type) ? trigger.type : trigger.type + 'Trigger', 20 | }; 21 | bindingNames.push(trigger.name); 22 | 23 | if (options.extraInputs) { 24 | for (const input of options.extraInputs) { 25 | bindings[input.name] = { 26 | ...input, 27 | direction: 'in', 28 | }; 29 | bindingNames.push(input.name); 30 | } 31 | } 32 | 33 | if (options.return) { 34 | bindings[returnBindingKey] = { 35 | ...options.return, 36 | direction: 'out', 37 | }; 38 | bindingNames.push(returnBindingKey); 39 | } 40 | 41 | if (options.extraOutputs) { 42 | for (const output of options.extraOutputs) { 43 | bindings[output.name] = { 44 | ...output, 45 | direction: 'out', 46 | }; 47 | bindingNames.push(output.name); 48 | } 49 | } 50 | 51 | const dupeBindings = bindingNames.filter((v, i) => bindingNames.indexOf(v) !== i); 52 | if (dupeBindings.length > 0) { 53 | throw new AzFuncSystemError( 54 | `Duplicate bindings found for function "${name}". Remove a duplicate binding or manually specify the "name" property to make it unique.` 55 | ); 56 | } 57 | 58 | let retryOptions: coreTypes.RpcRetryOptions | undefined; 59 | if (options.retry) { 60 | retryOptions = { 61 | ...options.retry, 62 | retryStrategy: options.retry.strategy, 63 | delayInterval: toRpcDuration((options.retry).delayInterval, 'retry.delayInterval'), 64 | maximumInterval: toRpcDuration( 65 | (options.retry).maximumInterval, 66 | 'retry.maximumInterval' 67 | ), 68 | minimumInterval: toRpcDuration( 69 | (options.retry).minimumInterval, 70 | 'retry.minimumInterval' 71 | ), 72 | }; 73 | } 74 | 75 | return { name, bindings, retryOptions }; 76 | } 77 | -------------------------------------------------------------------------------- /src/converters/toRpcDuration.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { RpcDuration } from '@azure/functions-core'; 5 | import { Duration } from '../../types'; 6 | import { AzFuncSystemError } from '../errors'; 7 | import { isDefined } from '../utils/nonNull'; 8 | 9 | export function toRpcDuration(dateTime: Duration | number | undefined, propertyName: string): RpcDuration | undefined { 10 | if (isDefined(dateTime)) { 11 | try { 12 | let timeInMilliseconds: number | undefined; 13 | if (typeof dateTime === 'object') { 14 | const minutes = (dateTime.minutes || 0) + (dateTime.hours || 0) * 60; 15 | const seconds = (dateTime.seconds || 0) + minutes * 60; 16 | timeInMilliseconds = (dateTime.milliseconds || 0) + seconds * 1000; 17 | } else if (typeof dateTime === 'number') { 18 | timeInMilliseconds = dateTime; 19 | } 20 | 21 | if (isDefined(timeInMilliseconds) && timeInMilliseconds >= 0) { 22 | return { 23 | seconds: Math.round(timeInMilliseconds / 1000), 24 | }; 25 | } 26 | } catch { 27 | // fall through 28 | } 29 | 30 | throw new AzFuncSystemError( 31 | `A 'number' or 'Duration' object was expected instead of a '${typeof dateTime}'. Cannot parse value of '${propertyName}'.` 32 | ); 33 | } 34 | 35 | return undefined; 36 | } 37 | -------------------------------------------------------------------------------- /src/converters/toRpcHttp.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { RpcHttpData, RpcTypedData } from '@azure/functions-core'; 5 | import { AzFuncSystemError } from '../errors'; 6 | import { sendProxyResponse } from '../http/httpProxy'; 7 | import { HttpResponse } from '../http/HttpResponse'; 8 | import { enableHttpStream } from '../setup'; 9 | import { toRpcHttpCookie } from './toRpcHttpCookie'; 10 | import { toRpcTypedData } from './toRpcTypedData'; 11 | 12 | export async function toRpcHttp(invocationId: string, data: unknown): Promise { 13 | if (data === null || data === undefined) { 14 | return data; 15 | } else if (typeof data !== 'object') { 16 | throw new AzFuncSystemError( 17 | 'The HTTP response must be an object with optional properties "body", "status", "headers", and "cookies".' 18 | ); 19 | } 20 | 21 | const response = data instanceof HttpResponse ? data : new HttpResponse(data); 22 | if (enableHttpStream) { 23 | // send http data over http proxy instead of rpc 24 | await sendProxyResponse(invocationId, response); 25 | return; 26 | } 27 | 28 | const rpcResponse: RpcHttpData = {}; 29 | rpcResponse.statusCode = response.status.toString(); 30 | 31 | rpcResponse.headers = {}; 32 | for (const [key, value] of response.headers.entries()) { 33 | rpcResponse.headers[key] = value; 34 | } 35 | 36 | rpcResponse.cookies = []; 37 | for (const cookie of response.cookies) { 38 | rpcResponse.cookies.push(toRpcHttpCookie(cookie)); 39 | } 40 | 41 | rpcResponse.enableContentNegotiation = response.enableContentNegotiation; 42 | 43 | const bodyBytes = await response.arrayBuffer(); 44 | rpcResponse.body = toRpcTypedData(bodyBytes); 45 | 46 | return { http: rpcResponse }; 47 | } 48 | -------------------------------------------------------------------------------- /src/converters/toRpcHttpCookie.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { Cookie } from '@azure/functions'; 5 | import { RpcHttpCookie, RpcHttpCookieSameSite } from '@azure/functions-core'; 6 | import { toNullableBool, toNullableDouble, toNullableString, toNullableTimestamp, toRpcString } from './toRpcNullable'; 7 | 8 | /** 9 | * From RFC specifications for 'Set-Cookie' response header: https://www.rfc-editor.org/rfc/rfc6265.txt 10 | * @param inputCookie 11 | */ 12 | export function toRpcHttpCookie(inputCookie: Cookie): RpcHttpCookie { 13 | // Resolve RpcHttpCookie.SameSite enum, a one-off 14 | let rpcSameSite: RpcHttpCookieSameSite = 'none'; 15 | if (inputCookie && inputCookie.sameSite) { 16 | const sameSite = inputCookie.sameSite.toLocaleLowerCase(); 17 | if (sameSite === 'lax') { 18 | rpcSameSite = 'lax'; 19 | } else if (sameSite === 'strict') { 20 | rpcSameSite = 'strict'; 21 | } else if (sameSite === 'none') { 22 | rpcSameSite = 'explicitNone'; 23 | } 24 | } 25 | 26 | const rpcCookie: RpcHttpCookie = { 27 | name: inputCookie && toRpcString(inputCookie.name, 'cookie.name'), 28 | value: inputCookie && toRpcString(inputCookie.value, 'cookie.value'), 29 | domain: toNullableString(inputCookie && inputCookie.domain, 'cookie.domain'), 30 | path: toNullableString(inputCookie && inputCookie.path, 'cookie.path'), 31 | expires: toNullableTimestamp(inputCookie && inputCookie.expires, 'cookie.expires'), 32 | secure: toNullableBool(inputCookie && inputCookie.secure, 'cookie.secure'), 33 | httpOnly: toNullableBool(inputCookie && inputCookie.httpOnly, 'cookie.httpOnly'), 34 | sameSite: rpcSameSite, 35 | maxAge: toNullableDouble(inputCookie && inputCookie.maxAge, 'cookie.maxAge'), 36 | }; 37 | 38 | return rpcCookie; 39 | } 40 | -------------------------------------------------------------------------------- /src/converters/toRpcNullable.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { RpcNullableBool, RpcNullableDouble, RpcNullableString, RpcNullableTimestamp } from '@azure/functions-core'; 5 | import { AzFuncSystemError } from '../errors'; 6 | import { isDefined } from '../utils/nonNull'; 7 | 8 | /** 9 | * Converts boolean input to an 'INullableBool' to be sent through the RPC layer. 10 | * Input that is not a boolean but is also not null or undefined logs a function app level warning. 11 | * @param nullable Input to be converted to an INullableBool if it is a valid boolean 12 | * @param propertyName The name of the property that the caller will assign the output to. Used for debugging. 13 | */ 14 | export function toNullableBool(nullable: boolean | undefined, propertyName: string): undefined | RpcNullableBool { 15 | if (typeof nullable === 'boolean') { 16 | return { 17 | value: nullable, 18 | }; 19 | } 20 | 21 | if (isDefined(nullable)) { 22 | throw new AzFuncSystemError( 23 | `A 'boolean' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.` 24 | ); 25 | } 26 | 27 | return undefined; 28 | } 29 | 30 | /** 31 | * Converts number or string that parses to a number to an 'INullableDouble' to be sent through the RPC layer. 32 | * Input that is not a valid number but is also not null or undefined logs a function app level warning. 33 | * @param nullable Input to be converted to an INullableDouble if it is a valid number 34 | * @param propertyName The name of the property that the caller will assign the output to. Used for debugging. 35 | */ 36 | export function toNullableDouble( 37 | nullable: number | string | undefined, 38 | propertyName: string 39 | ): undefined | RpcNullableDouble { 40 | if (typeof nullable === 'number') { 41 | return { 42 | value: nullable, 43 | }; 44 | } else if (typeof nullable === 'string') { 45 | if (!isNaN(Number(nullable))) { 46 | const parsedNumber = parseFloat(nullable); 47 | return { 48 | value: parsedNumber, 49 | }; 50 | } 51 | } 52 | 53 | if (isDefined(nullable)) { 54 | throw new AzFuncSystemError( 55 | `A 'number' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.` 56 | ); 57 | } 58 | 59 | return undefined; 60 | } 61 | 62 | /** 63 | * Converts string input to an 'INullableString' to be sent through the RPC layer. 64 | * Input that is not a string but is also not null or undefined logs a function app level warning. 65 | * @param nullable Input to be converted to an INullableString if it is a valid string 66 | * @param propertyName The name of the property that the caller will assign the output to. Used for debugging. 67 | */ 68 | export function toRpcString(nullable: string | undefined, propertyName: string): string { 69 | if (typeof nullable === 'string') { 70 | return nullable; 71 | } 72 | 73 | if (isDefined(nullable)) { 74 | throw new AzFuncSystemError( 75 | `A 'string' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.` 76 | ); 77 | } 78 | 79 | return ''; 80 | } 81 | 82 | /** 83 | * Converts string input to an 'INullableString' to be sent through the RPC layer. 84 | * Input that is not a string but is also not null or undefined logs a function app level warning. 85 | * @param nullable Input to be converted to an INullableString if it is a valid string 86 | * @param propertyName The name of the property that the caller will assign the output to. Used for debugging. 87 | */ 88 | export function toNullableString(nullable: string | undefined, propertyName: string): undefined | RpcNullableString { 89 | if (typeof nullable === 'string') { 90 | return { 91 | value: nullable, 92 | }; 93 | } 94 | 95 | if (isDefined(nullable)) { 96 | throw new AzFuncSystemError( 97 | `A 'string' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.` 98 | ); 99 | } 100 | 101 | return undefined; 102 | } 103 | 104 | /** 105 | * Converts Date or number input to an 'INullableTimestamp' to be sent through the RPC layer. 106 | * Input that is not a Date or number but is also not null or undefined logs a function app level warning. 107 | * @param nullable Input to be converted to an INullableTimestamp if it is valid input 108 | * @param propertyName The name of the property that the caller will assign the output to. Used for debugging. 109 | */ 110 | export function toNullableTimestamp( 111 | dateTime: Date | number | undefined, 112 | propertyName: string 113 | ): RpcNullableTimestamp | undefined { 114 | if (isDefined(dateTime)) { 115 | try { 116 | const timeInMilliseconds = typeof dateTime === 'number' ? dateTime : dateTime.getTime(); 117 | 118 | if (timeInMilliseconds && timeInMilliseconds >= 0) { 119 | return { 120 | value: { 121 | seconds: Math.round(timeInMilliseconds / 1000), 122 | }, 123 | }; 124 | } 125 | } catch { 126 | throw new AzFuncSystemError( 127 | `A 'number' or 'Date' input was expected instead of a '${typeof dateTime}'. Cannot parse value of '${propertyName}'.` 128 | ); 129 | } 130 | } 131 | return undefined; 132 | } 133 | -------------------------------------------------------------------------------- /src/converters/toRpcTypedData.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { RpcTypedData } from '@azure/functions-core'; 5 | 6 | export function toRpcTypedData(data: unknown): RpcTypedData | null | undefined { 7 | if (data === null || data === undefined) { 8 | return data; 9 | } else if (typeof data === 'string') { 10 | return { string: data }; 11 | } else if (Buffer.isBuffer(data)) { 12 | return { bytes: data }; 13 | } else if (ArrayBuffer.isView(data)) { 14 | const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); 15 | return { bytes: bytes }; 16 | } else if (data instanceof ArrayBuffer) { 17 | const bytes = new Uint8Array(data); 18 | return { bytes: bytes }; 19 | } else if (typeof data === 'number') { 20 | if (Number.isInteger(data)) { 21 | return { int: data }; 22 | } else { 23 | return { double: data }; 24 | } 25 | } else { 26 | return { json: JSON.stringify(data) }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export interface AzFuncError { 5 | /** 6 | * System errors can be tracked in our telemetry 7 | * User errors cannot be tracked in our telemetry because they could have user information (users can still track it themselves in their app insights resource) 8 | */ 9 | isAzureFunctionsSystemError: boolean; 10 | } 11 | 12 | export interface ValidatedError extends Error, Partial { 13 | /** 14 | * Use `trySetErrorMessage` to set the error message 15 | */ 16 | readonly message: string; 17 | } 18 | 19 | export class AzFuncSystemError extends Error { 20 | isAzureFunctionsSystemError = true; 21 | } 22 | 23 | export class AzFuncTypeError extends TypeError implements AzFuncError { 24 | isAzureFunctionsSystemError = true; 25 | } 26 | 27 | export class AzFuncRangeError extends RangeError implements AzFuncError { 28 | isAzureFunctionsSystemError = true; 29 | } 30 | 31 | export class ReadOnlyError extends AzFuncTypeError { 32 | constructor(propertyName: string) { 33 | super(`Cannot assign to read only property '${propertyName}'`); 34 | } 35 | } 36 | 37 | export function ensureErrorType(err: unknown): ValidatedError { 38 | if (err instanceof Error) { 39 | return err; 40 | } else { 41 | let message: string; 42 | if (err === undefined || err === null) { 43 | message = 'Unknown error'; 44 | } else if (typeof err === 'string') { 45 | message = err; 46 | } else if (typeof err === 'object') { 47 | message = JSON.stringify(err); 48 | } else { 49 | message = String(err); 50 | } 51 | return new Error(message); 52 | } 53 | } 54 | 55 | export function trySetErrorMessage(err: Error, message: string): void { 56 | try { 57 | err.message = message; 58 | } catch { 59 | // If we can't set the message, we'll keep the error as is 60 | } 61 | } 62 | 63 | /** 64 | * This is mostly for callbacks where `null` or `undefined` indicates there is no error 65 | * By contrast, anything thrown/caught is assumed to be an error regardless of what it is 66 | */ 67 | export function isError(err: unknown): boolean { 68 | return err !== null && err !== undefined; 69 | } 70 | -------------------------------------------------------------------------------- /src/hooks/AppStartContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { HookContext } from './HookContext'; 6 | 7 | export class AppStartContext extends HookContext implements types.AppStartContext {} 8 | -------------------------------------------------------------------------------- /src/hooks/AppTerminateContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { HookContext } from './HookContext'; 6 | 7 | export class AppTerminateContext extends HookContext implements types.AppTerminateContext {} 8 | -------------------------------------------------------------------------------- /src/hooks/HookContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { ReadOnlyError } from '../errors'; 6 | import { nonNullProp } from '../utils/nonNull'; 7 | 8 | export class HookContext implements types.HookContext { 9 | #init: types.HookContextInit; 10 | 11 | constructor(init?: types.HookContextInit) { 12 | this.#init = init ?? {}; 13 | this.#init.hookData ??= {}; 14 | } 15 | 16 | get hookData(): Record { 17 | return nonNullProp(this.#init, 'hookData'); 18 | } 19 | 20 | set hookData(_value: unknown) { 21 | throw new ReadOnlyError('hookData'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/hooks/InvocationHookContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { ReadOnlyError } from '../errors'; 6 | import { InvocationContext } from '../InvocationContext'; 7 | import { nonNullProp } from '../utils/nonNull'; 8 | import { HookContext } from './HookContext'; 9 | 10 | export class InvocationHookContext extends HookContext implements types.InvocationHookContext { 11 | #init: types.InvocationHookContextInit; 12 | 13 | constructor(init?: types.InvocationHookContextInit) { 14 | super(init); 15 | this.#init = init ?? {}; 16 | this.#init.inputs ??= []; 17 | this.#init.invocationContext ??= new InvocationContext(); 18 | } 19 | 20 | get invocationContext(): types.InvocationContext { 21 | return nonNullProp(this.#init, 'invocationContext'); 22 | } 23 | 24 | set invocationContext(_value: types.InvocationContext) { 25 | throw new ReadOnlyError('invocationContext'); 26 | } 27 | 28 | get inputs(): unknown[] { 29 | return nonNullProp(this.#init, 'inputs'); 30 | } 31 | 32 | set inputs(value: unknown[]) { 33 | this.#init.inputs = value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/LogHookContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { ReadOnlyError } from '../errors'; 6 | import { nonNullProp } from '../utils/nonNull'; 7 | import { HookContext } from './HookContext'; 8 | 9 | export class LogHookContext extends HookContext implements types.LogHookContext { 10 | #init: types.LogHookContextInit; 11 | 12 | constructor(init?: types.LogHookContextInit) { 13 | super(init); 14 | this.#init = init ?? {}; 15 | this.#init.level ??= 'information'; 16 | this.#init.message ??= 'unknown'; 17 | this.#init.category ??= 'user'; 18 | } 19 | 20 | get level(): types.LogLevel { 21 | return nonNullProp(this.#init, 'level'); 22 | } 23 | 24 | set level(value: types.LogLevel) { 25 | this.#init.level = value; 26 | } 27 | 28 | get message(): string { 29 | return nonNullProp(this.#init, 'message'); 30 | } 31 | 32 | set message(value: string) { 33 | this.#init.message = value; 34 | } 35 | 36 | get category(): types.LogCategory { 37 | return nonNullProp(this.#init, 'category'); 38 | } 39 | 40 | set category(_value: types.LogCategory) { 41 | throw new ReadOnlyError('category'); 42 | } 43 | 44 | get invocationContext(): types.InvocationContext | undefined { 45 | return this.#init.invocationContext; 46 | } 47 | 48 | set invocationContext(_value: types.InvocationContext | undefined) { 49 | throw new ReadOnlyError('invocationContext'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/hooks/PostInvocationContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { InvocationHookContext } from './InvocationHookContext'; 6 | 7 | export class PostInvocationContext extends InvocationHookContext implements types.PostInvocationContext { 8 | #init: types.PostInvocationContextInit; 9 | 10 | constructor(init?: types.PostInvocationContextInit) { 11 | super(init); 12 | this.#init = init ?? {}; 13 | } 14 | 15 | get result(): unknown { 16 | return this.#init.result; 17 | } 18 | 19 | set result(value: unknown) { 20 | this.#init.result = value; 21 | } 22 | 23 | get error(): unknown { 24 | return this.#init.error; 25 | } 26 | 27 | set error(value: unknown) { 28 | this.#init.error = value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/hooks/PreInvocationContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { nonNullProp } from '../utils/nonNull'; 6 | import { InvocationHookContext } from './InvocationHookContext'; 7 | 8 | export class PreInvocationContext extends InvocationHookContext implements types.PreInvocationContext { 9 | #init: types.PreInvocationContextInit; 10 | 11 | constructor(init?: types.PreInvocationContextInit) { 12 | super(init); 13 | this.#init = init ?? {}; 14 | this.#init.functionCallback ??= () => {}; 15 | } 16 | 17 | get functionHandler(): types.FunctionHandler { 18 | return nonNullProp(this.#init, 'functionCallback'); 19 | } 20 | 21 | set functionHandler(value: types.FunctionHandler) { 22 | this.#init.functionCallback = value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/hooks/registerHook.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { 5 | AppStartHandler, 6 | AppTerminateHandler, 7 | LogHookHandler, 8 | PostInvocationHandler, 9 | PreInvocationHandler, 10 | } from '@azure/functions'; 11 | import * as coreTypes from '@azure/functions-core'; 12 | import { AzFuncSystemError, ensureErrorType } from '../errors'; 13 | import { Disposable } from '../utils/Disposable'; 14 | import { tryGetCoreApiLazy } from '../utils/tryGetCoreApiLazy'; 15 | import { AppStartContext } from './AppStartContext'; 16 | import { AppTerminateContext } from './AppTerminateContext'; 17 | import { LogHookContext } from './LogHookContext'; 18 | import { PostInvocationContext } from './PostInvocationContext'; 19 | import { PreInvocationContext } from './PreInvocationContext'; 20 | 21 | function registerHook(hookName: string, callback: coreTypes.HookCallback): coreTypes.Disposable { 22 | const coreApi = tryGetCoreApiLazy(); 23 | if (!coreApi) { 24 | console.warn( 25 | `WARNING: Skipping call to register ${hookName} hook because the "@azure/functions" package is in test mode.` 26 | ); 27 | return new Disposable(() => { 28 | console.warn( 29 | `WARNING: Skipping call to dispose ${hookName} hook because the "@azure/functions" package is in test mode.` 30 | ); 31 | }); 32 | } else { 33 | return coreApi.registerHook(hookName, callback); 34 | } 35 | } 36 | 37 | export function appStart(handler: AppStartHandler): Disposable { 38 | return registerHook('appStart', (coreContext) => { 39 | return handler(new AppStartContext(coreContext)); 40 | }); 41 | } 42 | 43 | export function appTerminate(handler: AppTerminateHandler): Disposable { 44 | return registerHook('appTerminate', (coreContext) => { 45 | return handler(new AppTerminateContext(coreContext)); 46 | }); 47 | } 48 | 49 | export function preInvocation(handler: PreInvocationHandler): Disposable { 50 | return registerHook('preInvocation', (coreContext) => { 51 | return handler(new PreInvocationContext(coreContext)); 52 | }); 53 | } 54 | 55 | export function postInvocation(handler: PostInvocationHandler): Disposable { 56 | return registerHook('postInvocation', (coreContext) => { 57 | return handler(new PostInvocationContext(coreContext)); 58 | }); 59 | } 60 | 61 | export function log(handler: LogHookHandler): Disposable { 62 | try { 63 | return registerHook('log', (coreContext) => { 64 | return handler(new LogHookContext(coreContext)); 65 | }); 66 | } catch (err) { 67 | const error = ensureErrorType(err); 68 | if (error.name === 'RangeError' && error.isAzureFunctionsSystemError) { 69 | throw new AzFuncSystemError(`Log hooks require Azure Functions Host v4.34 or higher.`); 70 | } else { 71 | throw err; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/http/HttpResponse.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { HttpResponseInit } from '@azure/functions'; 6 | import { Blob } from 'buffer'; 7 | import { ReadableStream } from 'stream/web'; 8 | import { FormData, Headers, Response as uResponse, ResponseInit as uResponseInit } from 'undici'; 9 | import { isDefined } from '../utils/nonNull'; 10 | 11 | interface InternalHttpResponseInit extends HttpResponseInit { 12 | undiciResponse?: uResponse; 13 | } 14 | 15 | export class HttpResponse implements types.HttpResponse { 16 | readonly cookies: types.Cookie[]; 17 | readonly enableContentNegotiation: boolean; 18 | 19 | #uRes: uResponse; 20 | #init: InternalHttpResponseInit; 21 | 22 | constructor(init?: InternalHttpResponseInit) { 23 | init ??= {}; 24 | this.#init = init; 25 | 26 | if (init.undiciResponse) { 27 | this.#uRes = init.undiciResponse; 28 | } else { 29 | const uResInit: uResponseInit = { status: init.status, headers: init.headers }; 30 | if (isDefined(init.jsonBody)) { 31 | this.#uRes = uResponse.json(init.jsonBody, uResInit); 32 | } else { 33 | this.#uRes = new uResponse(init.body, uResInit); 34 | } 35 | } 36 | 37 | this.cookies = init.cookies ?? []; 38 | this.enableContentNegotiation = !!init.enableContentNegotiation; 39 | } 40 | 41 | get status(): number { 42 | return this.#uRes.status; 43 | } 44 | 45 | get headers(): Headers { 46 | return this.#uRes.headers; 47 | } 48 | 49 | get body(): ReadableStream | null { 50 | return this.#uRes.body; 51 | } 52 | 53 | get bodyUsed(): boolean { 54 | return this.#uRes.bodyUsed; 55 | } 56 | 57 | async arrayBuffer(): Promise { 58 | return this.#uRes.arrayBuffer(); 59 | } 60 | 61 | async blob(): Promise { 62 | return this.#uRes.blob(); 63 | } 64 | 65 | async formData(): Promise { 66 | return this.#uRes.formData(); 67 | } 68 | 69 | async json(): Promise { 70 | return this.#uRes.json(); 71 | } 72 | 73 | async text(): Promise { 74 | return this.#uRes.text(); 75 | } 76 | 77 | clone(): HttpResponse { 78 | const newInit = structuredClone(this.#init); 79 | newInit.undiciResponse = this.#uRes.clone(); 80 | return new HttpResponse(newInit); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/http/extractHttpUserFromHeaders.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { HttpRequestUser } from '@azure/functions'; 5 | import { Headers } from 'undici'; 6 | import { nonNullValue } from '../utils/nonNull'; 7 | 8 | /* grandfathered in. Should fix when possible */ 9 | /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */ 10 | 11 | export function extractHttpUserFromHeaders(headers: Headers): HttpRequestUser | null { 12 | let user: HttpRequestUser | null = null; 13 | 14 | const clientPrincipal = headers.get('x-ms-client-principal'); 15 | if (clientPrincipal) { 16 | const claimsPrincipalData = JSON.parse(Buffer.from(clientPrincipal, 'base64').toString('utf-8')); 17 | 18 | if (claimsPrincipalData['identityProvider']) { 19 | user = { 20 | type: 'StaticWebApps', 21 | id: claimsPrincipalData['userId'], 22 | username: claimsPrincipalData['userDetails'], 23 | identityProvider: claimsPrincipalData['identityProvider'], 24 | claimsPrincipalData, 25 | }; 26 | } else { 27 | user = { 28 | type: 'AppService', 29 | id: nonNullValue(headers.get('x-ms-client-principal-id'), 'user-id'), 30 | username: nonNullValue(headers.get('x-ms-client-principal-name'), 'user-name'), 31 | identityProvider: nonNullValue(headers.get('x-ms-client-principal-idp'), 'user-idp'), 32 | claimsPrincipalData, 33 | }; 34 | } 35 | } 36 | 37 | return user; 38 | } 39 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export * as app from './app'; 5 | export { AppStartContext } from './hooks/AppStartContext'; 6 | export { AppTerminateContext } from './hooks/AppTerminateContext'; 7 | export { HookContext } from './hooks/HookContext'; 8 | export { InvocationHookContext } from './hooks/InvocationHookContext'; 9 | export { LogHookContext } from './hooks/LogHookContext'; 10 | export { PostInvocationContext } from './hooks/PostInvocationContext'; 11 | export { PreInvocationContext } from './hooks/PreInvocationContext'; 12 | export { HttpRequest } from './http/HttpRequest'; 13 | export { HttpResponse } from './http/HttpResponse'; 14 | export * as input from './input'; 15 | export { InvocationContext } from './InvocationContext'; 16 | export * as output from './output'; 17 | export * as trigger from './trigger'; 18 | export { Disposable } from './utils/Disposable'; 19 | 20 | export enum SqlChangeOperation { 21 | Insert = 0, 22 | Update = 1, 23 | Delete = 2, 24 | } 25 | 26 | export enum MySqlChangeOperation { 27 | Update = 0, 28 | } 29 | -------------------------------------------------------------------------------- /src/input.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { 5 | CosmosDBInput, 6 | CosmosDBInputOptions, 7 | FunctionInput, 8 | GenericInputOptions, 9 | MySqlInput, 10 | MySqlInputOptions, 11 | SqlInput, 12 | SqlInputOptions, 13 | StorageBlobInput, 14 | StorageBlobInputOptions, 15 | TableInput, 16 | TableInputOptions, 17 | WebPubSubConnectionInput, 18 | WebPubSubConnectionInputOptions, 19 | WebPubSubContextInput, 20 | WebPubSubContextInputOptions, 21 | } from '@azure/functions'; 22 | import { addBindingName } from './addBindingName'; 23 | 24 | export function storageBlob(options: StorageBlobInputOptions): StorageBlobInput { 25 | return addInputBindingName({ 26 | ...options, 27 | type: 'blob', 28 | }); 29 | } 30 | 31 | export function table(options: TableInputOptions): TableInput { 32 | return addInputBindingName({ 33 | ...options, 34 | type: 'table', 35 | }); 36 | } 37 | 38 | export function cosmosDB(options: CosmosDBInputOptions): CosmosDBInput { 39 | return addInputBindingName({ 40 | ...options, 41 | type: 'cosmosDB', 42 | }); 43 | } 44 | 45 | export function sql(options: SqlInputOptions): SqlInput { 46 | return addInputBindingName({ 47 | ...options, 48 | type: 'sql', 49 | }); 50 | } 51 | 52 | export function mySql(options: MySqlInputOptions): MySqlInput { 53 | return addInputBindingName({ 54 | ...options, 55 | type: 'mysql', 56 | }); 57 | } 58 | 59 | export function webPubSubConnection(options: WebPubSubConnectionInputOptions): WebPubSubConnectionInput { 60 | return addInputBindingName({ 61 | ...options, 62 | type: 'webPubSubConnection', 63 | }); 64 | } 65 | 66 | export function webPubSubContext(options: WebPubSubContextInputOptions): WebPubSubContextInput { 67 | return addInputBindingName({ 68 | ...options, 69 | type: 'webPubSubContext', 70 | }); 71 | } 72 | 73 | export function generic(options: GenericInputOptions): FunctionInput { 74 | return addInputBindingName(options); 75 | } 76 | 77 | function addInputBindingName(binding: T): T & { name: string } { 78 | return addBindingName(binding, 'Input'); 79 | } 80 | -------------------------------------------------------------------------------- /src/output.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { 5 | CosmosDBOutput, 6 | CosmosDBOutputOptions, 7 | EventGridOutput, 8 | EventGridOutputOptions, 9 | EventHubOutput, 10 | EventHubOutputOptions, 11 | FunctionOutput, 12 | GenericOutputOptions, 13 | HttpOutput, 14 | HttpOutputOptions, 15 | MySqlOutput, 16 | MySqlOutputOptions, 17 | ServiceBusQueueOutput, 18 | ServiceBusQueueOutputOptions, 19 | ServiceBusTopicOutput, 20 | ServiceBusTopicOutputOptions, 21 | SqlOutput, 22 | SqlOutputOptions, 23 | StorageBlobOutput, 24 | StorageBlobOutputOptions, 25 | StorageQueueOutput, 26 | StorageQueueOutputOptions, 27 | TableOutput, 28 | TableOutputOptions, 29 | WebPubSubOutput, 30 | WebPubSubOutputOptions, 31 | } from '@azure/functions'; 32 | import { addBindingName } from './addBindingName'; 33 | 34 | export function http(options: HttpOutputOptions): HttpOutput { 35 | return addOutputBindingName({ 36 | ...options, 37 | type: 'http', 38 | }); 39 | } 40 | 41 | export function storageBlob(options: StorageBlobOutputOptions): StorageBlobOutput { 42 | return addOutputBindingName({ 43 | ...options, 44 | type: 'blob', 45 | }); 46 | } 47 | 48 | export function table(options: TableOutputOptions): TableOutput { 49 | return addOutputBindingName({ 50 | ...options, 51 | type: 'table', 52 | }); 53 | } 54 | 55 | export function storageQueue(options: StorageQueueOutputOptions): StorageQueueOutput { 56 | return addOutputBindingName({ 57 | ...options, 58 | type: 'queue', 59 | }); 60 | } 61 | 62 | export function serviceBusQueue(options: ServiceBusQueueOutputOptions): ServiceBusQueueOutput { 63 | return addOutputBindingName({ 64 | ...options, 65 | type: 'serviceBus', 66 | }); 67 | } 68 | 69 | export function serviceBusTopic(options: ServiceBusTopicOutputOptions): ServiceBusTopicOutput { 70 | return addOutputBindingName({ 71 | ...options, 72 | type: 'serviceBus', 73 | }); 74 | } 75 | 76 | export function eventHub(options: EventHubOutputOptions): EventHubOutput { 77 | return addOutputBindingName({ 78 | ...options, 79 | type: 'eventHub', 80 | }); 81 | } 82 | 83 | export function eventGrid(options: EventGridOutputOptions): EventGridOutput { 84 | return addOutputBindingName({ 85 | ...options, 86 | type: 'eventGrid', 87 | }); 88 | } 89 | 90 | export function cosmosDB(options: CosmosDBOutputOptions): CosmosDBOutput { 91 | return addOutputBindingName({ 92 | ...options, 93 | type: 'cosmosDB', 94 | }); 95 | } 96 | 97 | export function sql(options: SqlOutputOptions): SqlOutput { 98 | return addOutputBindingName({ 99 | ...options, 100 | type: 'sql', 101 | }); 102 | } 103 | 104 | export function mySql(options: MySqlOutputOptions): MySqlOutput { 105 | return addOutputBindingName({ 106 | ...options, 107 | type: 'mysql', 108 | }); 109 | } 110 | 111 | export function webPubSub(options: WebPubSubOutputOptions): WebPubSubOutput { 112 | return addOutputBindingName({ 113 | ...options, 114 | type: 'webPubSub', 115 | }); 116 | } 117 | 118 | export function generic(options: GenericOutputOptions): FunctionOutput { 119 | return addOutputBindingName(options); 120 | } 121 | 122 | function addOutputBindingName(binding: T): T & { name: string } { 123 | return addBindingName(binding, 'Output'); 124 | } 125 | -------------------------------------------------------------------------------- /src/setup.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { SetupOptions } from '../types'; 5 | import { AzFuncSystemError } from './errors'; 6 | import { isDefined } from './utils/nonNull'; 7 | import { tryGetCoreApiLazy } from './utils/tryGetCoreApiLazy'; 8 | import { workerSystemLog } from './utils/workerSystemLog'; 9 | 10 | let setupLocked = false; 11 | export function lockSetup(): void { 12 | setupLocked = true; 13 | } 14 | 15 | export let enableHttpStream = false; 16 | export const capabilities: Record = {}; 17 | 18 | export function setup(opts: SetupOptions): void { 19 | if (setupLocked) { 20 | throw new AzFuncSystemError("Setup options can't be changed after app startup has finished."); 21 | } 22 | 23 | if (opts.enableHttpStream) { 24 | // NOTE: coreApi.log was coincidentally added the same time as http streaming, 25 | // so we can use that to validate the host version instead of messing with semver parsing 26 | const coreApi = tryGetCoreApiLazy(); 27 | if (coreApi && !coreApi.log) { 28 | throw new AzFuncSystemError(`HTTP streaming requires Azure Functions Host v4.28 or higher.`); 29 | } 30 | } 31 | 32 | if (isDefined(opts.enableHttpStream)) { 33 | enableHttpStream = opts.enableHttpStream; 34 | } 35 | 36 | if (opts.capabilities) { 37 | for (let [key, val] of Object.entries(opts.capabilities)) { 38 | if (isDefined(val)) { 39 | val = String(val); 40 | workerSystemLog('debug', `Capability ${key} set to ${val}.`); 41 | capabilities[key] = val; 42 | } 43 | } 44 | } 45 | 46 | if (enableHttpStream) { 47 | workerSystemLog('debug', `HTTP streaming enabled.`); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/trigger.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { 5 | CosmosDBTrigger, 6 | CosmosDBTriggerOptions, 7 | EventGridTrigger, 8 | EventGridTriggerOptions, 9 | EventHubTrigger, 10 | EventHubTriggerOptions, 11 | FunctionTrigger, 12 | GenericTriggerOptions, 13 | HttpTrigger, 14 | HttpTriggerOptions, 15 | MySqlTrigger, 16 | MySqlTriggerOptions, 17 | ServiceBusQueueTrigger, 18 | ServiceBusQueueTriggerOptions, 19 | ServiceBusTopicTrigger, 20 | ServiceBusTopicTriggerOptions, 21 | SqlTrigger, 22 | SqlTriggerOptions, 23 | StorageBlobTrigger, 24 | StorageBlobTriggerOptions, 25 | StorageQueueTrigger, 26 | StorageQueueTriggerOptions, 27 | TimerTrigger, 28 | TimerTriggerOptions, 29 | WarmupTrigger, 30 | WarmupTriggerOptions, 31 | WebPubSubTrigger, 32 | WebPubSubTriggerOptions, 33 | } from '@azure/functions'; 34 | import { addBindingName } from './addBindingName'; 35 | 36 | export function http(options: HttpTriggerOptions): HttpTrigger { 37 | return addTriggerBindingName({ 38 | ...options, 39 | authLevel: options.authLevel || 'anonymous', 40 | methods: options.methods || ['GET', 'POST'], 41 | type: 'httpTrigger', 42 | }); 43 | } 44 | 45 | export function timer(options: TimerTriggerOptions): TimerTrigger { 46 | return addTriggerBindingName({ 47 | ...options, 48 | type: 'timerTrigger', 49 | }); 50 | } 51 | 52 | export function storageBlob(options: StorageBlobTriggerOptions): StorageBlobTrigger { 53 | return addTriggerBindingName({ 54 | ...options, 55 | type: 'blobTrigger', 56 | }); 57 | } 58 | 59 | export function storageQueue(options: StorageQueueTriggerOptions): StorageQueueTrigger { 60 | return addTriggerBindingName({ 61 | ...options, 62 | type: 'queueTrigger', 63 | }); 64 | } 65 | 66 | export function serviceBusQueue(options: ServiceBusQueueTriggerOptions): ServiceBusQueueTrigger { 67 | return addTriggerBindingName({ 68 | ...options, 69 | type: 'serviceBusTrigger', 70 | }); 71 | } 72 | 73 | export function serviceBusTopic(options: ServiceBusTopicTriggerOptions): ServiceBusTopicTrigger { 74 | return addTriggerBindingName({ 75 | ...options, 76 | type: 'serviceBusTrigger', 77 | }); 78 | } 79 | 80 | export function eventHub(options: EventHubTriggerOptions): EventHubTrigger { 81 | return addTriggerBindingName({ 82 | ...options, 83 | type: 'eventHubTrigger', 84 | }); 85 | } 86 | 87 | export function eventGrid(options: EventGridTriggerOptions): EventGridTrigger { 88 | return addTriggerBindingName({ 89 | ...options, 90 | type: 'eventGridTrigger', 91 | }); 92 | } 93 | 94 | export function cosmosDB(options: CosmosDBTriggerOptions): CosmosDBTrigger { 95 | return addTriggerBindingName({ 96 | ...options, 97 | type: 'cosmosDBTrigger', 98 | }); 99 | } 100 | 101 | export function warmup(options: WarmupTriggerOptions): WarmupTrigger { 102 | return addTriggerBindingName({ 103 | ...options, 104 | type: 'warmupTrigger', 105 | }); 106 | } 107 | 108 | export function sql(options: SqlTriggerOptions): SqlTrigger { 109 | return addTriggerBindingName({ 110 | ...options, 111 | type: 'sqlTrigger', 112 | }); 113 | } 114 | 115 | export function mySql(options: MySqlTriggerOptions): MySqlTrigger { 116 | return addTriggerBindingName({ 117 | ...options, 118 | type: 'mysqlTrigger', 119 | }); 120 | } 121 | 122 | export function webPubSub(options: WebPubSubTriggerOptions): WebPubSubTrigger { 123 | return addTriggerBindingName({ 124 | ...options, 125 | type: 'webPubSubTrigger', 126 | }); 127 | } 128 | 129 | export function generic(options: GenericTriggerOptions): FunctionTrigger { 130 | return addTriggerBindingName(options); 131 | } 132 | 133 | function addTriggerBindingName(binding: T): T & { name: string } { 134 | return addBindingName(binding, 'Trigger'); 135 | } 136 | -------------------------------------------------------------------------------- /src/utils/Disposable.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | /** 5 | * Based off of VS Code 6 | * https://github.com/microsoft/vscode/blob/7bed4ce3e9f5059b5fc638c348f064edabcce5d2/src/vs/workbench/api/common/extHostTypes.ts#L65 7 | */ 8 | export class Disposable { 9 | static from(...inDisposables: { dispose(): any }[]): Disposable { 10 | let disposables: ReadonlyArray<{ dispose(): any }> | undefined = inDisposables; 11 | return new Disposable(function () { 12 | if (disposables) { 13 | for (const disposable of disposables) { 14 | if (disposable && typeof disposable.dispose === 'function') { 15 | disposable.dispose(); 16 | } 17 | } 18 | disposables = undefined; 19 | } 20 | }); 21 | } 22 | 23 | #callOnDispose?: () => any; 24 | 25 | constructor(callOnDispose: () => any) { 26 | this.#callOnDispose = callOnDispose; 27 | } 28 | 29 | dispose(): any { 30 | if (typeof this.#callOnDispose === 'function') { 31 | this.#callOnDispose(); 32 | this.#callOnDispose = undefined; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/fallbackLogHandler.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | 6 | export function fallbackLogHandler(level: types.LogLevel, ...args: unknown[]): void { 7 | switch (level) { 8 | case 'trace': 9 | console.trace(...args); 10 | break; 11 | case 'debug': 12 | console.debug(...args); 13 | break; 14 | case 'information': 15 | console.info(...args); 16 | break; 17 | case 'warning': 18 | console.warn(...args); 19 | break; 20 | case 'critical': 21 | case 'error': 22 | console.error(...args); 23 | break; 24 | default: 25 | console.log(...args); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/getRandomHexString.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as crypto from 'crypto'; 5 | 6 | export function getRandomHexString(length = 10): string { 7 | const buffer: Buffer = crypto.randomBytes(Math.ceil(length / 2)); 8 | return buffer.toString('hex').slice(0, length); 9 | } 10 | 11 | export function getStringHash(data: string, length = 10): string { 12 | return crypto.createHash('sha256').update(data).digest('hex').slice(0, length); 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/isTrigger.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export function isTrigger(typeName: string | undefined | null): boolean { 5 | return !!typeName && /trigger$/i.test(typeName); 6 | } 7 | 8 | export function isHttpTrigger(typeName: string | undefined | null): boolean { 9 | return typeName?.toLowerCase() === 'httptrigger'; 10 | } 11 | 12 | export function isTimerTrigger(typeName: string | undefined | null): boolean { 13 | return typeName?.toLowerCase() === 'timertrigger'; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/nonNull.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { AzFuncSystemError } from '../errors'; 5 | 6 | /** 7 | * Retrieves a property by name from an object and checks that it's not null and not undefined. It is strongly typed 8 | * for the property and will give a compile error if the given name is not a property of the source. 9 | */ 10 | export function nonNullProp( 11 | source: TSource, 12 | name: TKey 13 | ): NonNullable { 14 | const value: NonNullable = >source[name]; 15 | return nonNullValue(value, name); 16 | } 17 | 18 | /** 19 | * Validates that a given value is not null and not undefined. 20 | */ 21 | export function nonNullValue(value: T | undefined | null, propertyNameOrMessage?: string): T { 22 | if (value === null || value === undefined) { 23 | throw new AzFuncSystemError( 24 | 'Internal error: Expected value to be neither null nor undefined' + 25 | (propertyNameOrMessage ? `: ${propertyNameOrMessage}` : '') 26 | ); 27 | } 28 | 29 | return value; 30 | } 31 | 32 | export function copyPropIfDefined(source: TData, destination: TData, key: TKey): void { 33 | if (source[key] !== null && source[key] !== undefined) { 34 | destination[key] = source[key]; 35 | } 36 | } 37 | 38 | export function isDefined(data: T | undefined | null): data is T { 39 | return data !== null && data !== undefined; 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/tryGetCoreApiLazy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as coreTypes from '@azure/functions-core'; 5 | 6 | let coreApi: typeof coreTypes | undefined | null; 7 | export function tryGetCoreApiLazy(): typeof coreTypes | null { 8 | if (coreApi === undefined) { 9 | try { 10 | // eslint-disable-next-line @typescript-eslint/no-var-requires 11 | coreApi = require('@azure/functions-core'); 12 | } catch { 13 | coreApi = null; 14 | } 15 | } 16 | return coreApi; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export function isEnvironmentVariableSet(val: string | boolean | number | undefined | null): boolean { 5 | return !/^(false|0)?$/i.test(val === undefined || val === null ? '' : String(val)); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/workerSystemLog.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as types from '@azure/functions'; 5 | import { format } from 'util'; 6 | import { fallbackLogHandler } from './fallbackLogHandler'; 7 | import { tryGetCoreApiLazy } from './tryGetCoreApiLazy'; 8 | 9 | export function workerSystemLog(level: types.LogLevel, ...args: unknown[]): void { 10 | const coreApi = tryGetCoreApiLazy(); 11 | // NOTE: coreApi.log doesn't exist on older versions of the worker 12 | if (coreApi && coreApi.log) { 13 | coreApi.log(level, 'system', format(...args)); 14 | } else { 15 | fallbackLogHandler(level, ...args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/no-unsafe-argument": "off", 4 | "@typescript-eslint/no-unsafe-assignment": "off", 5 | "@typescript-eslint/no-unsafe-call": "off", 6 | "@typescript-eslint/no-unsafe-member-access": "off", 7 | "@typescript-eslint/no-unsafe-return": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/InvocationModel.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { RpcLogCategory, RpcLogLevel } from '@azure/functions-core'; 6 | import { expect } from 'chai'; 7 | import { InvocationContext } from '../src'; 8 | import { InvocationModel } from '../src/InvocationModel'; 9 | 10 | function testLog(_level: RpcLogLevel, _category: RpcLogCategory, message: string) { 11 | console.log(message); 12 | } 13 | 14 | describe('InvocationModel', () => { 15 | describe('getResponse', () => { 16 | it('Hello world http', async () => { 17 | const model = new InvocationModel({ 18 | invocationId: 'testInvocId', 19 | metadata: { 20 | name: 'testFuncName', 21 | bindings: { 22 | httpTrigger1: { 23 | type: 'httpTrigger', 24 | direction: 'in', 25 | }, 26 | $return: { 27 | type: 'http', 28 | direction: 'out', 29 | }, 30 | }, 31 | }, 32 | request: {}, 33 | log: testLog, 34 | }); 35 | const context = new InvocationContext(); 36 | const response = await model.getResponse(context, { body: 'Hello, world!' }); 37 | expect(response).to.deep.equal({ 38 | invocationId: 'testInvocId', 39 | outputData: [], 40 | returnValue: { 41 | http: { 42 | body: { 43 | bytes: Buffer.from('Hello, world!'), 44 | }, 45 | cookies: [], 46 | enableContentNegotiation: false, 47 | headers: { 48 | 'content-type': 'text/plain;charset=UTF-8', 49 | }, 50 | statusCode: '200', 51 | }, 52 | }, 53 | }); 54 | }); 55 | 56 | it('undefined output is not included in rpc response', async () => { 57 | // https://github.com/Azure/azure-functions-nodejs-library/issues/71 58 | // If an output binding is undefined or null, we should exclude it from the `outputData` array otherwise host will throw an error 59 | // https://github.com/Azure/azure-functions-host/blob/6eea6da0952857b4cc64339f329cdf61432b5815/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs#L871 60 | const model = new InvocationModel({ 61 | invocationId: 'testInvocId', 62 | metadata: { 63 | name: 'testFuncName', 64 | bindings: { 65 | timerTrigger1: { 66 | type: 'timerTrigger', 67 | direction: 'in', 68 | }, 69 | queueOutput1: { 70 | type: 'queue', 71 | direction: 'out', 72 | }, 73 | }, 74 | }, 75 | request: {}, 76 | log: testLog, 77 | }); 78 | const context = new InvocationContext(); 79 | const response = await model.getResponse(context, undefined); 80 | expect(response).to.deep.equal({ invocationId: 'testInvocId', outputData: [], returnValue: undefined }); 81 | }); 82 | 83 | // https://github.com/Azure/azure-functions-nodejs-library/issues/210 84 | it('Missing binding', async () => { 85 | const model = new InvocationModel({ 86 | invocationId: 'testInvocId', 87 | metadata: { 88 | name: 'testFuncName', 89 | bindings: { 90 | httpTrigger1: { 91 | type: 'httpTrigger', 92 | direction: 'in', 93 | }, 94 | $return: { 95 | type: 'http', 96 | direction: 'out', 97 | }, 98 | }, 99 | }, 100 | request: { 101 | inputData: [ 102 | { 103 | name: 'httpTriggerMissing', 104 | }, 105 | ], 106 | }, 107 | log: testLog, 108 | }); 109 | await expect(model.getArguments()).to.be.rejectedWith( 110 | 'Failed to find binding "httpTriggerMissing" in bindings "httpTrigger1, $return".' 111 | ); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/Types.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { expect } from 'chai'; 5 | import * as cp from 'child_process'; 6 | import { Context } from 'mocha'; 7 | import * as path from 'path'; 8 | 9 | describe('Public TypeScript types', () => { 10 | for (const tsVersion of ['4']) { 11 | it(`builds with TypeScript v${tsVersion}`, async function (this: Context) { 12 | this.timeout(10 * 1000); 13 | expect(await runTsBuild(tsVersion)).to.equal(2); 14 | }); 15 | } 16 | }); 17 | 18 | async function runTsBuild(tsVersion: string): Promise { 19 | const repoRoot = path.join(__dirname, '..'); 20 | const tscPath = path.join(repoRoot, 'node_modules', `typescript${tsVersion}`, 'bin', 'tsc'); 21 | const projectFile = path.join(repoRoot, 'test', 'types', 'tsconfig.json'); 22 | return new Promise((resolve, reject) => { 23 | const cmd = cp.spawn('node', [tscPath, '--project', projectFile]); 24 | cmd.stdout.on('data', function (data) { 25 | console.log(data.toString()); 26 | }); 27 | cmd.stderr.on('data', function (data) { 28 | console.error(data.toString()); 29 | }); 30 | cmd.on('error', reject); 31 | cmd.on('close', resolve); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /test/converters/fromRpcContext.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { fromRpcRetryContext, fromRpcTraceContext } from '../../src/converters/fromRpcContext'; 7 | 8 | describe('fromRpcContext', () => { 9 | describe('fromRpcTraceContext', () => { 10 | it('Copies defined values', () => { 11 | const traceContext = fromRpcTraceContext({ 12 | traceParent: 'testParent', 13 | traceState: 'testState', 14 | attributes: { a: 'b' }, 15 | }); 16 | expect(traceContext?.traceParent).to.equal('testParent'); 17 | expect(traceContext?.traceState).to.equal('testState'); 18 | expect(traceContext?.attributes).to.deep.equal({ a: 'b' }); 19 | }); 20 | 21 | it('Converts null to undefined', () => { 22 | const traceContext = fromRpcTraceContext({ 23 | traceParent: null, 24 | traceState: null, 25 | attributes: null, 26 | }); 27 | expect(traceContext?.attributes).to.be.undefined; 28 | expect(traceContext?.traceParent).to.be.undefined; 29 | expect(traceContext?.traceState).to.be.undefined; 30 | }); 31 | 32 | it('Leaves undefined as-is', () => { 33 | const traceContext = fromRpcTraceContext({ 34 | traceParent: undefined, 35 | traceState: undefined, 36 | attributes: undefined, 37 | }); 38 | expect(traceContext?.attributes).to.be.undefined; 39 | expect(traceContext?.traceParent).to.be.undefined; 40 | expect(traceContext?.traceState).to.be.undefined; 41 | }); 42 | }); 43 | 44 | describe('fromRpcRetryContext', () => { 45 | it('Copies defined values', () => { 46 | const traceContext = fromRpcRetryContext({ 47 | retryCount: 1, 48 | maxRetryCount: 2, 49 | exception: undefined, 50 | }); 51 | expect(traceContext?.retryCount).to.equal(1); 52 | expect(traceContext?.maxRetryCount).to.equal(2); 53 | expect(traceContext?.exception).to.be.undefined; 54 | }); 55 | 56 | it('Copies defined values with exception', () => { 57 | const traceContext = fromRpcRetryContext({ 58 | retryCount: 1, 59 | maxRetryCount: 2, 60 | exception: { 61 | message: '1', 62 | source: '2', 63 | stackTrace: '3', 64 | }, 65 | }); 66 | expect(traceContext?.retryCount).to.equal(1); 67 | expect(traceContext?.maxRetryCount).to.equal(2); 68 | expect(traceContext?.exception).to.deep.equal({ 69 | message: '1', 70 | source: '2', 71 | stackTrace: '3', 72 | }); 73 | }); 74 | 75 | it('Errors for expected properties', () => { 76 | expect(() => 77 | fromRpcRetryContext({ 78 | retryCount: 1, 79 | maxRetryCount: undefined, 80 | }) 81 | ).to.throw(/internal error/i); 82 | 83 | expect(() => 84 | fromRpcRetryContext({ 85 | retryCount: null, 86 | maxRetryCount: 2, 87 | }) 88 | ).to.throw(/internal error/i); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/converters/fromRpcTypedData.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { fromString } from 'long'; 7 | import { HttpRequest } from '../../src'; 8 | import { fromRpcTypedData } from '../../src/converters/fromRpcTypedData'; 9 | import Long = require('long'); 10 | 11 | describe('fromRpcTypedData', () => { 12 | it('null', () => { 13 | expect(fromRpcTypedData(null)).to.be.undefined; 14 | expect(fromRpcTypedData(undefined)).to.be.undefined; 15 | }); 16 | 17 | it('string', () => { 18 | expect(fromRpcTypedData({ string: 'test' })).to.equal('test'); 19 | expect(fromRpcTypedData({ string: '{ "a": "b" }' })).to.deep.equal({ a: 'b' }); 20 | expect(fromRpcTypedData({ string: '{ "a": "b" ' })).to.equal('{ "a": "b" '); 21 | expect(fromRpcTypedData({ string: '[1,2]' })).to.deep.equal([1, 2]); 22 | expect(fromRpcTypedData({ string: 'true' })).to.equal(true); 23 | expect(fromRpcTypedData({ string: 'false' })).to.equal(false); 24 | expect(fromRpcTypedData({ string: '1' })).to.equal(1); 25 | expect(fromRpcTypedData({ string: '0' })).to.equal(0); 26 | }); 27 | 28 | it('json', () => { 29 | expect(fromRpcTypedData({ json: '{ "a": "b" }' })).to.deep.equal({ a: 'b' }); 30 | expect(fromRpcTypedData({ json: '[1,2]' })).to.deep.equal([1, 2]); 31 | expect(fromRpcTypedData({ json: 'true' })).to.be.true; 32 | expect(fromRpcTypedData({ json: 'false' })).to.be.false; 33 | expect(fromRpcTypedData({ json: '1' })).to.equal(1); 34 | expect(fromRpcTypedData({ json: '0' })).to.equal(0); 35 | expect(() => fromRpcTypedData({ json: '{ "a": "b" ' })).to.throw(/json/i); 36 | }); 37 | 38 | it('bytes', () => { 39 | const result: any = fromRpcTypedData({ bytes: new Uint8Array([116, 101, 115, 116]) }); 40 | expect(result).to.be.instanceOf(Buffer); 41 | expect(result.toString()).to.equal('test'); 42 | }); 43 | 44 | it('stream', () => { 45 | const result: any = fromRpcTypedData({ stream: new Uint8Array([116, 101, 115, 116]) }); 46 | expect(result).to.be.instanceOf(Buffer); 47 | expect(result.toString()).to.equal('test'); 48 | }); 49 | 50 | it('http', async () => { 51 | const result: any = fromRpcTypedData({ 52 | http: { 53 | method: 'POST', 54 | body: { 55 | bytes: new Uint8Array([116, 101, 115, 116]), 56 | }, 57 | url: 'http://microsoft.com', 58 | }, 59 | }); 60 | expect(result).to.be.instanceOf(HttpRequest); 61 | expect(await result.text()).to.equal('test'); 62 | expect(result.url).to.equal('http://microsoft.com/'); 63 | }); 64 | 65 | it('int', () => { 66 | expect(fromRpcTypedData({ int: 1 })).to.equal(1); 67 | expect(fromRpcTypedData({ int: 0 })).to.equal(0); 68 | }); 69 | 70 | it('double', () => { 71 | expect(fromRpcTypedData({ double: 1 })).to.equal(1); 72 | expect(fromRpcTypedData({ double: 1.3 })).to.equal(1.3); 73 | expect(fromRpcTypedData({ double: 0 })).to.equal(0); 74 | }); 75 | 76 | it('collectionBytes', () => { 77 | const test1 = new Uint8Array([116, 101, 115, 116, 49]); 78 | const test2 = new Uint8Array([116, 101, 115, 116, 50]); 79 | const result: any = fromRpcTypedData({ collectionBytes: { bytes: [test1, test2] } }); 80 | expect(result[0]).to.be.instanceOf(Buffer); 81 | expect(result[0].toString()).to.equal('test1'); 82 | expect(result[1]).to.be.instanceOf(Buffer); 83 | expect(result[1].toString()).to.equal('test2'); 84 | }); 85 | 86 | it('collectionString', () => { 87 | expect(fromRpcTypedData({ collectionString: { string: ['test1', 'test2'] } })).to.deep.equal([ 88 | 'test1', 89 | 'test2', 90 | ]); 91 | expect(fromRpcTypedData({ collectionString: { string: ['{"a": "b"}', 'test2'] } })).to.deep.equal([ 92 | { a: 'b' }, 93 | 'test2', 94 | ]); 95 | expect(fromRpcTypedData({ collectionString: { string: ['{"a": "b"', 'test2'] } })).to.deep.equal([ 96 | '{"a": "b"', 97 | 'test2', 98 | ]); 99 | }); 100 | 101 | it('collectionDouble', () => { 102 | const result: any = fromRpcTypedData({ collectionDouble: { double: [1.1, 2.2] } }); 103 | expect(result).to.deep.equal([1.1, 2.2]); 104 | }); 105 | 106 | it('collectionSint64', () => { 107 | const result: any = fromRpcTypedData({ collectionSint64: { sint64: [123, fromString('9007199254740992')] } }); 108 | expect(result[0]).to.equal(123); 109 | expect(result[1]).to.be.instanceOf(Long); 110 | expect(result[1].toString()).to.equal('9007199254740992'); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/converters/toCamelCase.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { toCamelCaseValue } from '../../src/converters/toCamelCase'; 7 | 8 | describe('toCamelCaseValue', () => { 9 | it('false-y values', () => { 10 | const testData = { 11 | Zero: 0, 12 | False: false, 13 | Null: null, 14 | Undefined: undefined, 15 | EMPTY: '', 16 | }; 17 | 18 | expect(toCamelCaseValue(testData)).to.deep.equal({ 19 | zero: 0, 20 | false: false, 21 | null: null, 22 | undefined: undefined, 23 | eMPTY: '', 24 | }); 25 | }); 26 | 27 | it('array', () => { 28 | const testData = [{ A: 1 }, { B: 2 }]; 29 | expect(toCamelCaseValue(testData)).to.deep.equal([{ a: 1 }, { b: 2 }]); 30 | }); 31 | 32 | it('nested object', () => { 33 | const testData = { A: { B: { C: { D: 1 } } } }; 34 | expect(toCamelCaseValue(testData)).to.deep.equal({ a: { b: { c: { d: 1 } } } }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/converters/toRpcDuration.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { toRpcDuration } from '../../src/converters/toRpcDuration'; 7 | 8 | describe('toRpcDuration', () => { 9 | it('number', () => { 10 | const value = toRpcDuration(5000, 'test'); 11 | expect(value).to.deep.equal({ seconds: 5 }); 12 | }); 13 | 14 | it('zero', () => { 15 | const value = toRpcDuration(0, 'test'); 16 | expect(value).to.deep.equal({ seconds: 0 }); 17 | }); 18 | 19 | it('milliseconds', () => { 20 | const value = toRpcDuration({ milliseconds: 6000 }, 'test'); 21 | expect(value).to.deep.equal({ seconds: 6 }); 22 | }); 23 | 24 | it('seconds', () => { 25 | const value = toRpcDuration({ seconds: 7 }, 'test'); 26 | expect(value).to.deep.equal({ seconds: 7 }); 27 | }); 28 | 29 | it('minutes', () => { 30 | const value = toRpcDuration({ minutes: 3 }, 'test'); 31 | expect(value).to.deep.equal({ seconds: 180 }); 32 | }); 33 | 34 | it('hours', () => { 35 | const value = toRpcDuration({ hours: 2 }, 'test'); 36 | expect(value).to.deep.equal({ seconds: 7200 }); 37 | }); 38 | 39 | it('combined', () => { 40 | const value = toRpcDuration({ hours: 1, minutes: 1, seconds: 1, milliseconds: 1000 }, 'test'); 41 | expect(value).to.deep.equal({ seconds: 3662 }); 42 | }); 43 | 44 | it('throws and does not convert string', () => { 45 | expect(() => { 46 | toRpcDuration('invalid', 'test'); 47 | }).to.throw( 48 | "A 'number' or 'Duration' object was expected instead of a 'string'. Cannot parse value of 'test'." 49 | ); 50 | }); 51 | 52 | it('does not convert null', () => { 53 | const value = toRpcDuration(null, 'test'); 54 | expect(value).to.be.undefined; 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/converters/toRpcHttp.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import * as chai from 'chai'; 6 | import { expect } from 'chai'; 7 | import * as chaiAsPromised from 'chai-as-promised'; 8 | import { Headers } from 'undici'; 9 | import { toRpcHttp } from '../../src/converters/toRpcHttp'; 10 | import { HttpResponse } from '../../src/http/HttpResponse'; 11 | 12 | chai.use(chaiAsPromised); 13 | 14 | describe('toRpcHttp', () => { 15 | const textPlainHeaders = { 'content-type': 'text/plain;charset=UTF-8' }; 16 | const jsonHeaders = { 'content-type': 'application/json' }; 17 | function getExpectedRpcHttp(body: string, headers: Record, statusCode = '200') { 18 | return { 19 | http: { 20 | statusCode, 21 | body: { 22 | bytes: Buffer.from(body), 23 | }, 24 | headers: headers, 25 | enableContentNegotiation: false, 26 | cookies: [], 27 | }, 28 | }; 29 | } 30 | 31 | it('hello world', async () => { 32 | const result = await toRpcHttp('invocId', { body: 'hello world' }); 33 | expect(result).to.deep.equal(getExpectedRpcHttp('hello world', textPlainHeaders)); 34 | }); 35 | 36 | it('response class hello world', async () => { 37 | const result = await toRpcHttp('invocId', new HttpResponse({ body: 'hello world' })); 38 | expect(result).to.deep.equal(getExpectedRpcHttp('hello world', textPlainHeaders)); 39 | }); 40 | 41 | it('response class json', async () => { 42 | const result = await toRpcHttp('invocId', new HttpResponse({ jsonBody: { hello: 'world' } })); 43 | expect(result).to.deep.equal(getExpectedRpcHttp('{"hello":"world"}', jsonHeaders)); 44 | }); 45 | 46 | it('response class json manual json', async () => { 47 | const req = new HttpResponse({ body: '{ "hello": "world" }' }); 48 | req.headers.set('content-type', 'application/json'); 49 | const result = await toRpcHttp('invocId', req); 50 | expect(result).to.deep.equal( 51 | getExpectedRpcHttp('{ "hello": "world" }', { 52 | 'content-type': 'application/json', 53 | }) 54 | ); 55 | }); 56 | 57 | it('undefined', async () => { 58 | const result = await toRpcHttp('invocId', {}); 59 | expect(result).to.deep.equal(getExpectedRpcHttp('', {})); 60 | }); 61 | 62 | it('array (weird, but still valid)', async () => { 63 | const result = await toRpcHttp('invocId', Object.assign([], { body: 'a' })); 64 | expect(result).to.deep.equal(getExpectedRpcHttp('a', textPlainHeaders)); 65 | }); 66 | 67 | it('invalid data string', async () => { 68 | await expect(toRpcHttp('invocId', 'invalid')).to.eventually.be.rejectedWith(/must be an object/i); 69 | }); 70 | 71 | it('invalid data boolean', async () => { 72 | await expect(toRpcHttp('invocId', true)).to.eventually.be.rejectedWith(/must be an object/i); 73 | }); 74 | 75 | it('body buffer', async () => { 76 | const result = await toRpcHttp('invocId', { body: Buffer.from('b') }); 77 | expect(result).to.deep.equal(getExpectedRpcHttp('b', {})); 78 | }); 79 | 80 | it('body number', async () => { 81 | const result = await toRpcHttp('invocId', { body: 3 }); 82 | expect(result).to.deep.equal(getExpectedRpcHttp('3', textPlainHeaders)); 83 | }); 84 | 85 | it('status number', async () => { 86 | const result = await toRpcHttp('invocId', { status: 400 }); 87 | expect(result).to.deep.equal(getExpectedRpcHttp('', {}, '400')); 88 | }); 89 | 90 | it('status string', async () => { 91 | const result = await toRpcHttp('invocId', { status: '500' }); 92 | expect(result).to.deep.equal(getExpectedRpcHttp('', {}, '500')); 93 | }); 94 | 95 | it('status invalid', async () => { 96 | await expect(toRpcHttp('invocId', { status: {} })).to.eventually.be.rejectedWith(/status/i); 97 | }); 98 | 99 | it('headers object', async () => { 100 | const result = await toRpcHttp('invocId', { 101 | headers: { 102 | a: 'b', 103 | }, 104 | }); 105 | expect(result?.http?.headers?.a).to.equal('b'); 106 | }); 107 | 108 | it('headers array', async () => { 109 | const result = await toRpcHttp('invocId', { 110 | headers: [['c', 'd']], 111 | }); 112 | expect(result?.http?.headers?.c).to.equal('d'); 113 | }); 114 | 115 | it('headers class', async () => { 116 | const result = await toRpcHttp('invocId', { 117 | headers: new Headers({ 118 | e: 'f', 119 | }), 120 | }); 121 | expect(result?.http?.headers?.e).to.equal('f'); 122 | }); 123 | 124 | it('headers invalid', async () => { 125 | await expect(toRpcHttp('invocId', { headers: true })).to.eventually.be.rejectedWith( 126 | /argument.*could not be converted/i 127 | ); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/converters/toRpcHttpCookie.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { Cookie } from '@azure/functions'; 6 | import { expect } from 'chai'; 7 | import { toRpcHttpCookie } from '../../src/converters/toRpcHttpCookie'; 8 | 9 | describe('toRpcHttpCookie', () => { 10 | it('http cookies', () => { 11 | const cookieInputs: Cookie[] = [ 12 | { 13 | name: 'mycookie', 14 | value: 'myvalue', 15 | maxAge: 200000, 16 | }, 17 | { 18 | name: 'mycookie2', 19 | value: 'myvalue2', 20 | path: '/', 21 | maxAge: '200000', 22 | }, 23 | { 24 | name: 'mycookie3-expires', 25 | value: 'myvalue3-expires', 26 | expires: new Date('December 17, 1995 03:24:00 PST'), 27 | }, 28 | ]; 29 | 30 | const rpcCookies = cookieInputs.map(toRpcHttpCookie); 31 | expect(rpcCookies[0]?.name).to.equal('mycookie'); 32 | expect(rpcCookies[0]?.value).to.equal('myvalue'); 33 | expect((rpcCookies[0]?.maxAge).value).to.equal(200000); 34 | 35 | expect(rpcCookies[1]?.name).to.equal('mycookie2'); 36 | expect(rpcCookies[1]?.value).to.equal('myvalue2'); 37 | expect((rpcCookies[1]?.path).value).to.equal('/'); 38 | expect((rpcCookies[1]?.maxAge).value).to.equal(200000); 39 | 40 | expect(rpcCookies[2]?.name).to.equal('mycookie3-expires'); 41 | expect(rpcCookies[2]?.value).to.equal('myvalue3-expires'); 42 | expect((rpcCookies[2]?.expires).value.seconds).to.equal(819199440); 43 | }); 44 | 45 | it('http cookie SameSite', () => { 46 | const cookieInputs: Cookie[] = [ 47 | { 48 | name: 'none-cookie', 49 | value: 'myvalue', 50 | sameSite: 'None', 51 | }, 52 | { 53 | name: 'lax-cookie', 54 | value: 'myvalue', 55 | sameSite: 'Lax', 56 | }, 57 | { 58 | name: 'strict-cookie', 59 | value: 'myvalue', 60 | sameSite: 'Strict', 61 | }, 62 | { 63 | name: 'default-cookie', 64 | value: 'myvalue', 65 | }, 66 | ]; 67 | 68 | const rpcCookies = cookieInputs.map(toRpcHttpCookie); 69 | expect(rpcCookies[0]?.name).to.equal('none-cookie'); 70 | expect(rpcCookies[0]?.sameSite).to.equal('explicitNone'); 71 | 72 | expect(rpcCookies[1]?.name).to.equal('lax-cookie'); 73 | expect(rpcCookies[1]?.sameSite).to.equal('lax'); 74 | 75 | expect(rpcCookies[2]?.name).to.equal('strict-cookie'); 76 | expect(rpcCookies[2]?.sameSite).to.equal('strict'); 77 | 78 | expect(rpcCookies[3]?.name).to.equal('default-cookie'); 79 | expect(rpcCookies[3]?.sameSite).to.equal('none'); 80 | }); 81 | 82 | it('throws on invalid input', () => { 83 | expect(() => { 84 | const cookieInputs: any[] = [ 85 | { 86 | name: 123, 87 | value: 'myvalue', 88 | maxAge: 200000, 89 | }, 90 | { 91 | name: 'mycookie2', 92 | value: 'myvalue2', 93 | path: '/', 94 | maxAge: '200000', 95 | }, 96 | { 97 | name: 'mycookie3-expires', 98 | value: 'myvalue3-expires', 99 | expires: new Date('December 17, 1995 03:24:00'), 100 | }, 101 | { 102 | name: 'mycookie3-expires', 103 | value: 'myvalue3-expires', 104 | expires: new Date(''), 105 | }, 106 | ]; 107 | 108 | cookieInputs.map(toRpcHttpCookie); 109 | }).to.throw(''); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/converters/toRpcNullable.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { 7 | toNullableBool, 8 | toNullableDouble, 9 | toNullableString, 10 | toNullableTimestamp, 11 | } from '../../src/converters/toRpcNullable'; 12 | 13 | describe('toRpcNullable', () => { 14 | describe('toNullableBool', () => { 15 | it('true', () => { 16 | const nullable = toNullableBool(true, 'test'); 17 | expect(nullable && nullable.value).to.equal(true); 18 | }); 19 | 20 | it('false', () => { 21 | const nullable = toNullableBool(false, 'test'); 22 | expect(nullable && nullable.value).to.equal(false); 23 | }); 24 | 25 | it('throws and does not convert string', () => { 26 | expect(() => { 27 | toNullableBool('true', 'test'); 28 | }).to.throw("A 'boolean' type was expected instead of a 'string' type. Cannot parse value of 'test'."); 29 | }); 30 | 31 | it('does not convert null', () => { 32 | const nullable = toNullableBool(null, 'test'); 33 | expect(nullable && nullable.value).to.be.undefined; 34 | }); 35 | }); 36 | 37 | describe('toNullableString', () => { 38 | it('string', () => { 39 | const input = 'hello'; 40 | const nullable = toNullableString(input, 'test'); 41 | expect(nullable && nullable.value).to.equal(input); 42 | }); 43 | 44 | it('empty string', () => { 45 | const input = ''; 46 | const nullable = toNullableString(input, 'test'); 47 | expect(nullable && nullable.value).to.equal(input); 48 | }); 49 | 50 | it('throws and does not convert number', () => { 51 | expect(() => { 52 | toNullableString(123, 'test'); 53 | }).to.throw("A 'string' type was expected instead of a 'number' type. Cannot parse value of 'test'."); 54 | }); 55 | 56 | it('does not convert null', () => { 57 | const nullable = toNullableString(null, 'test'); 58 | expect(nullable && nullable.value).to.be.undefined; 59 | }); 60 | }); 61 | 62 | describe('toNullableDouble', () => { 63 | it('number', () => { 64 | const input = 1234567; 65 | const nullable = toNullableDouble(input, 'test'); 66 | expect(nullable && nullable.value).to.equal(input); 67 | }); 68 | 69 | it('0', () => { 70 | const input = 0; 71 | const nullable = toNullableDouble(input, 'test'); 72 | expect(nullable && nullable.value).to.equal(input); 73 | }); 74 | 75 | it('negative number', () => { 76 | const input = -11234567; 77 | const nullable = toNullableDouble(input, 'test'); 78 | expect(nullable && nullable.value).to.equal(input); 79 | }); 80 | 81 | it('numeric string', () => { 82 | const input = '1234567'; 83 | const nullable = toNullableDouble(input, 'test'); 84 | expect(nullable && nullable.value).to.equal(1234567); 85 | }); 86 | 87 | it('float string', () => { 88 | const input = '1234567.002'; 89 | const nullable = toNullableDouble(input, 'test'); 90 | expect(nullable && nullable.value).to.equal(1234567.002); 91 | }); 92 | 93 | it('throws and does not convert non-number string', () => { 94 | expect(() => { 95 | toNullableDouble('123hellohello!!111', 'test'); 96 | }).to.throw("A 'number' type was expected instead of a 'string' type. Cannot parse value of 'test'."); 97 | }); 98 | 99 | it('does not convert undefined', () => { 100 | const nullable = toNullableDouble(undefined, 'test'); 101 | expect(nullable && nullable.value).to.be.undefined; 102 | }); 103 | }); 104 | 105 | describe('toNullableTimestamp', () => { 106 | it('Date', () => { 107 | const input = new Date('1/2/2014'); 108 | const nullable = toNullableTimestamp(input, 'test'); 109 | const secondInput = Math.round((input).getTime() / 1000); 110 | expect(nullable && nullable.value && nullable.value.seconds).to.equal(secondInput); 111 | }); 112 | 113 | it('Date.now', () => { 114 | const input = Date.now(); 115 | const nullable = toNullableTimestamp(input, 'test'); 116 | const secondInput = Math.round(input / 1000); 117 | expect(nullable && nullable.value && nullable.value.seconds).to.equal(secondInput); 118 | }); 119 | 120 | it('milliseconds', () => { 121 | const input = Date.now(); 122 | const nullable = toNullableTimestamp(input, 'test'); 123 | const secondInput = Math.round(input / 1000); 124 | expect(nullable && nullable.value && nullable.value.seconds).to.equal(secondInput); 125 | }); 126 | 127 | it('does not convert string', () => { 128 | expect(() => { 129 | toNullableTimestamp('1/2/3 2014', 'test'); 130 | }).to.throw("A 'number' or 'Date' input was expected instead of a 'string'. Cannot parse value of 'test'."); 131 | }); 132 | 133 | it('does not convert object', () => { 134 | expect(() => { 135 | toNullableTimestamp({ time: 100 }, 'test'); 136 | }).to.throw("A 'number' or 'Date' input was expected instead of a 'object'. Cannot parse value of 'test'."); 137 | }); 138 | 139 | it('does not convert undefined', () => { 140 | const nullable = toNullableTimestamp(undefined, 'test'); 141 | expect(nullable && nullable.value).to.be.undefined; 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /test/converters/toRpcTypedData.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { toRpcTypedData } from '../../src/converters/toRpcTypedData'; 7 | 8 | describe('toRpcTypedData', () => { 9 | it('undefined', () => { 10 | expect(toRpcTypedData(undefined)).to.be.undefined; 11 | }); 12 | 13 | it('null', () => { 14 | expect(toRpcTypedData(null)).to.be.null; 15 | }); 16 | 17 | it('string', () => { 18 | expect(toRpcTypedData('test')).to.deep.equal({ string: 'test' }); 19 | }); 20 | 21 | it('buffer', () => { 22 | expect(toRpcTypedData(Buffer.from('test'))).to.deep.equal({ bytes: Buffer.from('test') }); 23 | }); 24 | 25 | it('array buffer', () => { 26 | expect(toRpcTypedData(new DataView(new ArrayBuffer(4)))).to.deep.equal({ bytes: new Uint8Array(4) }); 27 | }); 28 | 29 | it('int', () => { 30 | expect(toRpcTypedData(3)).to.deep.equal({ int: 3 }); 31 | }); 32 | 33 | it('double', () => { 34 | expect(toRpcTypedData(3.4)).to.deep.equal({ double: 3.4 }); 35 | }); 36 | 37 | it('json object', () => { 38 | expect(toRpcTypedData({ a: 'b' })).to.deep.equal({ json: '{"a":"b"}' }); 39 | }); 40 | 41 | it('json array', () => { 42 | expect(toRpcTypedData([{ a: 'b' }])).to.deep.equal({ json: '[{"a":"b"}]' }); 43 | }); 44 | 45 | it('json boolean', () => { 46 | expect(toRpcTypedData(true)).to.deep.equal({ json: 'true' }); 47 | }); 48 | 49 | it('function', () => { 50 | expect( 51 | toRpcTypedData(() => { 52 | console.log('hello'); 53 | }) 54 | ).to.deep.equal({ json: undefined }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/errors.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { ensureErrorType, trySetErrorMessage } from '../src/errors'; 7 | 8 | describe('ensureErrorType', () => { 9 | it('null', () => { 10 | validateError(ensureErrorType(null), 'Unknown error'); 11 | }); 12 | 13 | it('undefined', () => { 14 | validateError(ensureErrorType(undefined), 'Unknown error'); 15 | }); 16 | 17 | it('boolean', () => { 18 | validateError(ensureErrorType(true), 'true'); 19 | validateError(ensureErrorType(false), 'false'); 20 | }); 21 | 22 | it('number', () => { 23 | validateError(ensureErrorType(5), '5'); 24 | }); 25 | 26 | it('string', () => { 27 | validateError(ensureErrorType('test'), 'test'); 28 | validateError(ensureErrorType(' '), ' '); 29 | validateError(ensureErrorType(''), ''); 30 | }); 31 | 32 | it('object', () => { 33 | validateError(ensureErrorType({ test: '2' }), '{"test":"2"}'); 34 | }); 35 | 36 | it('array', () => { 37 | validateError(ensureErrorType([1, 2]), '[1,2]'); 38 | }); 39 | 40 | it('error', () => { 41 | const actualError = new Error('test2'); // Should return the original error instance, so don't use validateError which is more of a "deep" equal 42 | expect(ensureErrorType(actualError)).to.equal(actualError); 43 | }); 44 | 45 | it('modify error message', () => { 46 | const actualError = new Error('test2'); 47 | trySetErrorMessage(actualError, 'modified message'); 48 | 49 | expect(actualError.message).to.equal('modified message'); 50 | }); 51 | 52 | it('readonly error', () => { 53 | class ReadOnlyError extends Error { 54 | get message(): string { 55 | return 'a readonly message'; 56 | } 57 | } 58 | 59 | const actualError = new ReadOnlyError(); 60 | 61 | // @ts-expect-error: create a function to test that writing throws an exception 62 | expect(() => (actualError.message = 'exception')).to.throw(); 63 | 64 | const wrappedError = ensureErrorType(actualError); 65 | const message = 'Readonly error has not been modified'; 66 | trySetErrorMessage(wrappedError, message); 67 | 68 | expect(wrappedError.message).to.equal('a readonly message'); 69 | expect(wrappedError.stack).to.not.contain('Readonly error has been modified'); 70 | }); 71 | 72 | function validateError(actual: Error, expectedMessage: string): void { 73 | expect(actual).to.be.instanceof(Error); 74 | expect(actual.message).to.equal(expectedMessage); 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /test/hooks.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { 7 | app, 8 | AppStartContext, 9 | AppTerminateContext, 10 | HookContext, 11 | InvocationContext, 12 | InvocationHookContext, 13 | PostInvocationContext, 14 | PreInvocationContext, 15 | } from '../src/index'; 16 | 17 | describe('hooks', () => { 18 | it("register doesn't throw error in unit test mode", () => { 19 | app.hook.appStart(() => {}); 20 | app.hook.appTerminate(() => {}); 21 | app.hook.postInvocation(() => {}); 22 | const registeredHook = app.hook.preInvocation(() => {}); 23 | registeredHook.dispose(); 24 | }); 25 | 26 | it('AppTerminateContext', () => { 27 | const context = new AppTerminateContext(); 28 | validateHookContext(context); 29 | }); 30 | 31 | it('AppStartContext', () => { 32 | const context = new AppStartContext(); 33 | validateHookContext(context); 34 | }); 35 | 36 | it('PreInvocationContext', () => { 37 | const context = new PreInvocationContext(); 38 | validateInvocationHookContext(context); 39 | expect(typeof context.functionHandler).to.equal('function'); 40 | 41 | const updatedFunc = () => { 42 | console.log('changed'); 43 | }; 44 | context.functionHandler = updatedFunc; 45 | expect(context.functionHandler).to.equal(updatedFunc); 46 | }); 47 | 48 | it('PostInvocationContext', () => { 49 | const context = new PostInvocationContext(); 50 | validateInvocationHookContext(context); 51 | expect(context.error).to.equal(undefined); 52 | expect(context.result).to.equal(undefined); 53 | 54 | const newError = new Error('test1'); 55 | context.error = newError; 56 | context.result = 'test2'; 57 | expect(context.error).to.equal(newError); 58 | expect(context.result).to.equal('test2'); 59 | }); 60 | 61 | function validateInvocationHookContext(context: InvocationHookContext): void { 62 | validateHookContext(context); 63 | expect(context.inputs).to.deep.equal([]); 64 | expect(context.invocationContext).to.deep.equal(new InvocationContext()); 65 | 66 | expect(() => { 67 | context.invocationContext = {}; 68 | }).to.throw(); 69 | context.inputs = ['change']; 70 | expect(context.inputs).to.deep.equal(['change']); 71 | } 72 | 73 | function validateHookContext(context: HookContext) { 74 | expect(context.hookData).to.deep.equal({}); 75 | expect(() => { 76 | context.hookData = {}; 77 | }).to.throw(); 78 | } 79 | }); 80 | -------------------------------------------------------------------------------- /test/http/HttpRequest.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import * as chai from 'chai'; 6 | import { expect } from 'chai'; 7 | import * as chaiAsPromised from 'chai-as-promised'; 8 | import { File } from 'undici'; 9 | import { HttpRequest } from '../../src/http/HttpRequest'; 10 | 11 | chai.use(chaiAsPromised); 12 | 13 | describe('HttpRequest', () => { 14 | it('clone', async () => { 15 | const req = new HttpRequest({ 16 | method: 'POST', 17 | url: 'http://localhost:7071/api/helloWorld', 18 | body: { 19 | string: 'body1', 20 | }, 21 | headers: { 22 | a: 'b', 23 | }, 24 | params: { 25 | c: 'd', 26 | }, 27 | query: { 28 | e: 'f', 29 | }, 30 | }); 31 | const req2 = req.clone(); 32 | expect(await req.text()).to.equal('body1'); 33 | expect(await req2.text()).to.equal('body1'); 34 | 35 | expect(req.headers).to.not.equal(req2.headers); 36 | expect(req.headers).to.deep.equal(req2.headers); 37 | 38 | expect(req.params).to.not.equal(req2.params); 39 | expect(req.params).to.deep.equal(req2.params); 40 | 41 | expect(req.query).to.not.equal(req2.query); 42 | expect(req.query).to.deep.equal(req2.query); 43 | }); 44 | 45 | describe('formData', () => { 46 | const multipartContentType = 'multipart/form-data; boundary=----WebKitFormBoundaryeJGMO2YP65ZZXRmv'; 47 | function createFormRequest(data: string, contentType: string = multipartContentType): HttpRequest { 48 | // Form data always uses CRLF instead of LF 49 | // https://www.rfc-editor.org/rfc/rfc2046#section-4.1.1 50 | data = data.replace(/\r?\n/g, '\r\n'); 51 | 52 | return new HttpRequest({ 53 | method: 'POST', 54 | url: 'http://localhost:7071/api/HttpForm1', 55 | body: { 56 | bytes: Buffer.from(data), 57 | }, 58 | headers: { 59 | 'content-type': contentType, 60 | }, 61 | }); 62 | } 63 | 64 | it('hello world', async () => { 65 | const req = createFormRequest(`------WebKitFormBoundaryeJGMO2YP65ZZXRmv 66 | Content-Disposition: form-data; name="name" 67 | 68 | Azure Functions 69 | ------WebKitFormBoundaryeJGMO2YP65ZZXRmv 70 | Content-Disposition: form-data; name="greeting" 71 | 72 | Hello 73 | ------WebKitFormBoundaryeJGMO2YP65ZZXRmv-- 74 | `); 75 | 76 | const parsedForm = await req.formData(); 77 | 78 | expect(parsedForm.has('name')).to.equal(true); 79 | expect(parsedForm.get('name')).to.equal('Azure Functions'); 80 | 81 | expect(parsedForm.has('greeting')).to.equal(true); 82 | expect(parsedForm.get('greeting')).to.equal('Hello'); 83 | }); 84 | 85 | it('file', async () => { 86 | const req = createFormRequest(`------WebKitFormBoundaryeJGMO2YP65ZZXRmv 87 | Content-Disposition: form-data; name="myfile"; filename="test.txt" 88 | Content-Type: text/plain 89 | 90 | hello 91 | world 92 | ------WebKitFormBoundaryeJGMO2YP65ZZXRmv-- 93 | `); 94 | 95 | const parsedForm = await req.formData(); 96 | expect(parsedForm.has('myfile')).to.equal(true); 97 | const file = parsedForm.get('myfile'); 98 | expect(file.name).to.equal('test.txt'); 99 | expect(file.type).to.equal('text/plain'); 100 | expect(await file.text()).to.equal(`hello\r\nworld`); 101 | }); 102 | 103 | it('duplicate parts', async () => { 104 | const req = createFormRequest(`------WebKitFormBoundaryeJGMO2YP65ZZXRmv 105 | Content-Disposition: form-data; name="dupeField" 106 | 107 | value1 108 | ------WebKitFormBoundaryeJGMO2YP65ZZXRmv 109 | Content-Disposition: form-data; name="dupeField" 110 | 111 | value2 112 | ------WebKitFormBoundaryeJGMO2YP65ZZXRmv-- 113 | `); 114 | 115 | const parsedForm = await req.formData(); 116 | expect(parsedForm.has('dupeField')).to.equal(true); 117 | expect(parsedForm.get('dupeField')).to.equal('value1'); 118 | 119 | expect(parsedForm.getAll('dupeField')).to.deep.equal(['value1', 'value2']); 120 | }); 121 | 122 | it('url encoded', async () => { 123 | const req = createFormRequest('name=Azure+Functions&greeting=Hello', 'application/x-www-form-urlencoded'); 124 | 125 | const parsedForm = await req.formData(); 126 | 127 | expect(parsedForm.has('name')).to.equal(true); 128 | expect(parsedForm.get('name')).to.equal('Azure Functions'); 129 | 130 | expect(parsedForm.has('greeting')).to.equal(true); 131 | expect(parsedForm.get('greeting')).to.equal('Hello'); 132 | }); 133 | 134 | it('Unsupported content type', async () => { 135 | const contentTypes = ['application/octet-stream', 'application/json', 'text/plain', 'invalid']; 136 | for (const contentType of contentTypes) { 137 | const req = createFormRequest('', contentType); 138 | await expect(req.formData()).to.eventually.be.rejectedWith(/Could not parse content as FormData/i); 139 | } 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /test/http/HttpResponse.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import * as chai from 'chai'; 6 | import { expect } from 'chai'; 7 | import * as chaiAsPromised from 'chai-as-promised'; 8 | import { HttpResponse } from '../../src/http/HttpResponse'; 9 | 10 | chai.use(chaiAsPromised); 11 | 12 | describe('HttpResponse', () => { 13 | it('clone', async () => { 14 | const res = new HttpResponse({ 15 | body: 'body1', 16 | headers: { 17 | a: 'b', 18 | }, 19 | cookies: [ 20 | { 21 | name: 'name1', 22 | value: 'value1', 23 | }, 24 | ], 25 | }); 26 | const res2 = res.clone(); 27 | expect(await res.text()).to.equal('body1'); 28 | expect(await res2.text()).to.equal('body1'); 29 | 30 | expect(res.headers).to.not.equal(res2.headers); 31 | expect(res.headers).to.deep.equal(res2.headers); 32 | 33 | expect(res.cookies).to.not.equal(res2.cookies); 34 | expect(res.cookies).to.deep.equal(res2.cookies); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/http/extractHttpUserFromHeaders.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { HttpRequestUser } from '@azure/functions'; 6 | import { expect } from 'chai'; 7 | import { Headers } from 'undici'; 8 | import { extractHttpUserFromHeaders } from '../../src/http/extractHttpUserFromHeaders'; 9 | 10 | describe('Extract Http User Claims Principal from Headers', () => { 11 | it('Correctly parses AppService headers', () => { 12 | const username = 'test@example.com'; 13 | const id = 'testId'; 14 | const provider = 'aad'; 15 | const claimsPrincipalData = { 16 | auth_typ: provider, 17 | claims: [ 18 | { 19 | typ: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', 20 | val: username, 21 | }, 22 | { typ: 'name', val: 'Example Test' }, 23 | { typ: 'nonce', val: '54b39eaa5596466f9336b9369e91d95e_20211117004911' }, 24 | { 25 | typ: 'http://schemas.microsoft.com/identity/claims/objectidentifier', 26 | val: id, 27 | }, 28 | { typ: 'preferred_username', val: username }, 29 | { typ: 'ver', val: '2.0' }, 30 | ], 31 | name_typ: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', 32 | role_typ: 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role', 33 | }; 34 | 35 | const headers: Headers = new Headers({ 36 | 'x-ms-client-principal-name': username, 37 | 'x-ms-client-principal-id': id, 38 | 'x-ms-client-principal-idp': provider, 39 | 'x-ms-client-principal': Buffer.from(JSON.stringify(claimsPrincipalData)).toString('base64'), 40 | }); 41 | 42 | const user: HttpRequestUser | null = extractHttpUserFromHeaders(headers); 43 | 44 | expect(user).to.not.be.null; 45 | expect(user?.type).to.equal('AppService'); 46 | expect(user?.id).to.equal(id); 47 | expect(user?.username).to.equal(username); 48 | expect(user?.identityProvider).to.equal(provider); 49 | expect(user?.claimsPrincipalData).to.deep.equal(claimsPrincipalData); 50 | }); 51 | 52 | it('Correctly parses StaticWebApps headers', () => { 53 | const id = 'testId'; 54 | const username = 'test@example.com'; 55 | const provider = 'aad'; 56 | const claimsPrinicipalData = { 57 | userId: id, 58 | userRoles: ['anonymous', 'authenticated'], 59 | identityProvider: provider, 60 | userDetails: username, 61 | }; 62 | 63 | const headers: Headers = new Headers({ 64 | 'x-ms-client-principal': Buffer.from(JSON.stringify(claimsPrinicipalData)).toString('base64'), 65 | }); 66 | 67 | const user: HttpRequestUser | null = extractHttpUserFromHeaders(headers); 68 | 69 | expect(user).to.not.be.null; 70 | expect(user?.type).to.equal('StaticWebApps'); 71 | expect(user?.id).to.equal(id); 72 | expect(user?.username).to.equal(username); 73 | expect(user?.identityProvider).to.equal(provider); 74 | expect(user?.claimsPrincipalData).to.deep.equal(claimsPrinicipalData); 75 | }); 76 | 77 | it('Correctly returns null on missing header data', () => { 78 | const headers: Headers = new Headers({ 79 | key1: 'val1', 80 | }); 81 | 82 | const user: HttpRequestUser | null = extractHttpUserFromHeaders(headers); 83 | 84 | expect(user).to.be.null; 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as globby from 'globby'; 5 | import * as Mocha from 'mocha'; 6 | import * as path from 'path'; 7 | 8 | export async function run(): Promise { 9 | try { 10 | const options: Mocha.MochaOptions = { 11 | color: true, 12 | require: ['ts-node/register'], 13 | reporter: 'mocha-multi-reporters', 14 | reporterOptions: { 15 | reporterEnabled: 'spec, mocha-junit-reporter', 16 | mochaJunitReporterReporterOptions: { 17 | mochaFile: path.resolve(__dirname, '..', 'test', 'unit-test-results.xml'), 18 | }, 19 | }, 20 | }; 21 | 22 | addEnvVarsToMochaOptions(options); 23 | console.log(`Mocha options: ${JSON.stringify(options, undefined, 2)}`); 24 | 25 | const mocha = new Mocha(options); 26 | 27 | const files: string[] = await globby('**/**.test.ts', { cwd: __dirname }); 28 | 29 | files.forEach((f) => mocha.addFile(path.resolve(__dirname, f))); 30 | 31 | const failures = await new Promise((resolve) => mocha.run(resolve)); 32 | if (failures > 0) { 33 | throw new Error(`${failures} tests failed.`); 34 | } 35 | } catch (err) { 36 | console.error(err); 37 | console.error('Test run failed'); 38 | process.exit(1); 39 | } 40 | } 41 | 42 | function addEnvVarsToMochaOptions(options: Mocha.MochaOptions): void { 43 | for (const envVar of Object.keys(process.env)) { 44 | const match: RegExpMatchArray | null = envVar.match(/^mocha_(.+)/i); 45 | if (match && match[1]) { 46 | const [, option] = match; 47 | let value: string | number = process.env[envVar] || ''; 48 | if (typeof value === 'string' && !isNaN(parseInt(value))) { 49 | value = parseInt(value); 50 | } 51 | (options)[option] = value; 52 | } 53 | } 54 | } 55 | 56 | void run(); 57 | -------------------------------------------------------------------------------- /test/setup.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import 'mocha'; 5 | import { expect } from 'chai'; 6 | import { capabilities, enableHttpStream, setup } from '../src/setup'; 7 | 8 | describe('setup', () => { 9 | it('enableHttpStream', () => { 10 | // default 11 | expect(enableHttpStream).to.equal(false); 12 | 13 | // set to true 14 | setup({ enableHttpStream: true }); 15 | expect(enableHttpStream).to.equal(true); 16 | 17 | // don't change if not explicitly set 18 | setup({}); 19 | expect(enableHttpStream).to.equal(true); 20 | setup({ capabilities: {} }); 21 | expect(enableHttpStream).to.equal(true); 22 | 23 | // set to false 24 | setup({ enableHttpStream: false }); 25 | expect(enableHttpStream).to.equal(false); 26 | }); 27 | 28 | it('capabilities', () => { 29 | // default 30 | expect(capabilities).to.deep.equal({}); 31 | 32 | // various setting & merging without replacing 33 | setup({ capabilities: { a: '1' } }); 34 | expect(capabilities).to.deep.equal({ a: '1' }); 35 | setup({}); 36 | expect(capabilities).to.deep.equal({ a: '1' }); 37 | setup({ capabilities: { b: '2' } }); 38 | expect(capabilities).to.deep.equal({ a: '1', b: '2' }); 39 | setup({ capabilities: { a: '3' } }); 40 | expect(capabilities).to.deep.equal({ a: '3', b: '2' }); 41 | 42 | // boolean converted to string 43 | setup({ capabilities: { c: true } }); 44 | expect(capabilities).to.deep.equal({ a: '3', b: '2', c: 'true' }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/types/index.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | // This file will be compiled by multiple versions of TypeScript as decribed in ./test/TypesTests.ts to verify there are no errors 5 | // Temporarily removing this code until we have updated templates for the new programming model 6 | -------------------------------------------------------------------------------- /test/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "strict": true, 6 | "outDir": "dist", 7 | "baseUrl": "./", 8 | "paths": { 9 | "@azure/functions": ["../../types"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "strict": true, 6 | "noUnusedLocals": true, 7 | "noUncheckedIndexedAccess": true, 8 | "outDir": "out", 9 | "sourceMap": true, 10 | "baseUrl": "./", 11 | "paths": { 12 | "@azure/functions": ["types"], 13 | "@azure/functions-core": ["types-core"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /types/cosmosDB.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { 5 | CosmosDBv3FunctionOptions, 6 | CosmosDBv3Handler, 7 | CosmosDBv3Input, 8 | CosmosDBv3InputOptions, 9 | CosmosDBv3Output, 10 | CosmosDBv3OutputOptions, 11 | CosmosDBv3Trigger, 12 | CosmosDBv3TriggerOptions, 13 | } from './cosmosDB.v3'; 14 | import { 15 | CosmosDBv4FunctionOptions, 16 | CosmosDBv4Handler, 17 | CosmosDBv4Input, 18 | CosmosDBv4InputOptions, 19 | CosmosDBv4Output, 20 | CosmosDBv4OutputOptions, 21 | CosmosDBv4Trigger, 22 | CosmosDBv4TriggerOptions, 23 | } from './cosmosDB.v4'; 24 | 25 | export type CosmosDBHandler = CosmosDBv3Handler | CosmosDBv4Handler; 26 | 27 | export type CosmosDBFunctionOptions = CosmosDBv3FunctionOptions | CosmosDBv4FunctionOptions; 28 | 29 | export type CosmosDBInputOptions = CosmosDBv3InputOptions | CosmosDBv4InputOptions; 30 | export type CosmosDBInput = CosmosDBv3Input | CosmosDBv4Input; 31 | 32 | export type CosmosDBTriggerOptions = CosmosDBv3TriggerOptions | CosmosDBv4TriggerOptions; 33 | export type CosmosDBTrigger = CosmosDBv3Trigger | CosmosDBv4Trigger; 34 | 35 | export type CosmosDBOutputOptions = CosmosDBv3OutputOptions | CosmosDBv4OutputOptions; 36 | export type CosmosDBOutput = CosmosDBv3Output | CosmosDBv4Output; 37 | -------------------------------------------------------------------------------- /types/eventGrid.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type EventGridHandler = (event: EventGridEvent, context: InvocationContext) => FunctionResult; 8 | 9 | export interface EventGridFunctionOptions extends EventGridTriggerOptions, Partial { 10 | handler: EventGridHandler; 11 | 12 | trigger?: EventGridTrigger; 13 | } 14 | 15 | /** 16 | * At this point in time there are no event grid trigger-specific options 17 | */ 18 | export interface EventGridTriggerOptions {} 19 | export type EventGridTrigger = FunctionTrigger & EventGridTriggerOptions; 20 | 21 | export interface EventGridOutputKeyOptions { 22 | /** 23 | * An app setting (or environment variable) that contains the URI for the custom topic 24 | */ 25 | topicEndpointUri: string; 26 | 27 | /** 28 | * An app setting (or environment variable) that contains an access key for the custom topic 29 | */ 30 | topicKeySetting: string; 31 | } 32 | export interface EventGridOutputConnectionOptions { 33 | /** 34 | * The value of the common prefix for the app setting that contains the `topicEndpointUri`. 35 | * When setting the `connection` property, the `topicEndpointUri` and `topicKeySetting` properties should NOT be set. 36 | */ 37 | connection: string; 38 | } 39 | export type EventGridOutputOptions = EventGridOutputKeyOptions | EventGridOutputConnectionOptions; 40 | export type EventGridOutput = FunctionOutput & EventGridOutputOptions; 41 | 42 | /** 43 | * [Link to docs and examples](https://docs.microsoft.com/azure/event-grid/event-schema) 44 | * This "partial" interface is meant to be used when creating an event yourself and allows some properties to be left out 45 | */ 46 | export interface EventGridPartialEvent { 47 | /** 48 | * Full resource path to the event source. This field isn't writeable. Event Grid provides this value 49 | * If included, must match the Event Grid topic Azure Resource Manager ID exactly. If not included, Event Grid will stamp onto the event. 50 | */ 51 | topic?: string; 52 | 53 | /** 54 | * Publisher-defined path to the event subject 55 | */ 56 | subject: string; 57 | 58 | /** 59 | * One of the registered event types for this event source 60 | */ 61 | eventType: string; 62 | 63 | /** 64 | * The time the event is generated based on the provider's UTC time 65 | */ 66 | eventTime: string; 67 | 68 | /** 69 | * Unique identifier for the event 70 | */ 71 | id: string; 72 | 73 | /** 74 | * Event data specific to the resource provider 75 | */ 76 | data?: Record; 77 | 78 | /** 79 | * The schema version of the data object. The publisher defines the schema version. 80 | * If not included, will be stamped with an empty value 81 | */ 82 | dataVersion?: string; 83 | 84 | /** 85 | * The schema version of the event metadata. Event Grid defines the schema of the top-level properties. Event Grid provides this value. 86 | * If included, must match the Event Grid Schema `metadataVersion` exactly (currently, only 1). If not included, Event Grid will stamp onto the event. 87 | */ 88 | metadataVersion?: string; 89 | } 90 | 91 | /** 92 | * [Link to docs and examples](https://docs.microsoft.com/azure/event-grid/event-schema) 93 | */ 94 | export interface EventGridEvent extends EventGridPartialEvent { 95 | /** 96 | * Full resource path to the event source. This field isn't writeable. Event Grid provides this value 97 | */ 98 | topic: string; 99 | 100 | /** 101 | * The schema version of the data object. The publisher defines the schema version. 102 | */ 103 | dataVersion: string; 104 | 105 | /** 106 | * The schema version of the event metadata. Event Grid defines the schema of the top-level properties. Event Grid provides this value. 107 | */ 108 | metadataVersion: string; 109 | } 110 | -------------------------------------------------------------------------------- /types/eventHub.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger, RetryOptions } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type EventHubHandler = (messages: unknown, context: InvocationContext) => FunctionResult; 8 | 9 | export interface EventHubFunctionOptions extends EventHubTriggerOptions, Partial { 10 | handler: EventHubHandler; 11 | 12 | trigger?: EventHubTrigger; 13 | 14 | /** 15 | * An optional retry policy to rerun a failed execution until either successful completion occurs or the maximum number of retries is reached. 16 | * Learn more [here](https://learn.microsoft.com/azure/azure-functions/functions-bindings-error-pages) 17 | */ 18 | retry?: RetryOptions; 19 | } 20 | 21 | export interface EventHubTriggerOptions { 22 | /** 23 | * An app setting (or environment variable) with the event hub connection string 24 | */ 25 | connection: string; 26 | 27 | /** 28 | * The name of the event hub. When the event hub name is also present in the connection string, that value overrides this property at runtime. 29 | */ 30 | eventHubName: string; 31 | 32 | /** 33 | * Set to `many` in order to enable batching. If omitted or set to `one`, a single message is passed to the function. 34 | */ 35 | cardinality?: 'many' | 'one'; 36 | 37 | /** 38 | * An optional property that sets the [consumer group](https://docs.microsoft.com/azure/event-hubs/event-hubs-features#event-consumers) used to subscribe to events in the hub. If omitted, the `$Default` consumer group is used. 39 | */ 40 | consumerGroup?: string; 41 | } 42 | export type EventHubTrigger = FunctionTrigger & EventHubTriggerOptions; 43 | 44 | export interface EventHubOutputOptions { 45 | /** 46 | * An app setting (or environment variable) with the event hub connection string 47 | */ 48 | connection: string; 49 | 50 | /** 51 | * The name of the event hub. When the event hub name is also present in the connection string, that value overrides this property at runtime. 52 | */ 53 | eventHubName: string; 54 | } 55 | export type EventHubOutput = FunctionOutput & EventHubOutputOptions; 56 | -------------------------------------------------------------------------------- /types/generic.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionOptions, RetryOptions } from './index'; 5 | 6 | export interface GenericFunctionOptions extends FunctionOptions { 7 | /** 8 | * An optional retry policy to rerun a failed execution until either successful completion occurs or the maximum number of retries is reached. 9 | * Learn more [here](https://learn.microsoft.com/azure/azure-functions/functions-bindings-error-pages) 10 | */ 11 | retry?: RetryOptions; 12 | } 13 | 14 | export interface GenericTriggerOptions extends Record { 15 | type: string; 16 | } 17 | 18 | export interface GenericInputOptions extends Record { 19 | type: string; 20 | } 21 | 22 | export interface GenericOutputOptions extends Record { 23 | type: string; 24 | } 25 | -------------------------------------------------------------------------------- /types/hooks/HookContext.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | /** 5 | * Base class for all hook context objects 6 | */ 7 | export declare class HookContext { 8 | /** 9 | * For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime 10 | */ 11 | constructor(init?: HookContextInit); 12 | 13 | /** 14 | * The recommended place to store and share data between hooks in the same scope (app-level vs invocation-level). 15 | * You should use a unique property name so that it doesn't conflict with other hooks' data. 16 | * This object is readonly. You may modify it, but attempting to overwrite it will throw an error 17 | */ 18 | readonly hookData: Record; 19 | } 20 | 21 | /** 22 | * Base interface for objects passed to HookContext constructors. 23 | * For testing purposes only. 24 | */ 25 | export interface HookContextInit { 26 | hookData?: Record; 27 | } 28 | -------------------------------------------------------------------------------- /types/hooks/appHooks.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { HookContext, HookContextInit } from './HookContext'; 5 | 6 | /** 7 | * Handler for app start hooks 8 | */ 9 | export type AppStartHandler = (context: AppStartContext) => void | Promise; 10 | 11 | /** 12 | * Context on a function app during app startup. 13 | */ 14 | export declare class AppStartContext extends HookContext { 15 | /** 16 | * For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime 17 | */ 18 | constructor(init?: AppStartContextInit); 19 | } 20 | 21 | /** 22 | * Handler for app terminate hooks 23 | */ 24 | export type AppTerminateHandler = (context: AppTerminateContext) => void | Promise; 25 | 26 | /** 27 | * Context on a function app during app termination. 28 | */ 29 | export declare class AppTerminateContext extends HookContext { 30 | /** 31 | * For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime 32 | */ 33 | constructor(init?: AppTerminateContextInit); 34 | } 35 | 36 | /** 37 | * Object passed to AppStartContext constructors. 38 | * For testing purposes only 39 | */ 40 | export interface AppStartContextInit extends HookContextInit {} 41 | 42 | /** 43 | * Object passed to AppTerminateContext constructors. 44 | * For testing purposes only 45 | */ 46 | export interface AppTerminateContextInit extends HookContextInit {} 47 | -------------------------------------------------------------------------------- /types/hooks/invocationHooks.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionHandler } from '../index'; 5 | import { InvocationContext } from '../InvocationContext'; 6 | import { HookContext, HookContextInit } from './HookContext'; 7 | 8 | /** 9 | * Handler for pre-invocation hooks. 10 | */ 11 | export type PreInvocationHandler = (context: PreInvocationContext) => void | Promise; 12 | 13 | /** 14 | * Context on a function before it executes. 15 | */ 16 | export declare class PreInvocationContext extends InvocationHookContext { 17 | /** 18 | * For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime 19 | */ 20 | constructor(init?: PreInvocationContextInit); 21 | 22 | /** 23 | * The arguments passed to this specific invocation. 24 | * Changes to this array _will_ affect the inputs passed to your function 25 | */ 26 | inputs: unknown[]; 27 | 28 | /** 29 | * The function handler for this specific invocation. Changes to this value _will_ affect the function itself 30 | */ 31 | functionHandler: FunctionHandler; 32 | } 33 | 34 | /** 35 | * Handler for post-invocation hooks 36 | */ 37 | export type PostInvocationHandler = (context: PostInvocationContext) => void | Promise; 38 | 39 | /** 40 | * Context on a function after it executes. 41 | */ 42 | export declare class PostInvocationContext extends InvocationHookContext { 43 | /** 44 | * For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime 45 | */ 46 | constructor(init?: PostInvocationContextInit); 47 | 48 | /** 49 | * The arguments passed to this specific invocation. 50 | */ 51 | inputs: unknown[]; 52 | 53 | /** 54 | * The result of the function. Changes to this value _will_ affect the overall result of the function 55 | */ 56 | result: unknown; 57 | 58 | /** 59 | * The error thrown by the function, or null/undefined if there is no error. Changes to this value _will_ affect the overall result of the function 60 | */ 61 | error: unknown; 62 | } 63 | 64 | /** 65 | * Base class for all invocation hook context objects 66 | */ 67 | export declare class InvocationHookContext extends HookContext { 68 | /** 69 | * For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime 70 | */ 71 | constructor(init?: InvocationHookContextInit); 72 | 73 | /** 74 | * The context object passed to the function. 75 | * This object is readonly. You may modify it, but attempting to overwrite it will throw an error 76 | */ 77 | readonly invocationContext: InvocationContext; 78 | } 79 | 80 | /** 81 | * Object passed to InvocationHookContext constructors. 82 | * For testing purposes only 83 | */ 84 | export interface InvocationHookContextInit extends HookContextInit { 85 | inputs?: unknown[]; 86 | 87 | invocationContext?: InvocationContext; 88 | } 89 | 90 | /** 91 | * Object passed to PreInvocationContext constructors. 92 | * For testing purposes only 93 | */ 94 | export interface PreInvocationContextInit extends InvocationHookContextInit { 95 | functionCallback?: FunctionHandler; 96 | } 97 | 98 | /** 99 | * Object passed to PostInvocationContext constructors. 100 | * For testing purposes only 101 | */ 102 | export interface PostInvocationContextInit extends InvocationHookContextInit { 103 | result?: unknown; 104 | 105 | error?: unknown; 106 | } 107 | -------------------------------------------------------------------------------- /types/hooks/logHooks.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { LogLevel } from '../index'; 5 | import { InvocationContext } from '../InvocationContext'; 6 | import { HookContext, HookContextInit } from './HookContext'; 7 | 8 | /** 9 | * Handler for log hooks. 10 | */ 11 | export type LogHookHandler = (context: LogHookContext) => void; 12 | 13 | /** 14 | * Context on a log 15 | */ 16 | export declare class LogHookContext extends HookContext { 17 | /** 18 | * For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime 19 | */ 20 | constructor(init?: LogHookContextInit); 21 | 22 | /** 23 | * If the log occurs during a function execution, the context object passed to the function handler. 24 | * Otherwise, undefined. 25 | */ 26 | readonly invocationContext: InvocationContext | undefined; 27 | 28 | /** 29 | * 'system' if the log is generated by Azure Functions, 'user' if the log is generated by your own app. 30 | */ 31 | readonly category: LogCategory; 32 | 33 | /** 34 | * Changes to this value _will_ affect the resulting log, but only for user-generated logs. 35 | */ 36 | level: LogLevel; 37 | 38 | /** 39 | * Changes to this value _will_ affect the resulting log, but only for user-generated logs. 40 | */ 41 | message: string; 42 | } 43 | 44 | /** 45 | * Object passed to LogHookContext constructors. 46 | * For testing purposes only 47 | */ 48 | export interface LogHookContextInit extends HookContextInit { 49 | invocationContext?: InvocationContext; 50 | 51 | level?: LogLevel; 52 | 53 | category?: LogCategory; 54 | 55 | message?: string; 56 | } 57 | 58 | export type LogCategory = 'user' | 'system' | 'customMetric'; 59 | -------------------------------------------------------------------------------- /types/hooks/registerHook.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { Disposable } from '../index'; 5 | import { AppStartHandler, AppTerminateHandler } from './appHooks'; 6 | import { PostInvocationHandler, PreInvocationHandler } from './invocationHooks'; 7 | import { LogHookHandler } from './logHooks'; 8 | 9 | /** 10 | * Register a hook to be run at the start of your application 11 | * 12 | * @param handler the handler for the hook 13 | * @returns a `Disposable` object that can be used to unregister the hook 14 | */ 15 | export function appStart(handler: AppStartHandler): Disposable; 16 | 17 | /** 18 | * Register a hook to be run during graceful shutdown of your application. 19 | * This hook will not be executed if your application is terminated forcefully. 20 | * Hooks have a limited time to execute during the termination grace period. 21 | * 22 | * @param handler the handler for the hook 23 | * @returns a `Disposable` object that can be used to unregister the hook 24 | */ 25 | export function appTerminate(handler: AppTerminateHandler): Disposable; 26 | 27 | /** 28 | * Register a hook to be run before a function is invoked. 29 | * 30 | * @param handler the handler for the hook 31 | * @returns a `Disposable` object that can be used to unregister the hook 32 | */ 33 | export function preInvocation(handler: PreInvocationHandler): Disposable; 34 | 35 | /** 36 | * Register a hook to be run after a function is invoked. 37 | * 38 | * @param handler the handler for the hook 39 | * @returns a `Disposable` object that can be used to unregister the hook 40 | */ 41 | export function postInvocation(handler: PostInvocationHandler): Disposable; 42 | 43 | /** 44 | * PREVIEW: Register a hook to be run for each log. 45 | * This functionality requires Azure Functions Host v4.34+. 46 | * 47 | * @param handler the handler for the hook 48 | * @returns a `Disposable` object that can be used to unregister the hook 49 | */ 50 | export function log(handler: LogHookHandler): Disposable; 51 | -------------------------------------------------------------------------------- /types/input.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { CosmosDBInput, CosmosDBInputOptions } from './cosmosDB'; 5 | import { GenericInputOptions } from './generic'; 6 | import { FunctionInput } from './index'; 7 | import { SqlInput, SqlInputOptions } from './sql'; 8 | import { StorageBlobInput, StorageBlobInputOptions } from './storage'; 9 | import { TableInput, TableInputOptions } from './table'; 10 | import { MySqlInput, MySqlInputOptions } from './mySql'; 11 | import { 12 | WebPubSubConnectionInput, 13 | WebPubSubConnectionInputOptions, 14 | WebPubSubContextInput, 15 | WebPubSubContextInputOptions, 16 | } from './webpubsub'; 17 | 18 | /** 19 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-blob-input?pivots=programming-language-javascript) 20 | */ 21 | export function storageBlob(options: StorageBlobInputOptions): StorageBlobInput; 22 | 23 | /** 24 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-table-input?pivots=programming-language-javascript) 25 | */ 26 | export function table(options: TableInputOptions): TableInput; 27 | 28 | /** 29 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-cosmosdb-v2-input?pivots=programming-language-javascript) 30 | */ 31 | export function cosmosDB(options: CosmosDBInputOptions): CosmosDBInput; 32 | 33 | /** 34 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-azure-sql-input?pivots=programming-language-javascript) 35 | */ 36 | export function sql(options: SqlInputOptions): SqlInput; 37 | 38 | /** 39 | * [Link to docs and examples](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-azure-mysql-input?pivots=programming-language-javascript) 40 | */ 41 | export function mySql(options: MySqlInputOptions): MySqlInput; 42 | 43 | /** 44 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-web-pubsub-input?pivots=programming-language-javascript) 45 | */ 46 | export function webPubSubConnection(options: WebPubSubConnectionInputOptions): WebPubSubConnectionInput; 47 | 48 | /** 49 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-web-pubsub-input?pivots=programming-language-javascript) 50 | */ 51 | export function webPubSubContext(options: WebPubSubContextInputOptions): WebPubSubContextInput; 52 | 53 | /** 54 | * A generic option that can be used for any input type 55 | * Use this method if your desired input type does not already have its own method 56 | */ 57 | export function generic(options: GenericInputOptions): FunctionInput; 58 | -------------------------------------------------------------------------------- /types/mySql.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionInput, FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type MySqlHandler = (changes: MySqlChange[], context: InvocationContext) => FunctionResult; 8 | 9 | export interface MySqlFunctionOptions extends MySqlTriggerOptions, Partial { 10 | handler: MySqlHandler; 11 | 12 | trigger?: MySqlTrigger; 13 | } 14 | 15 | export interface MySqlTriggerOptions { 16 | /** 17 | * The name of the table monitored by the trigger. 18 | */ 19 | tableName: string; 20 | 21 | /** 22 | * An app setting (or environment variable) with the connection string for the database containing the table monitored for changes 23 | */ 24 | connectionStringSetting: string; 25 | } 26 | export type MySqlTrigger = FunctionTrigger & MySqlTriggerOptions; 27 | 28 | export interface MySqlChange { 29 | Item: unknown; 30 | Operation: MySqlChangeOperation; 31 | } 32 | 33 | export enum MySqlChangeOperation { 34 | Update = 0, 35 | } 36 | 37 | export interface MySqlInputOptions { 38 | /** 39 | * The Transact-SQL query command or name of the stored procedure executed by the binding. 40 | */ 41 | commandText: string; 42 | 43 | /** 44 | * The command type value 45 | */ 46 | commandType: 'Text' | 'StoredProcedure'; 47 | 48 | /** 49 | * An app setting (or environment variable) with the connection string for the database against which the query or stored procedure is being executed 50 | */ 51 | connectionStringSetting: string; 52 | 53 | /** 54 | * Zero or more parameter values passed to the command during execution as a single string. 55 | * Must follow the format @param1=param1,@param2=param2. 56 | * Neither the parameter name nor the parameter value can contain a comma (,) or an equals sign (=). 57 | */ 58 | parameters?: string; 59 | } 60 | export type MySqlInput = FunctionInput & MySqlInputOptions; 61 | 62 | export interface MySqlOutputOptions { 63 | /** 64 | * The name of the table being written to by the binding. 65 | */ 66 | commandText: string; 67 | 68 | /** 69 | * An app setting (or environment variable) with the connection string for the database to which data is being written 70 | */ 71 | connectionStringSetting: string; 72 | } 73 | export type MySqlOutput = FunctionOutput & MySqlOutputOptions; 74 | -------------------------------------------------------------------------------- /types/output.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { CosmosDBOutput, CosmosDBOutputOptions } from './cosmosDB'; 5 | import { EventGridOutput, EventGridOutputOptions } from './eventGrid'; 6 | import { EventHubOutput, EventHubOutputOptions } from './eventHub'; 7 | import { GenericOutputOptions } from './generic'; 8 | import { HttpOutput, HttpOutputOptions } from './http'; 9 | import { FunctionOutput } from './index'; 10 | import { 11 | ServiceBusQueueOutput, 12 | ServiceBusQueueOutputOptions, 13 | ServiceBusTopicOutput, 14 | ServiceBusTopicOutputOptions, 15 | } from './serviceBus'; 16 | import { SqlOutput, SqlOutputOptions } from './sql'; 17 | import { StorageBlobOutput, StorageBlobOutputOptions, StorageQueueOutput, StorageQueueOutputOptions } from './storage'; 18 | import { TableOutput, TableOutputOptions } from './table'; 19 | import { MySqlOutput, MySqlOutputOptions } from './mySql'; 20 | import { WebPubSubOutput, WebPubSubOutputOptions } from './webpubsub'; 21 | 22 | /** 23 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-output?&pivots=programming-language-javascript) 24 | */ 25 | export function http(options: HttpOutputOptions): HttpOutput; 26 | 27 | /** 28 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-blob-output?pivots=programming-language-javascript) 29 | */ 30 | export function storageBlob(options: StorageBlobOutputOptions): StorageBlobOutput; 31 | 32 | /** 33 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-table-output?pivots=programming-language-javascript) 34 | */ 35 | export function table(options: TableOutputOptions): TableOutput; 36 | 37 | /** 38 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-queue-output?pivots=programming-language-javascript) 39 | */ 40 | export function storageQueue(options: StorageQueueOutputOptions): StorageQueueOutput; 41 | 42 | /** 43 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-service-bus-output?pivots=programming-language-javascript) 44 | */ 45 | export function serviceBusQueue(options: ServiceBusQueueOutputOptions): ServiceBusQueueOutput; 46 | 47 | /** 48 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-service-bus-output?pivots=programming-language-javascript) 49 | */ 50 | export function serviceBusTopic(options: ServiceBusTopicOutputOptions): ServiceBusTopicOutput; 51 | 52 | /** 53 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-event-hubs-output?pivots=programming-language-javascript) 54 | */ 55 | export function eventHub(options: EventHubOutputOptions): EventHubOutput; 56 | 57 | /** 58 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-event-grid-output?pivots=programming-language-javascript) 59 | */ 60 | export function eventGrid(options: EventGridOutputOptions): EventGridOutput; 61 | 62 | /** 63 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-cosmosdb-v2-output?pivots=programming-language-javascript) 64 | */ 65 | export function cosmosDB(options: CosmosDBOutputOptions): CosmosDBOutput; 66 | 67 | /** 68 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-azure-sql-output?pivots=programming-language-javascript) 69 | */ 70 | export function sql(options: SqlOutputOptions): SqlOutput; 71 | 72 | /** 73 | * [Link to docs and examples](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-azure-mysql-output?pivots=programming-language-javascript) 74 | */ 75 | export function mySql(options: MySqlOutputOptions): MySqlOutput; 76 | 77 | /** 78 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-web-pubsub-output?pivots=programming-language-javascript) 79 | */ 80 | export function webPubSub(options: WebPubSubOutputOptions): WebPubSubOutput; 81 | 82 | /** 83 | * A generic option that can be used for any output type 84 | * Use this method if your desired output type does not already have its own method 85 | */ 86 | export function generic(options: GenericOutputOptions): FunctionOutput; 87 | -------------------------------------------------------------------------------- /types/serviceBus.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type ServiceBusQueueHandler = (messages: unknown, context: InvocationContext) => FunctionResult; 8 | 9 | export interface ServiceBusQueueFunctionOptions extends ServiceBusQueueTriggerOptions, Partial { 10 | handler: ServiceBusQueueHandler; 11 | 12 | trigger?: ServiceBusQueueTrigger; 13 | } 14 | 15 | export interface ServiceBusQueueTriggerOptions { 16 | /** 17 | * An app setting (or environment variable) with the service bus connection string 18 | */ 19 | connection: string; 20 | 21 | /** 22 | * The name of the queue to monitor 23 | */ 24 | queueName: string; 25 | 26 | /** 27 | * `true` if connecting to a [session-aware](https://docs.microsoft.com/azure/service-bus-messaging/message-sessions) queue. Default is `false` 28 | */ 29 | isSessionsEnabled?: boolean; 30 | 31 | /** 32 | * Set to `many` in order to enable batching. If omitted or set to `one`, a single message is passed to the function. 33 | */ 34 | cardinality?: 'many' | 'one'; 35 | } 36 | export type ServiceBusQueueTrigger = FunctionTrigger & ServiceBusQueueTriggerOptions; 37 | 38 | export interface ServiceBusQueueOutputOptions { 39 | /** 40 | * An app setting (or environment variable) with the service bus connection string 41 | */ 42 | connection: string; 43 | 44 | /** 45 | * The name of the queue to monitor 46 | */ 47 | queueName: string; 48 | } 49 | export type ServiceBusQueueOutput = FunctionOutput & ServiceBusQueueOutputOptions; 50 | 51 | export type ServiceBusTopicHandler = (message: unknown, context: InvocationContext) => FunctionResult; 52 | 53 | export interface ServiceBusTopicFunctionOptions extends ServiceBusTopicTriggerOptions, Partial { 54 | handler: ServiceBusTopicHandler; 55 | 56 | trigger?: ServiceBusTopicTrigger; 57 | } 58 | 59 | export interface ServiceBusTopicTriggerOptions { 60 | /** 61 | * An app setting (or environment variable) with the service bus connection string 62 | */ 63 | connection: string; 64 | 65 | /** 66 | * The name of the topic to monitor 67 | */ 68 | topicName: string; 69 | 70 | /** 71 | * The name of the subscription to monitor 72 | */ 73 | subscriptionName: string; 74 | 75 | /** 76 | * `true` if connecting to a [session-aware](https://docs.microsoft.com/azure/service-bus-messaging/message-sessions) subscription. Default is `false` 77 | */ 78 | isSessionsEnabled?: boolean; 79 | 80 | /** 81 | * Set to `many` in order to enable batching. If omitted or set to `one`, a single message is passed to the function. 82 | */ 83 | cardinality?: 'many' | 'one'; 84 | } 85 | export type ServiceBusTopicTrigger = FunctionTrigger & ServiceBusTopicTriggerOptions; 86 | 87 | export interface ServiceBusTopicOutputOptions { 88 | /** 89 | * An app setting (or environment variable) with the service bus connection string 90 | */ 91 | connection: string; 92 | 93 | /** 94 | * The name of the topic to monitor 95 | */ 96 | topicName: string; 97 | } 98 | export type ServiceBusTopicOutput = FunctionOutput & ServiceBusTopicOutputOptions; 99 | -------------------------------------------------------------------------------- /types/setup.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | export interface SetupOptions { 5 | /** 6 | * Stream http requests and responses instead of loading entire body in memory. 7 | * [Learn more here](https://aka.ms/AzFuncNodeHttpStreams) 8 | */ 9 | enableHttpStream?: boolean; 10 | 11 | /** 12 | * Dictionary of Node.js worker capabilities. 13 | * This will be merged with existing capabilities specified by the Node.js worker and library. 14 | */ 15 | capabilities?: Record; 16 | } 17 | -------------------------------------------------------------------------------- /types/sql.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionInput, FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type SqlHandler = (changes: SqlChange[], context: InvocationContext) => FunctionResult; 8 | 9 | export interface SqlFunctionOptions extends SqlTriggerOptions, Partial { 10 | handler: SqlHandler; 11 | 12 | trigger?: SqlTrigger; 13 | } 14 | 15 | export interface SqlTriggerOptions { 16 | /** 17 | * The name of the table monitored by the trigger. 18 | */ 19 | tableName: string; 20 | 21 | /** 22 | * An app setting (or environment variable) with the connection string for the database containing the table monitored for changes 23 | */ 24 | connectionStringSetting: string; 25 | } 26 | export type SqlTrigger = FunctionTrigger & SqlTriggerOptions; 27 | 28 | export interface SqlChange { 29 | Item: unknown; 30 | Operation: SqlChangeOperation; 31 | } 32 | 33 | export enum SqlChangeOperation { 34 | Insert = 0, 35 | Update = 1, 36 | Delete = 2, 37 | } 38 | 39 | export interface SqlInputOptions { 40 | /** 41 | * The Transact-SQL query command or name of the stored procedure executed by the binding. 42 | */ 43 | commandText: string; 44 | 45 | /** 46 | * The command type value 47 | */ 48 | commandType: 'Text' | 'StoredProcedure'; 49 | 50 | /** 51 | * An app setting (or environment variable) with the connection string for the database against which the query or stored procedure is being executed 52 | */ 53 | connectionStringSetting: string; 54 | 55 | /** 56 | * Zero or more parameter values passed to the command during execution as a single string. 57 | * Must follow the format @param1=param1,@param2=param2. 58 | * Neither the parameter name nor the parameter value can contain a comma (,) or an equals sign (=). 59 | */ 60 | parameters?: string; 61 | } 62 | export type SqlInput = FunctionInput & SqlInputOptions; 63 | 64 | export interface SqlOutputOptions { 65 | /** 66 | * The name of the table being written to by the binding. 67 | */ 68 | commandText: string; 69 | 70 | /** 71 | * An app setting (or environment variable) with the connection string for the database to which data is being written 72 | */ 73 | connectionStringSetting: string; 74 | } 75 | export type SqlOutput = FunctionOutput & SqlOutputOptions; 76 | -------------------------------------------------------------------------------- /types/storage.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionInput, FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type StorageBlobHandler = (blob: unknown, context: InvocationContext) => FunctionResult; 8 | export type StorageQueueHandler = (queueEntry: unknown, context: InvocationContext) => FunctionResult; 9 | 10 | export interface StorageBlobFunctionOptions extends StorageBlobTriggerOptions, Partial { 11 | handler: StorageBlobHandler; 12 | 13 | trigger?: StorageBlobTrigger; 14 | } 15 | 16 | export interface StorageQueueFunctionOptions extends StorageQueueTriggerOptions, Partial { 17 | handler: StorageQueueHandler; 18 | 19 | trigger?: StorageQueueTrigger; 20 | } 21 | 22 | export interface StorageBlobOptions { 23 | /** 24 | * The path to the blob container, for example "samples-workitems/{name}" 25 | */ 26 | path: string; 27 | 28 | /** 29 | * An app setting (or environment variable) with the storage connection string to be used by this blob input or output 30 | */ 31 | connection: string; 32 | } 33 | 34 | export interface StorageQueueOptions { 35 | /** 36 | * The queue name 37 | */ 38 | queueName: string; 39 | 40 | /** 41 | * An app setting (or environment variable) with the storage connection string to be used by this queue input or output 42 | */ 43 | connection: string; 44 | } 45 | 46 | export interface StorageBlobTriggerOptions extends StorageBlobOptions { 47 | /** 48 | * The source of the triggering event. 49 | * Use `EventGrid` for an Event Grid-based blob trigger, which provides much lower latency. 50 | * The default is `LogsAndContainerScan`, which uses the standard polling mechanism to detect changes in the container. 51 | */ 52 | source?: 'EventGrid' | 'LogsAndContainerScan'; 53 | } 54 | export type StorageBlobTrigger = FunctionTrigger & StorageBlobTriggerOptions; 55 | 56 | export type StorageBlobInputOptions = StorageBlobOptions; 57 | export type StorageBlobInput = FunctionInput & StorageBlobInputOptions; 58 | 59 | export type StorageBlobOutputOptions = StorageBlobOptions; 60 | export type StorageBlobOutput = FunctionOutput & StorageBlobOutputOptions; 61 | 62 | export type StorageQueueTriggerOptions = StorageQueueOptions; 63 | export type StorageQueueTrigger = FunctionTrigger & StorageQueueTriggerOptions; 64 | 65 | export type StorageQueueOutputOptions = StorageQueueOptions; 66 | export type StorageQueueOutput = FunctionOutput & StorageQueueOutputOptions; 67 | -------------------------------------------------------------------------------- /types/table.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionInput, FunctionOutput } from './index'; 5 | 6 | export interface TableOutputOptions { 7 | /** 8 | * The table name 9 | */ 10 | tableName: string; 11 | 12 | /** 13 | * An app setting (or environment variable) with the storage connection string to be used by this table output 14 | */ 15 | connection: string; 16 | 17 | /** 18 | * The partition key of the table entity to write. 19 | */ 20 | partitionKey?: string; 21 | 22 | /** 23 | * The row key of the table entity to write. 24 | */ 25 | rowKey?: string; 26 | } 27 | export type TableOutput = FunctionOutput & TableOutputOptions; 28 | 29 | export interface TableInputOptions { 30 | /** 31 | * The table name 32 | */ 33 | tableName: string; 34 | 35 | /** 36 | * An app setting (or environment variable) with the storage connection string to be used by this table input 37 | */ 38 | connection: string; 39 | 40 | /** 41 | * The partition key of the table entity to read. 42 | */ 43 | partitionKey?: string; 44 | 45 | /** 46 | * The row key of the table entity to read. Can't be used with `take` or `filter`. 47 | */ 48 | rowKey?: string; 49 | 50 | /** 51 | * The maximum number of entities to return. Can't be used with `rowKey` 52 | */ 53 | take?: number; 54 | 55 | /** 56 | * An OData filter expression for the entities to return from the table. Can't be used with `rowKey`. 57 | */ 58 | filter?: string; 59 | } 60 | export type TableInput = FunctionInput & TableInputOptions; 61 | -------------------------------------------------------------------------------- /types/timer.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionOptions, FunctionResult, FunctionTrigger, RetryOptions } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type TimerHandler = (myTimer: Timer, context: InvocationContext) => FunctionResult; 8 | 9 | export interface TimerFunctionOptions extends TimerTriggerOptions, Partial { 10 | handler: TimerHandler; 11 | 12 | trigger?: TimerTrigger; 13 | 14 | /** 15 | * An optional retry policy to rerun a failed execution until either successful completion occurs or the maximum number of retries is reached. 16 | * Learn more [here](https://learn.microsoft.com/azure/azure-functions/functions-bindings-error-pages) 17 | */ 18 | retry?: RetryOptions; 19 | } 20 | 21 | export interface TimerTriggerOptions { 22 | /** 23 | * A [cron expression](https://docs.microsoft.com/azure/azure-functions/functions-bindings-timer?pivots=programming-language-javascript#ncrontab-expressions) of the format '{second} {minute} {hour} {day} {month} {day of week}' to specify the schedule 24 | */ 25 | schedule: string; 26 | 27 | /** 28 | * If `true`, the function is invoked when the runtime starts. 29 | * For example, the runtime starts when the function app wakes up after going idle due to inactivity, when the function app restarts due to function changes, and when the function app scales out. 30 | * _Use with caution_. runOnStartup should rarely if ever be set to `true`, especially in production. 31 | */ 32 | runOnStartup?: boolean; 33 | 34 | /** 35 | * When true, schedule will be persisted to aid in maintaining the correct schedule even through restarts. Defaults to true for schedules with interval >= 1 minute 36 | */ 37 | useMonitor?: boolean; 38 | } 39 | 40 | export type TimerTrigger = FunctionTrigger & TimerTriggerOptions; 41 | 42 | /** 43 | * Timer schedule information. Provided to your function when using a timer binding. 44 | */ 45 | export interface Timer { 46 | /** 47 | * Whether this timer invocation is due to a missed schedule occurrence. 48 | */ 49 | isPastDue: boolean; 50 | schedule: { 51 | /** 52 | * Whether intervals between invocations should account for DST. 53 | */ 54 | adjustForDST: boolean; 55 | }; 56 | scheduleStatus: { 57 | /** 58 | * The last recorded schedule occurrence. Date ISO string. 59 | */ 60 | last: string; 61 | /** 62 | * The expected next schedule occurrence. Date ISO string. 63 | */ 64 | next: string; 65 | /** 66 | * The last time this record was updated. This is used to re-calculate `next` with the current schedule after a host restart. Date ISO string. 67 | */ 68 | lastUpdated: string; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /types/trigger.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { CosmosDBTrigger, CosmosDBTriggerOptions } from './cosmosDB'; 5 | import { EventGridTrigger, EventGridTriggerOptions } from './eventGrid'; 6 | import { EventHubTrigger, EventHubTriggerOptions } from './eventHub'; 7 | import { GenericTriggerOptions } from './generic'; 8 | import { HttpTrigger, HttpTriggerOptions } from './http'; 9 | import { FunctionTrigger } from './index'; 10 | import { MySqlTrigger, MySqlTriggerOptions } from './mySql'; 11 | import { 12 | ServiceBusQueueTrigger, 13 | ServiceBusQueueTriggerOptions, 14 | ServiceBusTopicTrigger, 15 | ServiceBusTopicTriggerOptions, 16 | } from './serviceBus'; 17 | import { SqlTrigger, SqlTriggerOptions } from './sql'; 18 | import { 19 | StorageBlobTrigger, 20 | StorageBlobTriggerOptions, 21 | StorageQueueTrigger, 22 | StorageQueueTriggerOptions, 23 | } from './storage'; 24 | import { TimerTrigger, TimerTriggerOptions } from './timer'; 25 | import { WarmupTrigger, WarmupTriggerOptions } from './warmup'; 26 | import { WebPubSubTrigger, WebPubSubTriggerOptions } from './webpubsub'; 27 | 28 | /** 29 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?&pivots=programming-language-javascript) 30 | */ 31 | export function http(options: HttpTriggerOptions): HttpTrigger; 32 | 33 | /** 34 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-timer?pivots=programming-language-javascript) 35 | */ 36 | export function timer(options: TimerTriggerOptions): TimerTrigger; 37 | 38 | /** 39 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-blob-trigger?pivots=programming-language-javascript) 40 | */ 41 | export function storageBlob(options: StorageBlobTriggerOptions): StorageBlobTrigger; 42 | 43 | /** 44 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-queue-trigger?pivots=programming-language-javascript) 45 | */ 46 | export function storageQueue(options: StorageQueueTriggerOptions): StorageQueueTrigger; 47 | 48 | /** 49 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-service-bus-trigger?pivots=programming-language-javascript) 50 | */ 51 | export function serviceBusQueue(options: ServiceBusQueueTriggerOptions): ServiceBusQueueTrigger; 52 | 53 | /** 54 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-service-bus-trigger?pivots=programming-language-javascript) 55 | */ 56 | export function serviceBusTopic(options: ServiceBusTopicTriggerOptions): ServiceBusTopicTrigger; 57 | 58 | /** 59 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-event-hubs-trigger?pivots=programming-language-javascript) 60 | */ 61 | export function eventHub(options: EventHubTriggerOptions): EventHubTrigger; 62 | 63 | /** 64 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-event-grid-trigger?pivots=programming-language-javascript) 65 | */ 66 | export function eventGrid(options: EventGridTriggerOptions): EventGridTrigger; 67 | 68 | /** 69 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-cosmosdb-v2-trigger?pivots=programming-language-javascript) 70 | */ 71 | export function cosmosDB(options: CosmosDBTriggerOptions): CosmosDBTrigger; 72 | 73 | /** 74 | * [Link to docs and examples](https://learn.microsoft.com/azure/azure-functions/functions-bindings-warmup?tabs=isolated-process&pivots=programming-language-javascript) 75 | */ 76 | export function warmup(options: WarmupTriggerOptions): WarmupTrigger; 77 | 78 | /** 79 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-azure-sql-trigger?pivots=programming-language-javascript) 80 | */ 81 | export function sql(options: SqlTriggerOptions): SqlTrigger; 82 | 83 | /** 84 | * [Link to docs and examples](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-azure-mysql-trigger?pivots=programming-language-javascript) 85 | */ 86 | export function mySql(options: MySqlTriggerOptions): MySqlTrigger; 87 | 88 | /** 89 | * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-web-pubsub-trigger?pivots=programming-language-javascript) 90 | */ 91 | export function webPubSub(options: WebPubSubTriggerOptions): WebPubSubTrigger; 92 | 93 | /** 94 | * A generic option that can be used for any trigger type 95 | * Use this method if your desired trigger type does not already have its own method 96 | */ 97 | export function generic(options: GenericTriggerOptions): FunctionTrigger; 98 | -------------------------------------------------------------------------------- /types/warmup.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionOptions, FunctionResult, FunctionTrigger } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export interface WarmupContext {} 8 | export type WarmupHandler = (warmupContext: WarmupContext, context: InvocationContext) => FunctionResult; 9 | 10 | export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial { 11 | handler: WarmupHandler; 12 | 13 | trigger?: WarmupTrigger; 14 | } 15 | 16 | export interface WarmupTriggerOptions {} 17 | export type WarmupTrigger = FunctionTrigger & WarmupTriggerOptions; 18 | -------------------------------------------------------------------------------- /types/webpubsub.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { FunctionInput, FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; 5 | import { InvocationContext } from './InvocationContext'; 6 | 7 | export type WebPubSubHandler = (message: unknown, context: InvocationContext) => FunctionResult; 8 | 9 | export interface WebPubSubFunctionOptions extends WebPubSubTriggerOptions, Partial { 10 | handler: WebPubSubHandler; 11 | 12 | trigger?: WebPubSubTrigger; 13 | } 14 | 15 | export interface WebPubSubTriggerOptions { 16 | /** 17 | * Required - The variable name used in function code for the parameter that receives the event data 18 | */ 19 | name: string; 20 | 21 | /** 22 | * Required - The name of the hub to which the function is bound 23 | */ 24 | hub: string; 25 | 26 | /** 27 | * Required - The type of event to which the function should respond 28 | * Must be either 'user' or 'system' 29 | */ 30 | eventType: 'user' | 'system'; 31 | 32 | /** 33 | * Required - The name of the event to which the function should respond 34 | * For system event type: 'connect', 'connected', or 'disconnected' 35 | * For user-defined subprotocols: 'message' 36 | * For system supported subprotocol json.webpubsub.azure.v1: user-defined event name 37 | */ 38 | eventName: string; 39 | 40 | /** 41 | * Optional - Specifies which client protocol can trigger the Web PubSub trigger functions 42 | * Default is 'all' 43 | */ 44 | clientProtocols?: 'all' | 'webPubSub' | 'mqtt'; 45 | 46 | /** 47 | * Optional - The name of an app setting or setting collection that specifies the upstream Azure Web PubSub service 48 | * Used for signature validation 49 | * Defaults to "WebPubSubConnectionString" if not specified 50 | * Set to null to disable validation 51 | */ 52 | connection?: string | null; 53 | } 54 | 55 | export type WebPubSubTrigger = FunctionTrigger & WebPubSubTriggerOptions; 56 | 57 | export interface WebPubSubConnectionInputOptions { 58 | /** 59 | * Required - Variable name used in function code for input connection binding object. 60 | */ 61 | name: string; 62 | 63 | /** 64 | * Required - The name of the Web PubSub hub for the function to be triggered. 65 | * Can be set in the attribute (higher priority) or in app settings as a global value. 66 | */ 67 | hub: string; 68 | 69 | /** 70 | * Optional - The value of the user identifier claim to be set in the access key token. 71 | */ 72 | userId?: string; 73 | 74 | /** 75 | * Optional - The client protocol type. 76 | * Valid values are 'default' and 'mqtt'. 77 | * For MQTT clients, you must set it to 'mqtt'. 78 | * For other clients, you can omit the property or set it to 'default'. 79 | */ 80 | clientProtocol?: 'default' | 'mqtt'; 81 | 82 | /** 83 | * Optional - The name of the app setting that contains the Web PubSub Service connection string. 84 | * Defaults to "WebPubSubConnectionString". 85 | */ 86 | connection?: string; 87 | } 88 | export type WebPubSubConnectionInput = FunctionInput & WebPubSubConnectionInputOptions; 89 | 90 | export interface WebPubSubContextInputOptions { 91 | /** 92 | * Required - Variable name used in function code for input Web PubSub request. 93 | */ 94 | name: string; 95 | 96 | /** 97 | * Optional - The name of an app settings or setting collection that specifies the upstream Azure Web PubSub service. 98 | * The value is used for Abuse Protection and Signature validation. 99 | * The value is auto resolved with "WebPubSubConnectionString" by default. 100 | * Null means the validation isn't needed and always succeeds. 101 | */ 102 | connection?: string; 103 | } 104 | export type WebPubSubContextInput = FunctionInput & WebPubSubContextInputOptions; 105 | 106 | export interface WebPubSubOutputOptions { 107 | /** 108 | * Required - Variable name used in function code for output binding object. 109 | */ 110 | name: string; 111 | 112 | /** 113 | * Required - The name of the hub to which the function is bound. 114 | * Can be set in the attribute (higher priority) or in app settings as a global value. 115 | */ 116 | hub: string; 117 | 118 | /** 119 | * Optional - The name of the app setting that contains the Web PubSub Service connection string. 120 | * Defaults to "WebPubSubConnectionString". 121 | */ 122 | connection?: string; 123 | } 124 | export type WebPubSubOutput = FunctionOutput & WebPubSubOutputOptions; 125 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 2 | const ESLintPlugin = require('eslint-webpack-plugin'); 3 | 4 | module.exports = (_env, argv) => { 5 | const isDevMode = argv.mode === 'development'; 6 | return { 7 | entry: './src/index.ts', 8 | target: 'node', 9 | node: { 10 | __dirname: false, 11 | }, 12 | devtool: 'source-map', 13 | externals: [/^[^\.]+/], 14 | module: { 15 | parser: { 16 | javascript: { 17 | commonjsMagicComments: true, 18 | }, 19 | }, 20 | rules: [ 21 | { 22 | test: /\.tsx?$/, 23 | loader: 'ts-loader', 24 | }, 25 | ], 26 | }, 27 | resolve: { 28 | extensions: ['.ts', '.tsx', '.js'], 29 | }, 30 | output: { 31 | path: `${__dirname}/dist/`, 32 | filename: isDevMode ? 'azure-functions.js' : 'azure-functions.min.js', 33 | libraryTarget: 'commonjs2', 34 | }, 35 | plugins: [ 36 | new ForkTsCheckerWebpackPlugin({}), 37 | new ESLintPlugin({ 38 | files: ['src/**/*.ts', 'test/**/*.ts'], 39 | fix: isDevMode, 40 | }), 41 | ], 42 | }; 43 | }; 44 | --------------------------------------------------------------------------------