diff --git a/.gitignore b/.gitignore index 5ec18c6..4ac8545 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ azure-functions-language-worker-protobuf/* dist pkg -types/*.tgz +*.tgz Azure.Functions.Cli Azure.Functions.Cli.zip diff --git a/Worker.nuspec b/Worker.nuspec deleted file mode 100644 index 7f0eb59..0000000 --- a/Worker.nuspec +++ /dev/null @@ -1,18 +0,0 @@ - - - - Microsoft.Azure.Functions.NodeJsWorker - 3.4.0$prereleaseSuffix$ - Microsoft - Microsoft - false - Microsoft Azure Functions NodeJs Worker - © .NET Foundation. All rights reserved. - - - - - - - - \ No newline at end of file diff --git a/azure-functions-language-worker-protobuf/.gitignore b/azure-functions-language-worker-protobuf/.gitignore deleted file mode 100644 index 940794e..0000000 --- a/azure-functions-language-worker-protobuf/.gitignore +++ /dev/null @@ -1,288 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs diff --git a/azure-functions-language-worker-protobuf/LICENSE b/azure-functions-language-worker-protobuf/LICENSE deleted file mode 100644 index 2107107..0000000 --- a/azure-functions-language-worker-protobuf/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE diff --git a/azure-functions-language-worker-protobuf/README.md b/azure-functions-language-worker-protobuf/README.md deleted file mode 100644 index 14c406e..0000000 --- a/azure-functions-language-worker-protobuf/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# Azure Functions Language Worker Protobuf - -This repository contains the protobuf definition file which defines the gRPC service which is used between the [Azure Functions Host](https://github.com/Azure/azure-functions-host) and the Azure Functions language workers. This repo is shared across many repos in many languages (for each worker) by using git commands. - -To use this repo in Azure Functions language workers, follow steps below to add this repo as a subtree (*Adding This Repo*). If this repo is already embedded in a language worker repo, follow the steps to update the consumed file (*Pulling Updates*). - -Learn more about Azure Function's projects on the [meta](https://github.com/azure/azure-functions) repo. - -## Adding This Repo - -From within the Azure Functions language worker repo: -1. Define remote branch for cleaner git commands - - `git remote add proto-file https://github.com/azure/azure-functions-language-worker-protobuf.git` - - `git fetch proto-file` -2. Index contents of azure-functions-worker-protobuf to language worker repo - - `git read-tree --prefix= -u proto-file/` -3. Add new path in language worker repo to .gitignore file - - In .gitignore, add path in language worker repo -4. Finalize with commit - - `git commit -m "Added subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Branch: . Commit: "` - - `git push` - -## Pulling Updates - -From within the Azure Functions language worker repo: -1. Define remote branch for cleaner git commands - - `git remote add proto-file https://github.com/azure/azure-functions-language-worker-protobuf.git` - - `git fetch proto-file` -2. Pull a specific release tag - - `git fetch proto-file refs/tags/` - - Example: `git fetch proto-file refs/tags/v1.1.0-protofile` -3. Merge updates - - Merge with an explicit path to subtree: `git merge -X subtree= --squash --allow-unrelated-histories --strategy-option theirs` - - Example: `git merge -X subtree=src/WebJobs.Script.Grpc/azure-functions-language-worker-protobuf --squash v1.1.0-protofile --allow-unrelated-histories --strategy-option theirs` -4. Finalize with commit - - `git commit -m "Updated subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Tag: . Commit: "` - - `git push` - -## Releasing a Language Worker Protobuf version - -1. Draft a release in the GitHub UI - - Be sure to include details of the release -2. Create a release version, following semantic versioning guidelines ([semver.org](https://semver.org/)) -3. Tag the version with the pattern: `v..

-protofile` (example: `v1.1.0-protofile`) -3. Merge `dev` to `master` - -## Consuming FunctionRPC.proto -*Note: Update versionNumber before running following commands* - -## CSharp -``` -set NUGET_PATH="%UserProfile%\.nuget\packages" -set GRPC_TOOLS_PATH=%NUGET_PATH%\grpc.tools\\tools\windows_x86 -set PROTO_PATH=.\azure-functions-language-worker-protobuf\src\proto -set PROTO=.\azure-functions-language-worker-protobuf\src\proto\FunctionRpc.proto -set PROTOBUF_TOOLS=%NUGET_PATH%\google.protobuf.tools\\tools -set MSGDIR=.\Messages - -if exist %MSGDIR% rmdir /s /q %MSGDIR% -mkdir %MSGDIR% - -set OUTDIR=%MSGDIR%\DotNet -mkdir %OUTDIR% -%GRPC_TOOLS_PATH%\protoc.exe %PROTO% --csharp_out %OUTDIR% --grpc_out=%OUTDIR% --plugin=protoc-gen-grpc=%GRPC_TOOLS_PATH%\grpc_csharp_plugin.exe --proto_path=%PROTO_PATH% --proto_path=%PROTOBUF_TOOLS% -``` -## JavaScript -In package.json, add to the build script the following commands to build .js files and to build .ts files. Use and install npm package `protobufjs`. - -Generate JavaScript files: -``` -pbjs -t json-module -w commonjs -o azure-functions-language-worker-protobuf/src/rpc.js azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto -``` -Generate TypeScript files: -``` -pbjs -t static-module azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto -o azure-functions-language-worker-protobuf/src/rpc_static.js && pbts -o azure-functions-language-worker-protobuf/src/rpc.d.ts azure-functions-language-worker-protobuf/src/rpc_static.js -``` - -## Java -Maven plugin : [protobuf-maven-plugin](https://www.xolstice.org/protobuf-maven-plugin/) -In pom.xml add following under configuration for this plugin -${basedir}//azure-functions-language-worker-protobuf/src/proto - -## Python -``` -python -m pip install -e .[dev] -U -python setup.py build -``` - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.microsoft.com. - -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -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. diff --git a/azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto b/azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto deleted file mode 100644 index 04a6c78..0000000 --- a/azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto +++ /dev/null @@ -1,613 +0,0 @@ -syntax = "proto3"; -// protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 - -option java_multiple_files = true; -option java_package = "com.microsoft.azure.functions.rpc.messages"; -option java_outer_classname = "FunctionProto"; -option csharp_namespace = "Microsoft.Azure.WebJobs.Script.Grpc.Messages"; -option go_package ="github.com/Azure/azure-functions-go-worker/internal/rpc"; - -package AzureFunctionsRpcMessages; - -import "google/protobuf/duration.proto"; -import "identity/ClaimsIdentityRpc.proto"; -import "shared/NullableTypes.proto"; - -// Interface exported by the server. -service FunctionRpc { - rpc EventStream (stream StreamingMessage) returns (stream StreamingMessage) {} -} - -message StreamingMessage { - // Used to identify message between host and worker - string request_id = 1; - - // Payload of the message - oneof content { - - // Worker initiates stream - StartStream start_stream = 20; - - // Host sends capabilities/init data to worker - WorkerInitRequest worker_init_request = 17; - // Worker responds after initializing with its capabilities & status - WorkerInitResponse worker_init_response = 16; - - // Worker periodically sends empty heartbeat message to host - WorkerHeartbeat worker_heartbeat = 15; - - // Host sends terminate message to worker. - // Worker terminates if it can, otherwise host terminates after a grace period - WorkerTerminate worker_terminate = 14; - - // Add any worker relevant status to response - WorkerStatusRequest worker_status_request = 12; - WorkerStatusResponse worker_status_response = 13; - - // On file change event, host sends notification to worker - FileChangeEventRequest file_change_event_request = 6; - - // Worker requests a desired action (restart worker, reload function) - WorkerActionResponse worker_action_response = 7; - - // Host sends required metadata to worker to load function - FunctionLoadRequest function_load_request = 8; - // Worker responds after loading with the load result - FunctionLoadResponse function_load_response = 9; - - // Host requests a given invocation - InvocationRequest invocation_request = 4; - - // Worker responds to a given invocation - InvocationResponse invocation_response = 5; - - // Host sends cancel message to attempt to cancel an invocation. - // If an invocation is cancelled, host will receive an invocation response with status cancelled. - InvocationCancel invocation_cancel = 21; - - // Worker logs a message back to the host - RpcLog rpc_log = 2; - - FunctionEnvironmentReloadRequest function_environment_reload_request = 25; - - FunctionEnvironmentReloadResponse function_environment_reload_response = 26; - - // Ask the worker to close any open shared memory resources for a given invocation - CloseSharedMemoryResourcesRequest close_shared_memory_resources_request = 27; - CloseSharedMemoryResourcesResponse close_shared_memory_resources_response = 28; - - // Worker indexing message types - FunctionsMetadataRequest functions_metadata_request = 29; - FunctionMetadataResponse function_metadata_response = 30; - - // Host sends required metadata to worker to load functions - FunctionLoadRequestCollection function_load_request_collection = 31; - - // Host gets the list of function load responses - FunctionLoadResponseCollection function_load_response_collection = 32; - } -} - -// Process.Start required info -// connection details -// protocol type -// protocol version - -// Worker sends the host information identifying itself -message StartStream { - // id of the worker - string worker_id = 2; -} - -// Host requests the worker to initialize itself -message WorkerInitRequest { - // version of the host sending init request - string host_version = 1; - - // A map of host supported features/capabilities - map capabilities = 2; - - // inform worker of supported categories and their levels - // i.e. Worker = Verbose, Function.MyFunc = None - map log_categories = 3; - - // Full path of worker.config.json location - string worker_directory = 4; - - // base directory for function app - string function_app_directory = 5; -} - -// Worker responds with the result of initializing itself -message WorkerInitResponse { - // Version of worker - string worker_version = 1; - // A map of worker supported features/capabilities - map capabilities = 2; - - // Status of the response - StatusResult result = 3; -} - -// Used by the host to determine success/failure/cancellation -message StatusResult { - // Indicates Failure/Success/Cancelled - enum Status { - Failure = 0; - Success = 1; - Cancelled = 2; - } - // Status for the given result - Status status = 4; - - // Specific message about the result - string result = 1; - - // Exception message (if exists) for the status - RpcException exception = 2; - - // Captured logs or relevant details can use the logs property - repeated RpcLog logs = 3; -} - -// TODO: investigate grpc heartbeat - don't limit to grpc implemention - -// Message is empty by design - Will add more fields in future if needed -message WorkerHeartbeat {} - -// Warning before killing the process after grace_period -// Worker self terminates ..no response on this -message WorkerTerminate { - google.protobuf.Duration grace_period = 1; -} - -// Host notifies worker of file content change -message FileChangeEventRequest { - // Types of File change operations (See link for more info: https://msdn.microsoft.com/en-us/library/t6xf43e0(v=vs.110).aspx) - enum Type { - Unknown = 0; - Created = 1; - Deleted = 2; - Changed = 4; - Renamed = 8; - All = 15; - } - - // type for this event - Type type = 1; - - // full file path for the file change notification - string full_path = 2; - - // Name of the function affected - string name = 3; -} - -// Indicates whether worker reloaded successfully or needs a restart -message WorkerActionResponse { - // indicates whether a restart is needed, or reload succesfully - enum Action { - Restart = 0; - Reload = 1; - } - - // action for this response - Action action = 1; - - // text reason for the response - string reason = 2; -} - -// NOT USED -message WorkerStatusRequest{ -} - -// NOT USED -message WorkerStatusResponse { -} - -message FunctionEnvironmentReloadRequest { - // Environment variables from the current process - map environment_variables = 1; - // Current directory of function app - string function_app_directory = 2; -} - -message FunctionEnvironmentReloadResponse { - // Status of the response - StatusResult result = 3; -} - -// Tell the out-of-proc worker to close any shared memory maps it allocated for given invocation -message CloseSharedMemoryResourcesRequest { - repeated string map_names = 1; -} - -// Response from the worker indicating which of the shared memory maps have been successfully closed and which have not been closed -// The key (string) is the map name and the value (bool) is true if it was closed, false if not -message CloseSharedMemoryResourcesResponse { - map close_map_results = 1; -} - -// Host tells the worker to load a list of Functions -message FunctionLoadRequestCollection { - repeated FunctionLoadRequest function_load_requests = 1; -} - -// Host gets the list of function load responses -message FunctionLoadResponseCollection { - repeated FunctionLoadResponse function_load_responses = 1; -} - -// Load request of a single Function -message FunctionLoadRequest { - // unique function identifier (avoid name collisions, facilitate reload case) - string function_id = 1; - - // Metadata for the request - RpcFunctionMetadata metadata = 2; - - // A flag indicating if managed dependency is enabled or not - bool managed_dependency_enabled = 3; -} - -// Worker tells host result of reload -message FunctionLoadResponse { - // unique function identifier - string function_id = 1; - - // Result of load operation - StatusResult result = 2; - // TODO: return type expected? - - // Result of load operation - bool is_dependency_downloaded = 3; -} - -// Information on how a Function should be loaded and its bindings -message RpcFunctionMetadata { - // TODO: do we want the host's name - the language worker might do a better job of assignment than the host - string name = 4; - - // base directory for the Function - string directory = 1; - - // Script file specified - string script_file = 2; - - // Entry point specified - string entry_point = 3; - - // Bindings info - map bindings = 6; - - // Is set to true for proxy - bool is_proxy = 7; - - // Function indexing status - StatusResult status = 8; - - // Function language - string language = 9; - - // Raw binding info - repeated string raw_bindings = 10; - - // unique function identifier (avoid name collisions, facilitate reload case) - string function_id = 13; - - // A flag indicating if managed dependency is enabled or not - bool managed_dependency_enabled = 14; -} - -// Host tells worker it is ready to receive metadata -message FunctionsMetadataRequest { - // base directory for function app - string function_app_directory = 1; -} - -// Worker sends function metadata back to host -message FunctionMetadataResponse { - // list of function indexing responses - repeated RpcFunctionMetadata function_metadata_results = 1; - - // status of overall metadata request - StatusResult result = 2; - - // if set to true then host will perform indexing - bool use_default_metadata_indexing = 3; -} - -// Host requests worker to invoke a Function -message InvocationRequest { - // Unique id for each invocation - string invocation_id = 1; - - // Unique id for each Function - string function_id = 2; - - // Input bindings (include trigger) - repeated ParameterBinding input_data = 3; - - // binding metadata from trigger - map trigger_metadata = 4; - - // Populates activityId, tracestate and tags from host - RpcTraceContext trace_context = 5; - - // Current retry context - RetryContext retry_context = 6; -} - -// Host sends ActivityId, traceStateString and Tags from host -message RpcTraceContext { - // This corresponds to Activity.Current?.Id - string trace_parent = 1; - - // This corresponds to Activity.Current?.TraceStateString - string trace_state = 2; - - // This corresponds to Activity.Current?.Tags - map attributes = 3; -} - -// Host sends retry context for a function invocation -message RetryContext { - // Current retry count - int32 retry_count = 1; - - // Max retry count - int32 max_retry_count = 2; - - // Exception that caused the retry - RpcException exception = 3; -} - -// Host requests worker to cancel invocation -message InvocationCancel { - // Unique id for invocation - string invocation_id = 2; - - // Time period before force shutdown - google.protobuf.Duration grace_period = 1; // could also use absolute time -} - -// Worker responds with status of Invocation -message InvocationResponse { - // Unique id for invocation - string invocation_id = 1; - - // Output binding data - repeated ParameterBinding output_data = 2; - - // data returned from Function (for $return and triggers with return support) - TypedData return_value = 4; - - // Status of the invocation (success/failure/canceled) - StatusResult result = 3; -} - -// Used to encapsulate data which could be a variety of types -message TypedData { - oneof data { - string string = 1; - string json = 2; - bytes bytes = 3; - bytes stream = 4; - RpcHttp http = 5; - sint64 int = 6; - double double = 7; - CollectionBytes collection_bytes = 8; - CollectionString collection_string = 9; - CollectionDouble collection_double = 10; - CollectionSInt64 collection_sint64 = 11; - } -} - -// Specify which type of data is contained in the shared memory region being read -enum RpcDataType { - unknown = 0; - string = 1; - json = 2; - bytes = 3; - stream = 4; - http = 5; - int = 6; - double = 7; - collection_bytes = 8; - collection_string = 9; - collection_double = 10; - collection_sint64 = 11; -} - -// Used to provide metadata about shared memory region to read data from -message RpcSharedMemory { - // Name of the shared memory map containing data - string name = 1; - // Offset in the shared memory map to start reading data from - int64 offset = 2; - // Number of bytes to read (starting from the offset) - int64 count = 3; - // Final type to which the read data (in bytes) is to be interpreted as - RpcDataType type = 4; -} - -// Used to encapsulate collection string -message CollectionString { - repeated string string = 1; -} - -// Used to encapsulate collection bytes -message CollectionBytes { - repeated bytes bytes = 1; -} - -// Used to encapsulate collection double -message CollectionDouble { - repeated double double = 1; -} - -// Used to encapsulate collection sint64 -message CollectionSInt64 { - repeated sint64 sint64 = 1; -} - -// Used to describe a given binding on invocation -message ParameterBinding { - // Name for the binding - string name = 1; - - oneof rpc_data { - // Data for the binding - TypedData data = 2; - - // Metadata about the shared memory region to read data from - RpcSharedMemory rpc_shared_memory = 3; - } -} - -// Used to describe a given binding on load -message BindingInfo { - // Indicates whether it is an input or output binding (or a fancy inout binding) - enum Direction { - in = 0; - out = 1; - inout = 2; - } - - // Indicates the type of the data for the binding - enum DataType { - undefined = 0; - string = 1; - binary = 2; - stream = 3; - } - - // Type of binding (e.g. HttpTrigger) - string type = 2; - - // Direction of the given binding - Direction direction = 3; - - DataType data_type = 4; -} - -// Used to send logs back to the Host -message RpcLog { - // Matching ILogger semantics - // https://github.com/aspnet/Logging/blob/9506ccc3f3491488fe88010ef8b9eb64594abf95/src/Microsoft.Extensions.Logging/Logger.cs - // Level for the Log - enum Level { - Trace = 0; - Debug = 1; - Information = 2; - Warning = 3; - Error = 4; - Critical = 5; - None = 6; - } - - // Category of the log. Defaults to User if not specified. - enum RpcLogCategory { - User = 0; - System = 1; - CustomMetric = 2; - } - - // Unique id for invocation (if exists) - string invocation_id = 1; - - // TOD: This should be an enum - // Category for the log (startup, load, invocation, etc.) - string category = 2; - - // Level for the given log message - Level level = 3; - - // Message for the given log - string message = 4; - - // Id for the even associated with this log (if exists) - string event_id = 5; - - // Exception (if exists) - RpcException exception = 6; - - // json serialized property bag - string properties = 7; - - // Category of the log. Either user(default), system, or custom metric. - RpcLogCategory log_category = 8; - - // strongly-typed (ish) property bag - map propertiesMap = 9; -} - -// Encapsulates an Exception -message RpcException { - // Source of the exception - string source = 3; - - // Stack trace for the exception - string stack_trace = 1; - - // Textual message describing the exception - string message = 2; -} - -// Http cookie type. Note that only name and value are used for Http requests -message RpcHttpCookie { - // Enum that lets servers require that a cookie shouldn't be sent with cross-site requests - enum SameSite { - None = 0; - Lax = 1; - Strict = 2; - ExplicitNone = 3; - } - - // Cookie name - string name = 1; - - // Cookie value - string value = 2; - - // Specifies allowed hosts to receive the cookie - NullableString domain = 3; - - // Specifies URL path that must exist in the requested URL - NullableString path = 4; - - // Sets the cookie to expire at a specific date instead of when the client closes. - // It is generally recommended that you use "Max-Age" over "Expires". - NullableTimestamp expires = 5; - - // Sets the cookie to only be sent with an encrypted request - NullableBool secure = 6; - - // Sets the cookie to be inaccessible to JavaScript's Document.cookie API - NullableBool http_only = 7; - - // Allows servers to assert that a cookie ought not to be sent along with cross-site requests - SameSite same_site = 8; - - // Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. - NullableDouble max_age = 9; -} - -// TODO - solidify this or remove it -message RpcHttp { - string method = 1; - string url = 2; - map headers = 3; - TypedData body = 4; - map params = 10; - string status_code = 12; - map query = 15; - bool enable_content_negotiation= 16; - TypedData rawBody = 17; - repeated RpcClaimsIdentity identities = 18; - repeated RpcHttpCookie cookies = 19; - map nullable_headers = 20; - map nullable_params = 21; - map nullable_query = 22; -} diff --git a/azure-functions-language-worker-protobuf/src/proto/identity/ClaimsIdentityRpc.proto b/azure-functions-language-worker-protobuf/src/proto/identity/ClaimsIdentityRpc.proto deleted file mode 100644 index c3945bb..0000000 --- a/azure-functions-language-worker-protobuf/src/proto/identity/ClaimsIdentityRpc.proto +++ /dev/null @@ -1,26 +0,0 @@ -syntax = "proto3"; -// protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 - -option java_package = "com.microsoft.azure.functions.rpc.messages"; - -import "shared/NullableTypes.proto"; - -// Light-weight representation of a .NET System.Security.Claims.ClaimsIdentity object. -// This is the same serialization as found in EasyAuth, and needs to be kept in sync with -// its ClaimsIdentitySlim definition, as seen in the WebJobs extension: -// https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimsIdentitySlim.cs -message RpcClaimsIdentity { - NullableString authentication_type = 1; - NullableString name_claim_type = 2; - NullableString role_claim_type = 3; - repeated RpcClaim claims = 4; -} - -// Light-weight representation of a .NET System.Security.Claims.Claim object. -// This is the same serialization as found in EasyAuth, and needs to be kept in sync with -// its ClaimSlim definition, as seen in the WebJobs extension: -// https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimSlim.cs -message RpcClaim { - string value = 1; - string type = 2; -} diff --git a/azure-functions-language-worker-protobuf/src/proto/shared/NullableTypes.proto b/azure-functions-language-worker-protobuf/src/proto/shared/NullableTypes.proto deleted file mode 100644 index 4fb4765..0000000 --- a/azure-functions-language-worker-protobuf/src/proto/shared/NullableTypes.proto +++ /dev/null @@ -1,30 +0,0 @@ -syntax = "proto3"; -// protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 - -option java_package = "com.microsoft.azure.functions.rpc.messages"; - -import "google/protobuf/timestamp.proto"; - -message NullableString { - oneof string { - string value = 1; - } -} - -message NullableDouble { - oneof double { - double value = 1; - } -} - -message NullableBool { - oneof bool { - bool value = 1; - } -} - -message NullableTimestamp { - oneof timestamp { - google.protobuf.Timestamp value = 1; - } -} diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml index 2a5fc1a..8f87761 100644 --- a/azure-pipelines/build.yml +++ b/azure-pipelines/build.yml @@ -1,8 +1,3 @@ -parameters: -- name: IsPrerelease - type: boolean - default: true - pr: branches: include: @@ -29,77 +24,36 @@ jobs: - script: npm run webpack displayName: 'npm run webpack' - task: CopyFiles@2 - displayName: 'Copy worker files to staging' + displayName: 'Copy files to staging' inputs: sourceFolder: '$(Build.SourcesDirectory)' contents: | - dist/src/nodejsWorker.js - dist/src/worker-bundle.js + dist/src/index-bundle.js + types/index.d.ts LICENSE NOTICE.html package.json - worker.config.json - targetFolder: '$(Build.ArtifactStagingDirectory)/worker' + README.md + targetFolder: '$(Build.ArtifactStagingDirectory)' cleanTargetFolder: true - script: npm prune --production displayName: 'npm prune --production' # so that only production dependencies are included in SBOM - task: ManifestGeneratorTask@0 - displayName: 'Generate SBOM for worker' + displayName: 'Generate SBOM' inputs: - BuildDropPath: '$(Build.ArtifactStagingDirectory)/worker' + BuildDropPath: '$(Build.ArtifactStagingDirectory)' # The list of components can't be determined from the webpacked file in the staging dir, so reference the original node_modules folder BuildComponentPath: '$(Build.SourcesDirectory)/node_modules' - PackageName: 'Azure Functions Node.js Worker' - - task: NuGetCommand@2 - displayName: 'NuGet pack worker' - inputs: - command: pack - packagesToPack: '$(Build.SourcesDirectory)/Worker.nuspec' - packDestination: '$(Build.ArtifactStagingDirectory)/worker' - basePath: '$(Build.ArtifactStagingDirectory)/worker' - ${{ if eq(parameters.IsPrerelease, true) }}: - buildProperties: 'prereleaseSuffix=-alpha.$(Build.BuildNumber)' - - task: NuGetCommand@2 - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/v3.x'), eq(variables['UPLOADPACKAGETOPRERELEASEFEED'], true)) - inputs: - command: 'push' - packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg' - nuGetFeedType: 'internal' - publishVstsFeed: 'e6a70c92-4128-439f-8012-382fe78d6396/f37f760c-aebd-443e-9714-ce725cd427df' - allowPackageConflicts: true - displayName: 'Push NuGet package to the AzureFunctionsPreRelease feed' - # In order for the SBOM to be accurate, we want to explicitly specify the components folder, but the types package doesn't have any components - # We'll create an empty folder that _would_ store the components if it had any - - bash: | - mkdir types/node_modules - displayName: 'mkdir types/node_modules' - - task: CopyFiles@2 - displayName: 'Copy types files to staging' - inputs: - sourceFolder: '$(Build.SourcesDirectory)/types' - contents: | - index.d.ts - LICENSE - package.json - README.md - targetFolder: '$(Build.ArtifactStagingDirectory)/types' - cleanTargetFolder: true - - task: ManifestGeneratorTask@0 - displayName: 'Generate SBOM for types' - inputs: - BuildDropPath: '$(Build.ArtifactStagingDirectory)/types' - BuildComponentPath: '$(Build.SourcesDirectory)/types/node_modules' - PackageName: 'Azure Functions Type Definitions' + PackageName: 'Azure Functions Node.js Framework' - script: npm pack - displayName: 'npm pack types' - workingDirectory: '$(Build.ArtifactStagingDirectory)/types' + displayName: 'npm pack' + workingDirectory: '$(Build.ArtifactStagingDirectory)' - task: CopyFiles@2 - displayName: 'Copy packages to staging drop folder' + displayName: 'Copy package to staging drop folder' inputs: sourceFolder: '$(Build.ArtifactStagingDirectory)' contents: | - worker/*.nupkg - types/*.tgz + *.tgz targetFolder: '$(Build.ArtifactStagingDirectory)/drop' cleanTargetFolder: true - task: PublishPipelineArtifact@1 diff --git a/azure-pipelines/e2e-integration-test.yml b/azure-pipelines/e2e-integration-test.yml deleted file mode 100644 index 401b8e6..0000000 --- a/azure-pipelines/e2e-integration-test.yml +++ /dev/null @@ -1,75 +0,0 @@ -variables: { - NODE_14: '14.x', - NODE_16: '16.x', - NODE_18: '18.x' -} - -pr: none -trigger: none - -strategy: - maxParallel: 1 - matrix: - UBUNTU_NODE14: - IMAGE_TYPE: 'ubuntu-latest' - NODE_VERSION: $(NODE_14) - UBUNTU_NODE16: - IMAGE_TYPE: 'ubuntu-latest' - NODE_VERSION: $(NODE_16) - UBUNTU_NODE18: - IMAGE_TYPE: 'ubuntu-latest' - NODE_VERSION: $(NODE_18) - WINDOWS_NODE14: - IMAGE_TYPE: 'windows-latest' - NODE_VERSION: $(NODE_14) - WINDOWS_NODE16: - IMAGE_TYPE: 'windows-latest' - NODE_VERSION: $(NODE_16) - WINDOWS_NODE18: - IMAGE_TYPE: 'windows-latest' - NODE_VERSION: $(NODE_18) - MAC_NODE14: - IMAGE_TYPE: 'macOS-latest' - NODE_VERSION: $(NODE_14) - MAC_NODE16: - IMAGE_TYPE: 'macOS-latest' - NODE_VERSION: $(NODE_16) - MAC_NODE18: - IMAGE_TYPE: 'macOS-latest' - NODE_VERSION: $(NODE_18) -pool: - vmImage: $(IMAGE_TYPE) -steps: - - task: NodeTool@0 - inputs: - versionSpec: $(NODE_VERSION) - displayName: 'Install Node.js' - - script: npm ci - displayName: 'npm ci' - - script: npm run build - displayName: 'npm run build' - - script: npm run webpack - displayName: 'npm run webpack' - - task: UseDotNet@2 - displayName: 'Install .NET 6' - inputs: - version: 6.0.x - - pwsh: | - .\scripts\setup-e2e-tests.ps1 -UseCoreToolsBuildFromIntegrationTests - displayName: 'Setup e2e tests' - - powershell: | - .\scripts\run-e2e-tests.ps1 - displayName: 'Run e2e tests' - env: - AzureWebJobsStorage: $(AzureWebJobsStorage) - AzureWebJobsEventHubSender: $(AzureWebJobsEventHubSender) - AzureWebJobsCosmosDBConnectionString: $(AzureWebJobsCosmosDBConnectionString) - FUNCTIONS_WORKER_RUNTIME: 'node' - nodeVersion: $(NODE_VERSION) - - task: PublishTestResults@2 - displayName: 'Publish E2E Test Results' - condition: always() - inputs: - testRunner: VSTest - testResultsFiles: '**/*.trx' - testRunTitle: '$(Agent.JobName)' diff --git a/azure-pipelines/release-types.yml b/azure-pipelines/release.yml similarity index 74% rename from azure-pipelines/release-types.yml rename to azure-pipelines/release.yml index 87e0dea..73bcd61 100644 --- a/azure-pipelines/release-types.yml +++ b/azure-pipelines/release.yml @@ -13,9 +13,9 @@ pr: none resources: pipelines: - - pipeline: nodeWorkerCI + - pipeline: nodeLibraryCI project: 'Azure Functions' - source: azure-functions-nodejs-worker.build + source: azure-functions-nodejs-library.build branch: v3.x jobs: @@ -29,15 +29,15 @@ jobs: displayName: 'Install Node.js' inputs: versionSpec: 14.x - - download: nodeWorkerCI + - download: nodeLibraryCI - script: mv *.tgz package.tgz displayName: 'Rename tgz file' # because the publish command below requires an exact path - workingDirectory: '$(Pipeline.Workspace)/nodeWorkerCI/drop/types' + workingDirectory: '$(Pipeline.Workspace)/nodeLibraryCI/drop' - task: Npm@1 displayName: 'npm publish' inputs: command: custom - workingDir: '$(Pipeline.Workspace)/nodeWorkerCI/drop/types' + workingDir: '$(Pipeline.Workspace)/nodeLibraryCI/drop' verbose: true customCommand: 'publish package.tgz --tag ${{ parameters.NpmPublishTag }} --dry-run ${{ lower(parameters.NpmPublishDryRun) }}' - customEndpoint: 'TypeScript Types Publish' \ No newline at end of file + customEndpoint: 'Functions Node.js Library Publish' \ No newline at end of file diff --git a/azure-pipelines/test.yml b/azure-pipelines/test.yml index e0673d4..a3a0548 100644 --- a/azure-pipelines/test.yml +++ b/azure-pipelines/test.yml @@ -66,72 +66,3 @@ jobs: testResultsFiles: 'test/unit-test-results.xml' testRunTitle: '$(Agent.JobName)' condition: succeededOrFailed() - -- job: E2ETests - strategy: - maxParallel: 1 - matrix: - UBUNTU_NODE14: - IMAGE_TYPE: 'ubuntu-latest' - NODE_VERSION: $(NODE_14) - UBUNTU_NODE16: - IMAGE_TYPE: 'ubuntu-latest' - NODE_VERSION: $(NODE_16) - UBUNTU_NODE18: - IMAGE_TYPE: 'ubuntu-latest' - NODE_VERSION: $(NODE_18) - WINDOWS_NODE14: - IMAGE_TYPE: 'windows-latest' - NODE_VERSION: $(NODE_14) - WINDOWS_NODE16: - IMAGE_TYPE: 'windows-latest' - NODE_VERSION: $(NODE_16) - WINDOWS_NODE18: - IMAGE_TYPE: 'windows-latest' - NODE_VERSION: $(NODE_18) - MAC_NODE14: - IMAGE_TYPE: 'macOS-latest' - NODE_VERSION: $(NODE_14) - MAC_NODE16: - IMAGE_TYPE: 'macOS-latest' - NODE_VERSION: $(NODE_16) - MAC_NODE18: - IMAGE_TYPE: 'macOS-latest' - NODE_VERSION: $(NODE_18) - pool: - vmImage: $(IMAGE_TYPE) - steps: - - task: NodeTool@0 - inputs: - versionSpec: $(NODE_VERSION) - displayName: 'Install Node.js' - - script: npm ci - displayName: 'npm ci' - - script: npm run build - displayName: 'npm run build' - - script: npm run webpack - displayName: 'npm run webpack' - - task: UseDotNet@2 - displayName: 'Install .NET 6' - inputs: - version: 6.0.x - - pwsh: | - .\scripts\setup-e2e-tests.ps1 - displayName: 'Setup e2e tests' - - powershell: | - .\scripts\run-e2e-tests.ps1 - displayName: 'Run e2e tests' - env: - AzureWebJobsStorage: $(AzureWebJobsStorage) - AzureWebJobsEventHubSender: $(AzureWebJobsEventHubSender) - AzureWebJobsCosmosDBConnectionString: $(AzureWebJobsCosmosDBConnectionString) - FUNCTIONS_WORKER_RUNTIME: 'node' - languageWorkers:node:workerDirectory: $(System.DefaultWorkingDirectory) - nodeVersion: $(NODE_VERSION) - - task: PublishTestResults@2 - displayName: 'Publish E2E Test Results' - condition: always() - inputs: - testRunner: VSTest - testResultsFiles: '**/*.trx' - testRunTitle: '$(Agent.JobName)' diff --git a/package-lock.json b/package-lock.json index bb832ef..faf603c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,44 +1,41 @@ { - "name": "azure-functions-nodejs-worker", - "version": "3.4.0", + "name": "@azure/functions", + "version": "3.5.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "azure-functions-nodejs-worker", - "version": "3.4.0", - "license": "(MIT OR Apache-2.0)", + "name": "@azure/functions", + "version": "3.5.0-alpha.1", + "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.2.7", - "@grpc/proto-loader": "^0.6.4", - "blocked-at": "^1.2.0", "fs-extra": "^10.0.1", "long": "^4.0.0", - "minimist": "^1.2.5", "uuid": "^8.3.0" }, "devDependencies": { - "@types/blocked-at": "^1.0.1", "@types/chai": "^4.2.22", "@types/chai-as-promised": "^7.1.5", "@types/fs-extra": "^9.0.13", + "@types/long": "^4.0.2", "@types/minimist": "^1.2.2", - "@types/mocha": "^2.2.48", + "@types/mocha": "^9.1.1", "@types/mock-fs": "^4.13.1", "@types/mock-require": "^2.0.1", "@types/node": "^16.9.6", "@types/semver": "^7.3.9", "@types/sinon": "^7.0.0", + "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.12.1", "@typescript-eslint/parser": "^5.12.1", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "escape-string-regexp": "^4.0.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-deprecation": "^1.3.2", "eslint-plugin-header": "^3.1.1", "eslint-plugin-prettier": "^4.0.0", + "globby": "^11.0.0", "minimist": "^1.2.6", "mocha": "^9.1.1", "mocha-junit-reporter": "^2.0.2", @@ -46,7 +43,6 @@ "mock-fs": "^5.1.2", "mock-require": "^2.0.2", "prettier": "^2.4.1", - "protobufjs": "^6.11.3", "rimraf": "^2.6.3", "semver": "^7.3.5", "shx": "^0.3.3", @@ -216,86 +212,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", - "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", - "dependencies": { - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz", - "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==", - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.1.1" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@grpc/proto-loader/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/@grpc/proto-loader/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@grpc/proto-loader/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@grpc/proto-loader/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -409,60 +325,6 @@ "node": ">= 8" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, "node_modules/@sinonjs/commons": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", @@ -499,15 +361,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "node_modules/@types/blocked-at": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/blocked-at/-/blocked-at-1.0.1.tgz", - "integrity": "sha512-UOKB0XgFZ91VzfV/5o5iahH32aeVUA7OpjYhwM7dbevTgpNJ8i5uSAcU08rypFA8ewJqLIXlHNSdEpfbgtr2Ig==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/chai": { "version": "4.2.22", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", @@ -565,9 +418,10 @@ "dev": true }, "node_modules/@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "dev": true }, "node_modules/@types/minimist": { "version": "1.2.2", @@ -576,9 +430,9 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "2.2.48", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", - "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", "dev": true }, "node_modules/@types/mock-fs": { @@ -602,7 +456,8 @@ "node_modules/@types/node": { "version": "16.10.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz", - "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==" + "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", + "dev": true }, "node_modules/@types/semver": { "version": "7.3.9", @@ -616,6 +471,12 @@ "integrity": "sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==", "dev": true }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.12.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz", @@ -1095,6 +956,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -1103,6 +965,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1192,11 +1055,6 @@ "node": ">=8" } }, - "node_modules/blocked-at": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/blocked-at/-/blocked-at-1.2.0.tgz", - "integrity": "sha512-Ba9yhK4KcFrgqEPgsU0qVGiMimf+VrD9QJo9pgwjg4yl0GXwgOJS8IRx2rPepQjalrmUdGTqX47bSuJLUMLX7w==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1402,6 +1260,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1412,7 +1271,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/colorette": { "version": "2.0.15", @@ -1538,7 +1398,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/enhanced-resolve": { "version": "5.9.3", @@ -1587,6 +1448,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, "engines": { "node": ">=6" } @@ -2100,9 +1962,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2112,7 +1974,7 @@ "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-json-stable-stringify": { @@ -2274,6 +2136,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2353,16 +2216,16 @@ } }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -2438,9 +2301,9 @@ } }, "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { "node": ">= 4" @@ -2564,6 +2427,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -2798,11 +2662,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -2899,13 +2758,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -3426,9 +3285,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "engines": { "node": ">=8.6" @@ -3543,31 +3402,6 @@ "node": ">=0.4.0" } }, - "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -3646,6 +3480,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3948,6 +3783,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -3961,6 +3797,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4592,6 +4429,7 @@ "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, "engines": { "node": ">=10" } @@ -4783,67 +4621,6 @@ } } }, - "@grpc/grpc-js": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", - "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", - "requires": { - "@types/node": ">=12.12.47" - } - }, - "@grpc/proto-loader": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz", - "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.1.1" - }, - "dependencies": { - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - } - } - }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -4936,60 +4713,6 @@ "fastq": "^1.6.0" } }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, "@sinonjs/commons": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", @@ -5026,15 +4749,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@types/blocked-at": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/blocked-at/-/blocked-at-1.0.1.tgz", - "integrity": "sha512-UOKB0XgFZ91VzfV/5o5iahH32aeVUA7OpjYhwM7dbevTgpNJ8i5uSAcU08rypFA8ewJqLIXlHNSdEpfbgtr2Ig==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/chai": { "version": "4.2.22", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", @@ -5092,9 +4806,10 @@ "dev": true }, "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "dev": true }, "@types/minimist": { "version": "1.2.2", @@ -5103,9 +4818,9 @@ "dev": true }, "@types/mocha": { - "version": "2.2.48", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", - "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", "dev": true }, "@types/mock-fs": { @@ -5129,7 +4844,8 @@ "@types/node": { "version": "16.10.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz", - "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==" + "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", + "dev": true }, "@types/semver": { "version": "7.3.9", @@ -5143,6 +4859,12 @@ "integrity": "sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==", "dev": true }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "5.12.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz", @@ -5494,12 +5216,14 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -5565,11 +5289,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "blocked-at": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/blocked-at/-/blocked-at-1.2.0.tgz", - "integrity": "sha512-Ba9yhK4KcFrgqEPgsU0qVGiMimf+VrD9QJo9pgwjg4yl0GXwgOJS8IRx2rPepQjalrmUdGTqX47bSuJLUMLX7w==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5717,6 +5436,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -5724,7 +5444,8 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "colorette": { "version": "2.0.15", @@ -5826,7 +5547,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "enhanced-resolve": { "version": "5.9.3", @@ -5862,7 +5584,8 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", @@ -6215,9 +5938,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -6353,7 +6076,8 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-func-name": { "version": "2.0.0", @@ -6406,16 +6130,16 @@ } }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, @@ -6467,9 +6191,9 @@ "dev": true }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, "import-fresh": { @@ -6561,7 +6285,8 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-glob": { "version": "4.0.3", @@ -6738,11 +6463,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -6829,13 +6549,13 @@ "dev": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime-db": { @@ -7231,9 +6951,9 @@ "dev": true }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pkg-dir": { @@ -7311,26 +7031,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -7379,7 +7079,8 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-from-string": { "version": "2.0.2", @@ -7598,6 +7299,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7608,6 +7310,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -8068,7 +7771,8 @@ "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { "version": "2.0.0", diff --git a/package.json b/package.json index 4d910c9..8b797ca 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,70 @@ { - "name": "azure-functions-nodejs-worker", - "author": "Microsoft Corporation", - "version": "3.4.0", - "description": "Microsoft Azure Functions NodeJS Worker", - "license": "(MIT OR Apache-2.0)", + "name": "@azure/functions", + "version": "3.5.0-alpha.1", + "description": "Microsoft Azure Functions NodeJS Framework", + "keywords": [ + "azure", + "azure-functions", + "serverless", + "typescript" + ], + "author": "Microsoft", + "license": "MIT", + "homepage": "https://github.com/Azure/azure-functions-nodejs-library", + "repository": { + "type": "git", + "url": "https://github.com/Azure/azure-functions-nodejs-library.git" + }, + "bugs": { + "url": "https://github.com/Azure/azure-functions-nodejs-library/issues" + }, + "main": "dist/src/index-bundle.js", + "types": "types/index.d.ts", + "files": [ + "dist/src/index-bundle.js", + "types/index.d.ts", + "LICENSE", + "README.md", + "NOTICE.html", + "_manifest" + ], + "scripts": { + "build": "node ./node_modules/typescript/bin/tsc", + "test": "node ./dist/test/index.js", + "lint": "eslint .", + "lint-fix": "eslint . --fix", + "updateVersion": "ts-node ./scripts/updateVersion.ts", + "watch": "node ./node_modules/typescript/bin/tsc --watch", + "webpack": "webpack --mode production" + }, "dependencies": { - "@grpc/grpc-js": "^1.2.7", - "@grpc/proto-loader": "^0.6.4", - "blocked-at": "^1.2.0", "fs-extra": "^10.0.1", "long": "^4.0.0", - "minimist": "^1.2.5", "uuid": "^8.3.0" }, "devDependencies": { - "@types/blocked-at": "^1.0.1", "@types/chai": "^4.2.22", "@types/chai-as-promised": "^7.1.5", "@types/fs-extra": "^9.0.13", + "@types/long": "^4.0.2", "@types/minimist": "^1.2.2", - "@types/mocha": "^2.2.48", + "@types/mocha": "^9.1.1", "@types/mock-fs": "^4.13.1", "@types/mock-require": "^2.0.1", "@types/node": "^16.9.6", "@types/semver": "^7.3.9", "@types/sinon": "^7.0.0", + "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.12.1", "@typescript-eslint/parser": "^5.12.1", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "escape-string-regexp": "^4.0.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-deprecation": "^1.3.2", "eslint-plugin-header": "^3.1.1", "eslint-plugin-prettier": "^4.0.0", + "globby": "^11.0.0", "minimist": "^1.2.6", "mocha": "^9.1.1", "mocha-junit-reporter": "^2.0.2", @@ -42,7 +72,6 @@ "mock-fs": "^5.1.2", "mock-require": "^2.0.2", "prettier": "^2.4.1", - "protobufjs": "^6.11.3", "rimraf": "^2.6.3", "semver": "^7.3.5", "shx": "^0.3.3", @@ -53,28 +82,5 @@ "typescript4": "npm:typescript@~4.0.0", "webpack": "^5.72.1", "webpack-cli": "^4.8.0" - }, - "homepage": "https://github.com/Azure/azure-functions-nodejs-worker", - "repository": { - "type": "git", - "url": "https://github.com/Azure/azure-functions-nodejs-worker.git" - }, - "bugs": { - "url": "https://github.com/Azure/azure-functions-nodejs-worker/issues" - }, - "scripts": { - "clean": "rimraf dist && rimraf azure-functions-language-worker-protobuf/src/rpc*", - "build": "rimraf dist && npm run gen && shx mkdir -p dist/azure-functions-language-worker-protobuf/src && shx cp azure-functions-language-worker-protobuf/src/rpc.* dist/azure-functions-language-worker-protobuf/src/. && node ./node_modules/typescript/bin/tsc", - "gen": "node scripts/generateProtos.js", - "test": "mocha -r ts-node/register \"./test/**/*.ts\" --reporter mocha-multi-reporters --reporter-options configFile=test/mochaReporterOptions.json", - "lint": "eslint .", - "lint-fix": "eslint . --fix", - "updateVersion": "ts-node ./scripts/updateVersion.ts", - "watch": "node ./node_modules/typescript/bin/tsc --watch", - "webpack": "webpack --mode production" - }, - "files": [ - "dist" - ], - "main": "dist/src/nodejsWorker.js" + } } diff --git a/scripts/generateProtos.js b/scripts/generateProtos.js deleted file mode 100644 index 2586d0c..0000000 --- a/scripts/generateProtos.js +++ /dev/null @@ -1,45 +0,0 @@ -const util = require("util"); -const exec = util.promisify(require("child_process").exec); -const path = require("path"); - -async function generateProtos() { - try { - const protoSrc = path.join(__dirname, '..', 'azure-functions-language-worker-protobuf', 'src'); - const protoRoot = path.join(protoSrc, 'proto'); - - const protoFiles = [ - path.join(protoRoot, 'shared', 'NullableTypes.proto'), - path.join(protoRoot, 'identity', 'ClaimsIdentityRpc.proto'), - path.join(protoRoot, 'FunctionRpc.proto') - ].join(' '); - - console.log("Compiling protobuf definitions..."); - - console.log('Compiling to JavaScript...'); - const jsOut = path.join(protoSrc, 'rpc.js'); - await run(`pbjs -t json-module -w commonjs -o ${jsOut} ${protoFiles}`); - console.log(`Compiled to JavaScript: "${jsOut}"`); - - console.log('Compiling to JavaScript static module...'); - const jsStaticOut = path.join(protoSrc, 'rpc_static.js'); - await run(`pbjs -t static-module -o ${jsStaticOut} ${protoFiles}`); - console.log(`Compiled to JavaScript static module: "${jsStaticOut}"`); - - console.log('Compiling to TypeScript...'); - const dTsOut = path.join(protoSrc, 'rpc.d.ts'); - await run(`pbts -o ${dTsOut} ${jsStaticOut}`); - console.log(`Compiled to TypeScript: "${dTsOut}"`); - } catch (error) { - console.error('Failed to compile protobuf definitions:'); - console.error(error.message); - process.exit(-1); - } -}; - -async function run(command) { - const { stdout, stderr } = await exec(command); - console.log(stdout); - console.error(stderr); -} - -generateProtos(); diff --git a/scripts/run-e2e-tests.ps1 b/scripts/run-e2e-tests.ps1 deleted file mode 100644 index 5bed283..0000000 --- a/scripts/run-e2e-tests.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -function RunTest([string] $project, [string] $description,[bool] $skipBuild = $false, $filter = $null) { - Write-Host "Running test: $description" -ForegroundColor DarkCyan - Write-Host "-----------------------------------------------------------------------------" -ForegroundColor DarkCyan - Write-Host - - $cmdargs = "test", "$project", "-v", "q", "-l", "trx", "-r",".\testResults" - - if ($filter) { - $cmdargs += "--filter", "$filter" - } - - & dotnet $cmdargs | Out-Host - $r = $? - - Write-Host - Write-Host "-----------------------------------------------------------------------------" -ForegroundColor DarkCyan - Write-Host - - return $r -} - -$tests = @( - @{project ="$PSScriptRoot/../test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E.csproj"; description="E2E integration tests"} -) - -$success = $true -$testRunSucceeded = $true - -foreach ($test in $tests){ - $testRunSucceeded = RunTest $test.project $test.description $testRunSucceeded $test.filter - $success = $testRunSucceeded -and $success -} - -if (-not $success) { exit 1 } \ No newline at end of file diff --git a/scripts/setup-e2e-tests.ps1 b/scripts/setup-e2e-tests.ps1 deleted file mode 100644 index f195b0e..0000000 --- a/scripts/setup-e2e-tests.ps1 +++ /dev/null @@ -1,96 +0,0 @@ -# -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# -param -( - [Switch] - $UseCoreToolsBuildFromIntegrationTests -) - -$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() -if ($IsWindows) { - $FUNC_EXE_NAME = "func.exe" - $os = "win" -} else { - $FUNC_EXE_NAME = "func" - if ($IsMacOS) { - $os = "osx" - } else { - $os = "linux" - } -} - -$FUNC_RUNTIME_VERSION = '4' -$coreToolsDownloadURL = $null -if ($UseCoreToolsBuildFromIntegrationTests.IsPresent) -{ - Write-Host "Install Functions Core Tools for integration tests" -fore Green - $coreToolsDownloadURL = "https://functionsintegclibuilds.blob.core.windows.net/builds/$FUNC_RUNTIME_VERSION/latest/Azure.Functions.Cli.$os-$arch.zip" - $env:CORE_TOOLS_URL = "https://functionsintegclibuilds.blob.core.windows.net/builds/$FUNC_RUNTIME_VERSION/latest" -} -else -{ - $coreToolsDownloadURL = "https://functionsclibuilds.blob.core.windows.net/builds/$FUNC_RUNTIME_VERSION/latest/Azure.Functions.Cli.$os-$arch.zip" - if (-not $env:CORE_TOOLS_URL) - { - $env:CORE_TOOLS_URL = "https://functionsclibuilds.blob.core.windows.net/builds/$FUNC_RUNTIME_VERSION/latest" - } -} - -$FUNC_CLI_DIRECTORY = Join-Path $PSScriptRoot '..' 'Azure.Functions.Cli' - -Write-Host 'Deleting Functions Core Tools if exists...' -Remove-Item -Force "$FUNC_CLI_DIRECTORY.zip" -ErrorAction Ignore -Remove-Item -Recurse -Force $FUNC_CLI_DIRECTORY -ErrorAction Ignore - -$version = Invoke-RestMethod -Uri "$env:CORE_TOOLS_URL/version.txt" -$version = $version.Trim() -Write-Host "Downloading Functions Core Tools (Version: $version)..." - -$output = "$FUNC_CLI_DIRECTORY.zip" -Write-Host "Functions Core Tools download URL: $coreToolsDownloadURL" -Invoke-RestMethod -Uri $coreToolsDownloadURL -OutFile $output - -Write-Host 'Extracting Functions Core Tools...' -Expand-Archive $output -DestinationPath $FUNC_CLI_DIRECTORY - -if ($UseCoreToolsBuildFromIntegrationTests.IsPresent) -{ - Write-Host "Set Node worker directory" - $nodeWorkerFolderPath = [IO.Path]::Join($FUNC_CLI_DIRECTORY, 'workers', 'node') - - if (-not (Test-Path $nodeWorkerFolderPath)) - { - throw "Path '$nodeWorkerFolderPath' does not exist" - } - - $workerDirectory = "languageWorkers:node:workerDirectory" - $env:workerDirectory = $nodeWorkerFolderPath - Write-Host "env:languageWorkers:node:workerDirectory = '$env:workerDirectory'" -} - -$funcExePath = Join-Path $FUNC_CLI_DIRECTORY $FUNC_EXE_NAME - -Write-Host "Installing extensions..." -Push-Location "$PSScriptRoot/../test/end-to-end/testFunctionApp" - -if ($IsMacOS -or $IsLinux) { - chmod +x $funcExePath -} - -& $funcExePath extensions install | ForEach-Object { - if ($_ -match 'OK') - { Write-Host $_ -f Green } - elseif ($_ -match 'FAIL|ERROR') - { Write-Host $_ -f Red } - else - { Write-Host $_ } -} - -if ($LASTEXITCODE -ne 0) -{ - throw "Installing extensions failed." -} - -Pop-Location diff --git a/scripts/updateVersion.ts b/scripts/updateVersion.ts index cf2ee49..f7736db 100644 --- a/scripts/updateVersion.ts +++ b/scripts/updateVersion.ts @@ -9,10 +9,6 @@ import * as semver from 'semver'; const repoRoot = path.join(__dirname, '..'); const packageJsonPath = path.join(repoRoot, 'package.json'); -const typesRoot = path.join(repoRoot, 'types'); -const typesPackageJsonPath = path.join(typesRoot, 'package.json'); -const nuspecPath = path.join(repoRoot, 'Worker.nuspec'); -const nuspecVersionRegex = /(.*)\$prereleaseSuffix\$<\/version>/i; const constantsPath = path.join(repoRoot, 'src', 'constants.ts'); const constantsVersionRegex = /version = '(.*)'/i; @@ -22,10 +18,7 @@ if (args.validate) { } else if (args.version) { updateVersion(args.version); } else { - console.log(`This script can be used to either update the version of the worker or validate that the repo is in a valid state with regards to versioning. - -NOTE: For the types package, only the major & minor version need to match the worker. We follow the same pattern as DefinitelyTyped as described here: -https://github.com/DefinitelyTyped/DefinitelyTyped#how-do-definitely-typed-package-versions-relate-to-versions-of-the-corresponding-library + 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. Example usage: @@ -39,35 +32,18 @@ function validateVersion() { const packageJson = readJSONSync(packageJsonPath); const packageJsonVersion = packageJson.version; - const typesPackageJson = readJSONSync(typesPackageJsonPath); - const typesPackageJsonVersion = typesPackageJson.version; - - const nuspecVersion = getVersion(nuspecPath, nuspecVersionRegex); - const constantsVersion = getVersion(constantsPath, constantsVersionRegex); console.log('Found the following versions:'); console.log(`- package.json: ${packageJsonVersion}`); - console.log(`- types/package.json: ${typesPackageJsonVersion}`); - console.log(`- Worker.nuspec: ${nuspecVersion}`); console.log(`- src/constants.ts: ${constantsVersion}`); const parsedVersion = semver.parse(packageJsonVersion); - const parsedTypesVersion = semver.parse(typesPackageJsonVersion); - - if ( - !packageJsonVersion || - !nuspecVersion || - !constantsVersion || - !typesPackageJsonVersion || - !parsedVersion || - !parsedTypesVersion - ) { + + if (!packageJsonVersion || !constantsVersion || !parsedVersion) { throw new Error('Failed to detect valid versions in all expected files'); - } else if (nuspecVersion !== packageJsonVersion || constantsVersion !== packageJsonVersion) { - throw new Error(`Worker versions do not match.`); - } else if (parsedVersion.major !== parsedTypesVersion.major || parsedVersion.minor !== parsedTypesVersion.minor) { - throw new Error(`Types package does not match the major/minor version of the worker.`); + } else if (constantsVersion !== packageJsonVersion) { + throw new Error(`Versions do not match.`); } else { console.log('Versions match! 🎉'); } @@ -85,14 +61,6 @@ function getVersion(filePath: string, regex: RegExp): string { function updateVersion(newVersion: string) { updatePackageJsonVersion(repoRoot, newVersion); - if (newVersion.endsWith('.0')) { - updatePackageJsonVersion(typesRoot, newVersion); - } else { - console.log(`Skipping types/package.json because this is a patch version.`); - } - - updateVersionByRegex(nuspecPath, nuspecVersionRegex, newVersion); - updateVersionByRegex(constantsPath, constantsVersionRegex, newVersion); } diff --git a/src/Context.ts b/src/Context.ts index 2590666..a45e11d 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -10,8 +10,8 @@ import { Logger, TraceContext, } from '@azure/functions'; +import { RpcInvocationRequest, RpcLog, RpcParameterBinding } from '@azure/functions-core'; import { v4 as uuid } from 'uuid'; -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; import { convertKeysToCamelCase, getBindingDefinitions, @@ -22,20 +22,19 @@ import { FunctionInfo } from './FunctionInfo'; import { Request } from './http/Request'; import { Response } from './http/Response'; import EventEmitter = require('events'); -import LogLevel = rpc.RpcLog.Level; export function CreateContextAndInputs( info: FunctionInfo, - request: rpc.IInvocationRequest, - userLogCallback: UserLogCallback + request: RpcInvocationRequest, + userLogCallback: UserLogCallback, + doneEmitter: EventEmitter ) { - const doneEmitter = new EventEmitter(); const context = new InvocationContext(info, request, userLogCallback, doneEmitter); const bindings: ContextBindings = {}; const inputs: any[] = []; let httpInput: Request | undefined; - for (const binding of request.inputData) { + for (const binding of request.inputData) { if (binding.data && binding.name) { let input; if (binding.data && binding.data.http) { @@ -75,7 +74,6 @@ export function CreateContextAndInputs( return { context: context, inputs: inputs, - doneEmitter, }; } @@ -93,7 +91,7 @@ class InvocationContext implements Context { constructor( info: FunctionInfo, - request: rpc.IInvocationRequest, + request: RpcInvocationRequest, userLogCallback: UserLogCallback, doneEmitter: EventEmitter ) { @@ -109,11 +107,11 @@ class InvocationContext implements Context { this.bindings = {}; // Log message that is tied to function invocation - this.log = Object.assign((...args: any[]) => userLogCallback(LogLevel.Information, ...args), { - error: (...args: any[]) => userLogCallback(LogLevel.Error, ...args), - warn: (...args: any[]) => userLogCallback(LogLevel.Warning, ...args), - info: (...args: any[]) => userLogCallback(LogLevel.Information, ...args), - verbose: (...args: any[]) => userLogCallback(LogLevel.Trace, ...args), + this.log = Object.assign((...args: any[]) => userLogCallback(RpcLog.Level.Information, ...args), { + error: (...args: any[]) => userLogCallback(RpcLog.Level.Error, ...args), + warn: (...args: any[]) => userLogCallback(RpcLog.Level.Warning, ...args), + info: (...args: any[]) => userLogCallback(RpcLog.Level.Information, ...args), + verbose: (...args: any[]) => userLogCallback(RpcLog.Level.Trace, ...args), }); this.bindingData = getNormalizedBindingData(request); @@ -132,7 +130,7 @@ export interface InvocationResult { export type DoneCallback = (err?: unknown, result?: any) => void; -export type UserLogCallback = (level: LogLevel, ...args: any[]) => void; +export type UserLogCallback = (level: RpcLog.Level, ...args: any[]) => void; export interface Dict { [key: string]: T; diff --git a/src/Disposable.ts b/src/Disposable.ts deleted file mode 100644 index c40aee7..0000000 --- a/src/Disposable.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -/** - * Based off of VS Code - * https://github.com/microsoft/vscode/blob/a64e8e5673a44e5b9c2d493666bde684bd5a135c/src/vs/workbench/api/common/extHostTypes.ts#L32 - */ -export class Disposable { - static from(...inDisposables: { dispose(): any }[]): Disposable { - let disposables: ReadonlyArray<{ dispose(): any }> | undefined = inDisposables; - return new Disposable(function () { - if (disposables) { - for (const disposable of disposables) { - if (disposable && typeof disposable.dispose === 'function') { - disposable.dispose(); - } - } - disposables = undefined; - } - }); - } - - #callOnDispose?: () => any; - - constructor(callOnDispose: () => any) { - this.#callOnDispose = callOnDispose; - } - - dispose(): any { - if (this.#callOnDispose instanceof Function) { - this.#callOnDispose(); - this.#callOnDispose = undefined; - } - } -} diff --git a/src/FunctionInfo.ts b/src/FunctionInfo.ts index 6f6ea4c..6643f1a 100644 --- a/src/FunctionInfo.ts +++ b/src/FunctionInfo.ts @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; +import { RpcBindingInfo, RpcFunctionMetadata, RpcTypedData } from '@azure/functions-core'; import { toTypedData } from './converters/RpcConverters'; import { toRpcHttp } from './converters/RpcHttpConverters'; @@ -11,15 +11,15 @@ export class FunctionInfo { name: string; directory: string; bindings: { - [key: string]: rpc.IBindingInfo; + [key: string]: RpcBindingInfo; }; outputBindings: { - [key: string]: rpc.IBindingInfo & { converter: (any) => rpc.ITypedData }; + [key: string]: RpcBindingInfo & { converter: (any) => RpcTypedData }; }; httpOutputName: string; hasHttpTrigger: boolean; - constructor(metadata: rpc.IRpcFunctionMetadata) { + constructor(metadata: RpcFunctionMetadata) { this.name = metadata.name; this.directory = metadata.directory; this.bindings = {}; @@ -32,7 +32,7 @@ export class FunctionInfo { // determine output bindings & assign rpc converter (http has quirks) Object.keys(bindings) - .filter((name) => bindings[name].direction !== rpc.BindingInfo.Direction.in) + .filter((name) => bindings[name].direction !== RpcBindingInfo.Direction.in) .forEach((name) => { const type = bindings[name].type; if (type && type.toLowerCase() === 'http') { diff --git a/src/FunctionLoader.ts b/src/FunctionLoader.ts deleted file mode 100644 index 2edb516..0000000 --- a/src/FunctionLoader.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; -import { FunctionInfo } from './FunctionInfo'; -import { loadScriptFile } from './loadScriptFile'; -import { PackageJson } from './parsers/parsePackageJson'; -import { InternalException } from './utils/InternalException'; -import { nonNullProp } from './utils/nonNull'; - -export interface IFunctionLoader { - load(functionId: string, metadata: rpc.IRpcFunctionMetadata, packageJson: PackageJson): Promise; - getInfo(functionId: string): FunctionInfo; - getFunc(functionId: string): Function; -} - -export class FunctionLoader implements IFunctionLoader { - #loadedFunctions: { - [k: string]: { - info: FunctionInfo; - func: Function; - thisArg: unknown; - }; - } = {}; - - async load(functionId: string, metadata: rpc.IRpcFunctionMetadata, packageJson: PackageJson): Promise { - if (metadata.isProxy === true) { - return; - } - const script: any = await loadScriptFile(nonNullProp(metadata, 'scriptFile'), packageJson); - const entryPoint = (metadata && metadata.entryPoint); - const [userFunction, thisArg] = getEntryPoint(script, entryPoint); - this.#loadedFunctions[functionId] = { - info: new FunctionInfo(metadata), - func: userFunction, - thisArg, - }; - } - - getInfo(functionId: string): FunctionInfo { - const loadedFunction = this.#loadedFunctions[functionId]; - if (loadedFunction && loadedFunction.info) { - return loadedFunction.info; - } else { - throw new InternalException(`Function info for '${functionId}' is not loaded and cannot be invoked.`); - } - } - - getFunc(functionId: string): Function { - const loadedFunction = this.#loadedFunctions[functionId]; - if (loadedFunction && loadedFunction.func) { - // `bind` is necessary to set the `this` arg, but it's also nice because it makes a clone of the function, preventing this invocation from affecting future invocations - return loadedFunction.func.bind(loadedFunction.thisArg); - } else { - throw new InternalException(`Function code for '${functionId}' is not loaded and cannot be invoked.`); - } - } -} - -function getEntryPoint(f: any, entryPoint?: string): [Function, unknown] { - let thisArg: unknown; - if (f !== null && typeof f === 'object') { - thisArg = f; - if (entryPoint) { - // the module exports multiple functions - // and an explicit entry point was named - f = f[entryPoint]; - } else if (Object.keys(f).length === 1) { - // a single named function was exported - const name = Object.keys(f)[0]; - f = f[name]; - } else { - // finally, see if there is an exported function named - // 'run' or 'index' by convention - f = f.run || f.index; - } - } - - if (!f) { - const msg = - (entryPoint - ? `Unable to determine function entry point: ${entryPoint}. ` - : 'Unable to determine function entry point. ') + - 'If multiple functions are exported, ' + - "you must indicate the entry point, either by naming it 'run' or 'index', or by naming it " + - "explicitly via the 'entryPoint' metadata property."; - throw new InternalException(msg); - } else if (typeof f !== 'function') { - throw new InternalException( - 'The resolved entry point is not a function and cannot be invoked by the functions runtime. Make sure the function has been correctly exported.' - ); - } - - return [f, thisArg]; -} diff --git a/src/GrpcClient.ts b/src/GrpcClient.ts deleted file mode 100644 index a328d77..0000000 --- a/src/GrpcClient.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as grpc from '@grpc/grpc-js'; -import { ServiceClientConstructor } from '@grpc/grpc-js/build/src/make-client'; -import * as grpcloader from '@grpc/proto-loader'; -// import protobufjs json descriptor -import * as jsonModule from '../azure-functions-language-worker-protobuf/src/rpc'; - -import rpc = jsonModule.AzureFunctionsRpcMessages; - -function GetGrpcClientConstructor(): ServiceClientConstructor { - const packageDef = grpcloader.fromJSON(jsonModule as protobuf.INamespace, { - objects: true, - defaults: true, - oneofs: true, - }); - const serviceDef = packageDef['AzureFunctionsRpcMessages.FunctionRpc'] as grpcloader.ServiceDefinition; - const clientConstructor: ServiceClientConstructor = grpc.makeClientConstructor(serviceDef, 'FunctionRpc'); - return clientConstructor; -} - -export interface IEventStream { - write(message: rpc.IStreamingMessage); - on(event: 'data', listener: (message: rpc.StreamingMessage) => void); - on(event: string, listener: Function); - end(): void; -} - -export function CreateGrpcEventStream(connection: string, grpcMaxMessageLength: number): IEventStream { - const constructor: ServiceClientConstructor = GetGrpcClientConstructor(); - const clientOptions = { - 'grpc.max_send_message_length': grpcMaxMessageLength, - 'grpc.max_receive_message_length': grpcMaxMessageLength, - }; - const client = new constructor(connection, grpc.credentials.createInsecure(), clientOptions); - process.on('exit', () => { - grpc.closeClient(client); - }); - - const eventStream = client.eventStream(); - - eventStream.on('end', function () { - eventStream.end(); - process.exit(); - }); - return eventStream; -} diff --git a/src/InvocationModel.ts b/src/InvocationModel.ts new file mode 100644 index 0000000..4c8fbb0 --- /dev/null +++ b/src/InvocationModel.ts @@ -0,0 +1,173 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import { AzureFunction, Context } from '@azure/functions'; +import * as coreTypes from '@azure/functions-core'; +import { + CoreInvocationContext, + InvocationArguments, + RpcInvocationResponse, + RpcLog, + RpcParameterBinding, +} from '@azure/functions-core'; +import { format } from 'util'; +import { CreateContextAndInputs } from './Context'; +import { toTypedData } from './converters/RpcConverters'; +import { FunctionInfo } from './FunctionInfo'; +import { isError } from './utils/ensureErrorType'; +import EventEmitter = require('events'); + +const asyncDoneLearnMoreLink = 'https://go.microsoft.com/fwlink/?linkid=2097909'; + +export class InvocationModel implements coreTypes.InvocationModel { + #doneEmitter: EventEmitter = new EventEmitter(); + #isDone = false; + #resultIsPromise = false; + #coreCtx: CoreInvocationContext; + #funcInfo: FunctionInfo; + + constructor(coreCtx: CoreInvocationContext) { + this.#coreCtx = coreCtx; + this.#funcInfo = new FunctionInfo(coreCtx.metadata); + } + + async getArguments(): Promise { + const { context, inputs } = CreateContextAndInputs( + this.#funcInfo, + this.#coreCtx.request, + (level: RpcLog.Level, ...args: any[]) => this.#userLog(level, ...args), + this.#doneEmitter + ); + return { context, inputs }; + } + + async invokeFunction(context: Context, inputs: unknown[], functionCallback: AzureFunction): Promise { + const legacyDoneTask = new Promise((resolve, reject) => { + this.#doneEmitter.on('done', (err?: unknown, result?: unknown) => { + this.#onDone(); + if (isError(err)) { + reject(err); + } else { + resolve(result); + } + }); + }); + + try { + let rawResult = functionCallback(context, ...inputs); + this.#resultIsPromise = !!rawResult && typeof rawResult.then === 'function'; + let resultTask: Promise; + if (this.#resultIsPromise) { + rawResult = Promise.resolve(rawResult).then((r) => { + this.#onDone(); + return r; + }); + resultTask = Promise.race([rawResult, legacyDoneTask]); + } else { + resultTask = legacyDoneTask; + } + + return await resultTask; + } finally { + this.#isDone = true; + } + } + + async getResponse(context: Context, result: unknown): Promise { + const response: RpcInvocationResponse = { invocationId: this.#coreCtx.invocationId }; + response.outputData = []; + const info = this.#funcInfo; + + // Allow HTTP response from context.res if HTTP response is not defined from the context.bindings object + if (info.httpOutputName && context.res && context.bindings[info.httpOutputName] === undefined) { + context.bindings[info.httpOutputName] = context.res; + } + + // As legacy behavior, falsy values get serialized to `null` in AzFunctions. + // This breaks Durable Functions expectations, where customers expect any + // JSON-serializable values to be preserved by the framework, + // so we check if we're serializing for durable and, if so, ensure falsy + // values get serialized. + const isDurableBinding = info?.bindings?.name?.type == 'activityTrigger'; + + const returnBinding = info.getReturnBinding(); + // Set results from return / context.done + if (result || (isDurableBinding && result != null)) { + // $return binding is found: return result data to $return binding + if (returnBinding) { + response.returnValue = returnBinding.converter(result); + // $return binding is not found: read result as object of outputs + } else if (typeof result === 'object') { + response.outputData = Object.keys(info.outputBindings) + .filter((key) => result[key] !== undefined) + .map( + (key) => + { + name: key, + data: info.outputBindings[key].converter(result[key]), + } + ); + } + // returned value does not match any output bindings (named or $return) + // if not http, pass along value + if (!response.returnValue && response.outputData.length == 0 && !info.hasHttpTrigger) { + response.returnValue = toTypedData(result); + } + } + // Set results from context.bindings + if (context.bindings) { + response.outputData = response.outputData.concat( + Object.keys(info.outputBindings) + // Data from return prioritized over data from context.bindings + .filter((key) => { + const definedInBindings: boolean = context.bindings[key] !== undefined; + const hasReturnValue = !!result; + const hasReturnBinding = !!returnBinding; + const definedInReturn: boolean = + hasReturnValue && + !hasReturnBinding && + typeof result === 'object' && + result[key] !== undefined; + return definedInBindings && !definedInReturn; + }) + .map( + (key) => + { + name: key, + data: info.outputBindings[key].converter(context.bindings[key]), + } + ) + ); + } + return response; + } + + #log(level: RpcLog.Level, logCategory: RpcLog.RpcLogCategory, ...args: any[]): void { + this.#coreCtx.log(level, logCategory, format.apply(null, <[any, any[]]>args)); + } + + #systemLog(level: RpcLog.Level, ...args: any[]) { + this.#log(level, RpcLog.RpcLogCategory.System, ...args); + } + + #userLog(level: RpcLog.Level, ...args: any[]): void { + if (this.#isDone && this.#coreCtx.state !== 'postInvocationHooks') { + let badAsyncMsg = + "Warning: Unexpected call to 'log' on the context object after function execution has completed. Please check for asynchronous calls that are not awaited or calls to 'done' made before function execution completes. "; + badAsyncMsg += `Function name: ${this.#funcInfo.name}. Invocation Id: ${this.#coreCtx.invocationId}. `; + badAsyncMsg += `Learn more: ${asyncDoneLearnMoreLink}`; + this.#systemLog(RpcLog.Level.Warning, badAsyncMsg); + } + this.#log(level, RpcLog.RpcLogCategory.User, ...args); + } + + #onDone(): void { + if (this.#isDone) { + const message = this.#resultIsPromise + ? `Error: Choose either to return a promise or call 'done'. Do not use both in your script. Learn more: ${asyncDoneLearnMoreLink}` + : "Error: 'done' has already been called. Please check your script for extraneous calls to 'done'."; + this.#systemLog(RpcLog.Level.Error, message); + } + this.#isDone = true; + } +} diff --git a/src/Worker.ts b/src/Worker.ts deleted file mode 100644 index 8c90c0c..0000000 --- a/src/Worker.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as parseArgs from 'minimist'; -import { FunctionLoader } from './FunctionLoader'; -import { CreateGrpcEventStream } from './GrpcClient'; -import { setupCoreModule } from './setupCoreModule'; -import { setupEventStream } from './setupEventStream'; -import { startBlockedMonitor } from './utils/blockedMonitor'; -import { ensureErrorType } from './utils/ensureErrorType'; -import { InternalException } from './utils/InternalException'; -import { systemError, systemLog } from './utils/Logger'; -import { isEnvironmentVariableSet } from './utils/util'; -import { WorkerChannel } from './WorkerChannel'; - -export function startNodeWorker(args) { - const { host, port, workerId, requestId, grpcMaxMessageLength } = parseArgs(args.slice(2)); - if (!host || !port || !workerId || !requestId || !grpcMaxMessageLength) { - systemLog( - 'usage --host hostName --port portNumber --workerId workerId --requestId requestId --grpcMaxMessageLength grpcMaxMessageLength' - ); - // Find which arguments are in error - const debugInfo: string[] = []; - if (!host) debugInfo.push(`'hostName' is ${host}`); - if (!port) debugInfo.push(`'port' is ${port}`); - if (!workerId) debugInfo.push(`'workerId' is ${workerId}`); - if (!requestId) debugInfo.push(`'requestId' is ${requestId}`); - if (!grpcMaxMessageLength) debugInfo.push(`'grpcMaxMessageLength' is ${grpcMaxMessageLength}`); - - throw new InternalException(`gRPC client connection info is missing or incorrect (${debugInfo.join(', ')}).`); - } - - const connection = `${host}:${port}`; - systemLog(`Worker ${workerId} connecting on ${connection}`); - - let eventStream; - try { - eventStream = CreateGrpcEventStream(connection, parseInt(grpcMaxMessageLength)); - } catch (err) { - const error = ensureErrorType(err); - error.isAzureFunctionsInternalException = true; - error.message = 'Error creating GRPC event stream: ' + error.message; - throw error; - } - - const channel = new WorkerChannel(eventStream, new FunctionLoader()); - setupEventStream(workerId, channel); - setupCoreModule(channel); - - eventStream.write({ - requestId: requestId, - startStream: { - workerId: workerId, - }, - }); - - process.on('uncaughtException', (err: unknown) => { - const error = ensureErrorType(err); - let errorMessage: string; - if (error.isAzureFunctionsInternalException) { - errorMessage = `Worker ${workerId} uncaught exception: ${error.stack || err}`; - } else { - errorMessage = `Worker ${workerId} uncaught exception (learn more: https://go.microsoft.com/fwlink/?linkid=2097909 ): ${ - error.stack || err - }`; - } - - systemError(errorMessage); - process.exit(1); - }); - process.on('exit', (code) => { - systemLog(`Worker ${workerId} exited with code ${code}`); - }); - - if (isEnvironmentVariableSet(process.env.AZURE_FUNCTIONS_NODE_BLOCK_LOG)) { - startBlockedMonitor(channel); - } -} diff --git a/src/WorkerChannel.ts b/src/WorkerChannel.ts deleted file mode 100644 index 80cee7d..0000000 --- a/src/WorkerChannel.ts +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { HookCallback, HookContext, HookData } from '@azure/functions-core'; -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; -import { Disposable } from './Disposable'; -import { IFunctionLoader } from './FunctionLoader'; -import { IEventStream } from './GrpcClient'; -import { PackageJson, parsePackageJson } from './parsers/parsePackageJson'; -import { ensureErrorType } from './utils/ensureErrorType'; -import LogLevel = rpc.RpcLog.Level; -import LogCategory = rpc.RpcLog.RpcLogCategory; - -export class WorkerChannel { - eventStream: IEventStream; - functionLoader: IFunctionLoader; - packageJson: PackageJson; - /** - * This will only be set after worker init request is received - */ - hostVersion?: string; - /** - * this hook data will be passed to (and set by) all hooks in all scopes - */ - appHookData: HookData = {}; - /** - * this hook data is limited to the app-level scope and persisted only for app-level hooks - */ - appLevelOnlyHookData: HookData = {}; - #preInvocationHooks: HookCallback[] = []; - #postInvocationHooks: HookCallback[] = []; - #appStartHooks: HookCallback[] = []; - - constructor(eventStream: IEventStream, functionLoader: IFunctionLoader) { - this.eventStream = eventStream; - this.functionLoader = functionLoader; - this.packageJson = {}; - } - - /** - * Captured logs or relevant details can use the logs property - * @param requestId gRPC message request id - * @param msg gRPC message content - */ - log(log: rpc.IRpcLog) { - this.eventStream.write({ - rpcLog: log, - }); - } - - registerHook(hookName: string, callback: HookCallback): Disposable { - const hooks = this.#getHooks(hookName); - hooks.push(callback); - return new Disposable(() => { - const index = hooks.indexOf(callback); - if (index > -1) { - hooks.splice(index, 1); - } - }); - } - - async executeHooks( - hookName: string, - context: HookContext, - invocationId?: string | null, - msgCategory?: string - ): Promise { - const callbacks = this.#getHooks(hookName); - if (callbacks.length > 0) { - this.log({ - message: `Executing ${callbacks.length} "${hookName}" hooks`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - invocationId, - category: msgCategory, - }); - for (const callback of callbacks) { - await callback(context); - } - this.log({ - message: `Executed "${hookName}" hooks`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - invocationId, - category: msgCategory, - }); - } - } - - #getHooks(hookName: string): HookCallback[] { - switch (hookName) { - case 'preInvocation': - return this.#preInvocationHooks; - case 'postInvocation': - return this.#postInvocationHooks; - case 'appStart': - return this.#appStartHooks; - default: - throw new RangeError(`Unrecognized hook "${hookName}"`); - } - } - - async updatePackageJson(dir: string): Promise { - try { - this.packageJson = await parsePackageJson(dir); - } catch (err) { - const error = ensureErrorType(err); - this.log({ - message: `Worker failed to load package.json: ${error.message}`, - level: LogLevel.Warning, - logCategory: LogCategory.System, - }); - this.packageJson = {}; - } - } -} diff --git a/src/constants.ts b/src/constants.ts index 7bb7956..b6fc04a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. -export const version = '3.4.0'; +export const version = '3.5.0-alpha.1'; export enum HeaderName { contentType = 'content-type', diff --git a/src/converters/BindingConverters.ts b/src/converters/BindingConverters.ts index d1cda03..84da110 100644 --- a/src/converters/BindingConverters.ts +++ b/src/converters/BindingConverters.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { BindingDefinition, ContextBindingData } from '@azure/functions'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; +import { RpcBindingInfo, RpcInvocationRequest } from '@azure/functions-core'; import { FunctionInfo } from '../FunctionInfo'; import { fromTypedData } from './RpcConverters'; @@ -23,7 +23,7 @@ export function getBindingDefinitions(info: FunctionInfo): BindingDefinition[] { }); } -export function getNormalizedBindingData(request: rpc.IInvocationRequest): ContextBindingData { +export function getNormalizedBindingData(request: RpcInvocationRequest): ContextBindingData { const bindingData: ContextBindingData = { invocationId: request.invocationId, }; @@ -35,10 +35,8 @@ export function getNormalizedBindingData(request: rpc.IInvocationRequest): Conte return bindingData; } -function getDirectionName(direction: rpc.BindingInfo.Direction | null | undefined): BindingDirection { - const directionName = Object.keys(rpc.BindingInfo.Direction).find( - (k) => rpc.BindingInfo.Direction[k] === direction - ); +function getDirectionName(direction: RpcBindingInfo.Direction | null | undefined): BindingDirection { + const directionName = Object.keys(RpcBindingInfo.Direction).find((k) => RpcBindingInfo.Direction[k] === direction); return isBindingDirection(directionName) ? (directionName as BindingDirection) : undefined; } diff --git a/src/converters/RpcConverters.ts b/src/converters/RpcConverters.ts index e405aa2..80b7ff7 100644 --- a/src/converters/RpcConverters.ts +++ b/src/converters/RpcConverters.ts @@ -2,14 +2,15 @@ // Licensed under the MIT License. import { TraceContext } from '@azure/functions'; -import { isLong } from 'long'; import { - AzureFunctionsRpcMessages as rpc, - INullableBool, - INullableDouble, - INullableString, - INullableTimestamp, -} from '../../azure-functions-language-worker-protobuf/src/rpc'; + RpcNullableBool, + RpcNullableDouble, + RpcNullableString, + RpcNullableTimestamp, + RpcTraceContext, + RpcTypedData, +} from '@azure/functions-core'; +import { isLong } from 'long'; import { InternalException } from '../utils/InternalException'; /** @@ -18,7 +19,7 @@ import { InternalException } from '../utils/InternalException'; * @param typedData ITypedData object containing one of a string, json, or bytes property * @param convertStringToJson Optionally parse the string input type as JSON */ -export function fromTypedData(typedData?: rpc.ITypedData, convertStringToJson = true) { +export function fromTypedData(typedData?: RpcTypedData, convertStringToJson = true) { typedData = typedData || {}; let str = typedData.string || typedData.json; if (str !== undefined) { @@ -49,7 +50,7 @@ export function fromTypedData(typedData?: rpc.ITypedData, convertStringToJson = * Converts 'IRpcTraceContext' input from RPC layer to dictionary of key value pairs. * @param traceContext IRpcTraceContext object containing the activityId, tracestate and attributes. */ -export function fromRpcTraceContext(traceContext: rpc.IRpcTraceContext | null | undefined): TraceContext { +export function fromRpcTraceContext(traceContext: RpcTraceContext | null | undefined): TraceContext { if (traceContext) { return { traceparent: traceContext.traceParent, @@ -66,7 +67,7 @@ export function fromRpcTraceContext(traceContext: rpc.IRpcTraceContext | null | * TypedData can be string, json, or bytes * @param inputObject A JavaScript object that is a string, Buffer, ArrayBufferView, number, or object. */ -export function toTypedData(inputObject): rpc.ITypedData { +export function toTypedData(inputObject): RpcTypedData { if (typeof inputObject === 'string') { return { string: inputObject }; } else if (Buffer.isBuffer(inputObject)) { @@ -91,9 +92,9 @@ export function toTypedData(inputObject): rpc.ITypedData { * @param nullable Input to be converted to an INullableBool if it is a valid boolean * @param propertyName The name of the property that the caller will assign the output to. Used for debugging. */ -export function toNullableBool(nullable: boolean | undefined, propertyName: string): undefined | INullableBool { +export function toNullableBool(nullable: boolean | undefined, propertyName: string): undefined | RpcNullableBool { if (typeof nullable === 'boolean') { - return { + return { value: nullable, }; } @@ -116,15 +117,15 @@ export function toNullableBool(nullable: boolean | undefined, propertyName: stri export function toNullableDouble( nullable: number | string | undefined, propertyName: string -): undefined | INullableDouble { +): undefined | RpcNullableDouble { if (typeof nullable === 'number') { - return { + return { value: nullable, }; } else if (typeof nullable === 'string') { if (!isNaN(nullable)) { const parsedNumber = parseFloat(nullable); - return { + return { value: parsedNumber, }; } @@ -165,9 +166,9 @@ export function toRpcString(nullable: string | undefined, propertyName: string): * @param nullable Input to be converted to an INullableString if it is a valid string * @param propertyName The name of the property that the caller will assign the output to. Used for debugging. */ -export function toNullableString(nullable: string | undefined, propertyName: string): undefined | INullableString { +export function toNullableString(nullable: string | undefined, propertyName: string): undefined | RpcNullableString { if (typeof nullable === 'string') { - return { + return { value: nullable, }; } @@ -190,7 +191,7 @@ export function toNullableString(nullable: string | undefined, propertyName: str export function toNullableTimestamp( dateTime: Date | number | undefined, propertyName: string -): INullableTimestamp | undefined { +): RpcNullableTimestamp | undefined { if (dateTime != null) { try { const timeInMilliseconds = typeof dateTime === 'number' ? dateTime : dateTime.getTime(); diff --git a/src/converters/RpcHttpConverters.ts b/src/converters/RpcHttpConverters.ts index ef96dac..8c0ea8c 100644 --- a/src/converters/RpcHttpConverters.ts +++ b/src/converters/RpcHttpConverters.ts @@ -2,10 +2,7 @@ // Licensed under the MIT License. import { Cookie } from '@azure/functions'; -import { - AzureFunctionsRpcMessages as rpc, - INullableString, -} from '../../azure-functions-language-worker-protobuf/src/rpc'; +import { RpcHttpCookie, RpcHttpData, RpcNullableString, RpcTypedData } from '@azure/functions-core'; import { Dict } from '../Context'; import { fromTypedData, @@ -23,7 +20,7 @@ import { * This is to avoid breaking changes in v2. * @param body The body from the RPC layer. */ -export function fromRpcHttpBody(body: rpc.ITypedData) { +export function fromRpcHttpBody(body: RpcTypedData) { if (body && body.bytes) { return (body.bytes).toString(); } else { @@ -32,7 +29,7 @@ export function fromRpcHttpBody(body: rpc.ITypedData) { } export function fromNullableMapping( - nullableMapping: { [k: string]: INullableString } | null | undefined, + nullableMapping: { [k: string]: RpcNullableString } | null | undefined, originalMapping?: { [k: string]: string } | null ): Dict { let converted = {}; @@ -51,7 +48,7 @@ export function fromNullableMapping( * 'http' types are a special case from other 'ITypedData' types, which come from primitive types. * @param inputMessage An HTTP response object */ -export function toRpcHttp(inputMessage): rpc.ITypedData { +export function toRpcHttp(inputMessage): RpcTypedData { // Check if we will fail to find any of these if (typeof inputMessage !== 'object' || Array.isArray(inputMessage)) { throw new Error( @@ -59,7 +56,7 @@ export function toRpcHttp(inputMessage): rpc.ITypedData { ); } - const httpMessage: rpc.IRpcHttp = inputMessage; + const httpMessage: RpcHttpData = inputMessage; httpMessage.headers = toRpcHttpHeaders(inputMessage.headers); httpMessage.cookies = toRpcHttpCookieList(inputMessage.cookies || []); let status = inputMessage.statusCode; @@ -75,7 +72,7 @@ export function toRpcHttp(inputMessage): rpc.ITypedData { * Convert HTTP headers to a string/string mapping. * @param inputHeaders */ -function toRpcHttpHeaders(inputHeaders: rpc.ITypedData) { +function toRpcHttpHeaders(inputHeaders: RpcTypedData) { const rpcHttpHeaders: { [key: string]: string } = {}; for (const key in inputHeaders) { if (inputHeaders[key] != null) { @@ -86,11 +83,11 @@ function toRpcHttpHeaders(inputHeaders: rpc.ITypedData) { } /** - * Convert HTTP 'Cookie' array to an array of 'IRpcHttpCookie' objects to be sent through the RPC layer + * Convert HTTP 'Cookie' array to an array of 'RpcHttpCookie' objects to be sent through the RPC layer * @param inputCookies array of 'Cookie' objects representing options for the 'Set-Cookie' response header */ -export function toRpcHttpCookieList(inputCookies: Cookie[]): rpc.IRpcHttpCookie[] { - const rpcCookies: rpc.IRpcHttpCookie[] = []; +export function toRpcHttpCookieList(inputCookies: Cookie[]): RpcHttpCookie[] { + const rpcCookies: RpcHttpCookie[] = []; inputCookies.forEach((cookie) => { rpcCookies.push(toRpcHttpCookie(cookie)); }); @@ -102,21 +99,21 @@ export function toRpcHttpCookieList(inputCookies: Cookie[]): rpc.IRpcHttpCookie[ * From RFC specifications for 'Set-Cookie' response header: https://www.rfc-editor.org/rfc/rfc6265.txt * @param inputCookie */ -function toRpcHttpCookie(inputCookie: Cookie): rpc.IRpcHttpCookie { - // Resolve SameSite enum, a one-off - let rpcSameSite: rpc.RpcHttpCookie.SameSite = rpc.RpcHttpCookie.SameSite.None; +function toRpcHttpCookie(inputCookie: Cookie): RpcHttpCookie { + // Resolve RpcHttpCookie.SameSite enum, a one-off + let rpcSameSite: RpcHttpCookie.SameSite = RpcHttpCookie.SameSite.None; if (inputCookie && inputCookie.sameSite) { const sameSite = inputCookie.sameSite.toLocaleLowerCase(); if (sameSite === 'lax') { - rpcSameSite = rpc.RpcHttpCookie.SameSite.Lax; + rpcSameSite = RpcHttpCookie.SameSite.Lax; } else if (sameSite === 'strict') { - rpcSameSite = rpc.RpcHttpCookie.SameSite.Strict; + rpcSameSite = RpcHttpCookie.SameSite.Strict; } else if (sameSite === 'none') { - rpcSameSite = rpc.RpcHttpCookie.SameSite.ExplicitNone; + rpcSameSite = RpcHttpCookie.SameSite.ExplicitNone; } } - const rpcCookie: rpc.IRpcHttpCookie = { + const rpcCookie: RpcHttpCookie = { name: inputCookie && toRpcString(inputCookie.name, 'cookie.name'), value: inputCookie && toRpcString(inputCookie.value, 'cookie.value'), domain: toNullableString(inputCookie && inputCookie.domain, 'cookie.domain'), diff --git a/src/eventHandlers/EventHandler.ts b/src/eventHandlers/EventHandler.ts deleted file mode 100644 index d1ecab0..0000000 --- a/src/eventHandlers/EventHandler.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { WorkerChannel } from '../WorkerChannel'; - -export type SupportedRequestName = - | 'functionEnvironmentReloadRequest' - | 'functionLoadRequest' - | 'invocationRequest' - | 'workerInitRequest'; -export type SupportedRequest = rpc.StreamingMessage[SupportedRequestName]; - -export type SupportedResponseName = - | 'functionEnvironmentReloadResponse' - | 'functionLoadResponse' - | 'invocationResponse' - | 'workerInitResponse'; -export type SupportedResponse = rpc.StreamingMessage[SupportedResponseName]; - -export abstract class EventHandler< - TRequestName extends SupportedRequestName = SupportedRequestName, - TResponseName extends SupportedResponseName = SupportedResponseName, - TRequest = NonNullable, - TResponse = NonNullable -> { - abstract readonly responseName: TResponseName; - - /** - * The default response with any properties unique to this request that should be set for both success & failure scenarios - */ - abstract getDefaultResponse(request: TRequest): TResponse; - - /** - * Handles the event and returns the response - * NOTE: This method does not need to set the result/status. That will be handled in code common to all event handlers - */ - abstract handleEvent(channel: WorkerChannel, request: TRequest): Promise; -} diff --git a/src/eventHandlers/FunctionEnvironmentReloadHandler.ts b/src/eventHandlers/FunctionEnvironmentReloadHandler.ts deleted file mode 100644 index efd5584..0000000 --- a/src/eventHandlers/FunctionEnvironmentReloadHandler.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { startApp } from '../startApp'; -import { WorkerChannel } from '../WorkerChannel'; -import { EventHandler } from './EventHandler'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -/** - * Environment variables from the current process - */ -export class FunctionEnvironmentReloadHandler extends EventHandler< - 'functionEnvironmentReloadRequest', - 'functionEnvironmentReloadResponse' -> { - readonly responseName = 'functionEnvironmentReloadResponse'; - - getDefaultResponse(_msg: rpc.IFunctionEnvironmentReloadRequest): rpc.IFunctionEnvironmentReloadResponse { - return {}; - } - - async handleEvent( - channel: WorkerChannel, - msg: rpc.IFunctionEnvironmentReloadRequest - ): Promise { - const response = this.getDefaultResponse(msg); - - // Add environment variables from incoming - const numVariables = (msg.environmentVariables && Object.keys(msg.environmentVariables).length) || 0; - channel.log({ - message: `Reloading environment variables. Found ${numVariables} variables to reload.`, - level: LogLevel.Information, - logCategory: LogCategory.System, - }); - - // reset existing env vars - Object.keys(process.env).map((key) => delete process.env[key]); - // set new env vars - Object.assign(process.env, msg.environmentVariables); - - // Change current working directory - if (msg.functionAppDirectory) { - channel.log({ - message: `Changing current working directory to ${msg.functionAppDirectory}`, - level: LogLevel.Information, - logCategory: LogCategory.System, - }); - process.chdir(msg.functionAppDirectory); - await startApp(msg.functionAppDirectory, channel); - } - - return response; - } -} diff --git a/src/eventHandlers/FunctionLoadHandler.ts b/src/eventHandlers/FunctionLoadHandler.ts deleted file mode 100644 index 1f66a4c..0000000 --- a/src/eventHandlers/FunctionLoadHandler.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { ensureErrorType } from '../utils/ensureErrorType'; -import { nonNullProp } from '../utils/nonNull'; -import { WorkerChannel } from '../WorkerChannel'; -import { EventHandler } from './EventHandler'; - -/** - * Worker responds after loading required metadata to load function with the load result - */ -export class FunctionLoadHandler extends EventHandler<'functionLoadRequest', 'functionLoadResponse'> { - readonly responseName = 'functionLoadResponse'; - - getDefaultResponse(msg: rpc.IFunctionLoadRequest): rpc.IFunctionLoadResponse { - return { functionId: msg.functionId }; - } - - async handleEvent(channel: WorkerChannel, msg: rpc.IFunctionLoadRequest): Promise { - const response = this.getDefaultResponse(msg); - - const functionId = nonNullProp(msg, 'functionId'); - const metadata = nonNullProp(msg, 'metadata'); - try { - await channel.functionLoader.load(functionId, metadata, channel.packageJson); - } catch (err) { - const error = ensureErrorType(err); - error.isAzureFunctionsInternalException = true; - error.message = `Worker was unable to load function ${metadata.name}: '${error.message}'`; - throw error; - } - - return response; - } -} diff --git a/src/eventHandlers/InvocationHandler.ts b/src/eventHandlers/InvocationHandler.ts deleted file mode 100644 index 9a55dd9..0000000 --- a/src/eventHandlers/InvocationHandler.ts +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AzureFunction } from '@azure/functions'; -import { HookData, PostInvocationContext, PreInvocationContext } from '@azure/functions-core'; -import { format } from 'util'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { CreateContextAndInputs } from '../Context'; -import { toTypedData } from '../converters/RpcConverters'; -import { isError } from '../utils/ensureErrorType'; -import { nonNullProp } from '../utils/nonNull'; -import { WorkerChannel } from '../WorkerChannel'; -import { EventHandler } from './EventHandler'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -/** - * Host requests worker to invoke a Function - */ -export class InvocationHandler extends EventHandler<'invocationRequest', 'invocationResponse'> { - readonly responseName = 'invocationResponse'; - - getDefaultResponse(msg: rpc.IInvocationRequest): rpc.IInvocationResponse { - return { invocationId: msg.invocationId }; - } - - async handleEvent(channel: WorkerChannel, msg: rpc.IInvocationRequest): Promise { - const response = this.getDefaultResponse(msg); - - const invocationId = nonNullProp(msg, 'invocationId'); - const functionId = nonNullProp(msg, 'functionId'); - - // explicitly set outputData to empty array to concat later - response.outputData = []; - - let isDone = false; - let isExecutingPostInvocationHooks = false; - let resultIsPromise = false; - - const info = channel.functionLoader.getInfo(functionId); - const asyncDoneLearnMoreLink = 'https://go.microsoft.com/fwlink/?linkid=2097909'; - - const msgCategory = `${info.name}.Invocation`; - function log(level: LogLevel, logCategory: LogCategory, ...args: any[]) { - channel.log({ - invocationId: invocationId, - category: msgCategory, - message: format.apply(null, <[any, any[]]>args), - level: level, - logCategory, - }); - } - function systemLog(level: LogLevel, ...args: any[]) { - log(level, LogCategory.System, ...args); - } - function userLog(level: LogLevel, ...args: any[]) { - if (isDone && !isExecutingPostInvocationHooks) { - let badAsyncMsg = - "Warning: Unexpected call to 'log' on the context object after function execution has completed. Please check for asynchronous calls that are not awaited or calls to 'done' made before function execution completes. "; - badAsyncMsg += `Function name: ${info.name}. Invocation Id: ${invocationId}. `; - badAsyncMsg += `Learn more: ${asyncDoneLearnMoreLink}`; - systemLog(LogLevel.Warning, badAsyncMsg); - } - log(level, LogCategory.User, ...args); - } - - // Log invocation details to ensure the invocation received by node worker - systemLog(LogLevel.Debug, 'Received FunctionInvocationRequest'); - - function onDone(): void { - if (isDone) { - const message = resultIsPromise - ? `Error: Choose either to return a promise or call 'done'. Do not use both in your script. Learn more: ${asyncDoneLearnMoreLink}` - : "Error: 'done' has already been called. Please check your script for extraneous calls to 'done'."; - systemLog(LogLevel.Error, message); - } - isDone = true; - } - - let { context, inputs, doneEmitter } = CreateContextAndInputs(info, msg, userLog); - try { - const legacyDoneTask = new Promise((resolve, reject) => { - doneEmitter.on('done', (err?: unknown, result?: any) => { - onDone(); - if (isError(err)) { - reject(err); - } else { - resolve(result); - } - }); - }); - - let userFunction = channel.functionLoader.getFunc(functionId); - - const invocationHookData: HookData = {}; - - const preInvocContext: PreInvocationContext = { - hookData: invocationHookData, - appHookData: channel.appHookData, - invocationContext: context, - functionCallback: userFunction, - inputs, - }; - await channel.executeHooks('preInvocation', preInvocContext, invocationId, msgCategory); - inputs = preInvocContext.inputs; - userFunction = preInvocContext.functionCallback; - - let rawResult = userFunction(context, ...inputs); - resultIsPromise = rawResult && typeof rawResult.then === 'function'; - let resultTask: Promise; - if (resultIsPromise) { - rawResult = Promise.resolve(rawResult).then((r) => { - onDone(); - return r; - }); - resultTask = Promise.race([rawResult, legacyDoneTask]); - } else { - resultTask = legacyDoneTask; - } - - const postInvocContext: PostInvocationContext = { - hookData: invocationHookData, - appHookData: channel.appHookData, - invocationContext: context, - inputs, - result: null, - error: null, - }; - try { - postInvocContext.result = await resultTask; - } catch (err) { - postInvocContext.error = err; - } - - try { - isExecutingPostInvocationHooks = true; - await channel.executeHooks('postInvocation', postInvocContext, msg.invocationId, msgCategory); - } finally { - isExecutingPostInvocationHooks = false; - } - - if (isError(postInvocContext.error)) { - throw postInvocContext.error; - } - const result = postInvocContext.result; - - // Allow HTTP response from context.res if HTTP response is not defined from the context.bindings object - if (info.httpOutputName && context.res && context.bindings[info.httpOutputName] === undefined) { - context.bindings[info.httpOutputName] = context.res; - } - - // As legacy behavior, falsy values get serialized to `null` in AzFunctions. - // This breaks Durable Functions expectations, where customers expect any - // JSON-serializable values to be preserved by the framework, - // so we check if we're serializing for durable and, if so, ensure falsy - // values get serialized. - const isDurableBinding = info?.bindings?.name?.type == 'activityTrigger'; - - const returnBinding = info.getReturnBinding(); - // Set results from return / context.done - if (result || (isDurableBinding && result != null)) { - // $return binding is found: return result data to $return binding - if (returnBinding) { - response.returnValue = returnBinding.converter(result); - // $return binding is not found: read result as object of outputs - } else { - response.outputData = Object.keys(info.outputBindings) - .filter((key) => result[key] !== undefined) - .map( - (key) => - { - name: key, - data: info.outputBindings[key].converter(result[key]), - } - ); - } - // returned value does not match any output bindings (named or $return) - // if not http, pass along value - if (!response.returnValue && response.outputData.length == 0 && !info.hasHttpTrigger) { - response.returnValue = toTypedData(result); - } - } - // Set results from context.bindings - if (context.bindings) { - response.outputData = response.outputData.concat( - Object.keys(info.outputBindings) - // Data from return prioritized over data from context.bindings - .filter((key) => { - const definedInBindings: boolean = context.bindings[key] !== undefined; - const hasReturnValue = !!result; - const hasReturnBinding = !!returnBinding; - const definedInReturn: boolean = - hasReturnValue && !hasReturnBinding && result[key] !== undefined; - return definedInBindings && !definedInReturn; - }) - .map( - (key) => - { - name: key, - data: info.outputBindings[key].converter(context.bindings[key]), - } - ) - ); - } - } finally { - isDone = true; - } - - return response; - } -} diff --git a/src/eventHandlers/WorkerInitHandler.ts b/src/eventHandlers/WorkerInitHandler.ts deleted file mode 100644 index 7e83862..0000000 --- a/src/eventHandlers/WorkerInitHandler.ts +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { access, constants } from 'fs'; -import * as path from 'path'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { startApp } from '../startApp'; -import { isError } from '../utils/ensureErrorType'; -import { nonNullProp } from '../utils/nonNull'; -import { WorkerChannel } from '../WorkerChannel'; -import { EventHandler } from './EventHandler'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -/** - * Host sends capabilities/init data to worker and requests the worker to initialize itself - */ -export class WorkerInitHandler extends EventHandler<'workerInitRequest', 'workerInitResponse'> { - readonly responseName = 'workerInitResponse'; - - getDefaultResponse(_msg: rpc.IWorkerInitRequest): rpc.IWorkerInitResponse { - return {}; - } - - async handleEvent(channel: WorkerChannel, msg: rpc.IWorkerInitRequest): Promise { - const response = this.getDefaultResponse(msg); - - channel.log({ - message: 'Received WorkerInitRequest', - level: LogLevel.Debug, - logCategory: LogCategory.System, - }); - - logColdStartWarning(channel); - - channel.hostVersion = nonNullProp(msg, 'hostVersion'); - - if (msg.functionAppDirectory) { - await startApp(msg.functionAppDirectory, channel); - } - - response.capabilities = { - RpcHttpTriggerMetadataRemoved: 'true', - RpcHttpBodyOnly: 'true', - IgnoreEmptyValuedRpcHttpHeaders: 'true', - UseNullableValueDictionaryForHttp: 'true', - WorkerStatus: 'true', - TypedDataCollection: 'true', - }; - - return response; - } -} - -export function logColdStartWarning(channel: WorkerChannel, delayInMs?: number): void { - // On reading a js file with function code('require') NodeJs tries to find 'package.json' all the way up to the file system root. - // In Azure files it causes a delay during cold start as connection to Azure Files is an expensive operation. - if ( - process.env.WEBSITE_CONTENTAZUREFILECONNECTIONSTRING && - process.env.WEBSITE_CONTENTSHARE && - process.env.AzureWebJobsScriptRoot - ) { - // Add delay to avoid affecting coldstart - if (!delayInMs) { - delayInMs = 5000; - } - setTimeout(() => { - access(path.join(process.env.AzureWebJobsScriptRoot!, 'package.json'), constants.F_OK, (err) => { - if (isError(err)) { - channel.log({ - message: - 'package.json is not found at the root of the Function App in Azure Files - cold start for NodeJs can be affected.', - level: LogLevel.Debug, - logCategory: LogCategory.System, - }); - } - }); - }, delayInMs); - } -} diff --git a/src/http/Request.ts b/src/http/Request.ts index 57b9ac0..1cba4d1 100644 --- a/src/http/Request.ts +++ b/src/http/Request.ts @@ -10,7 +10,7 @@ import { HttpRequestQuery, HttpRequestUser, } from '@azure/functions'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; +import { RpcHttpData, RpcTypedData } from '@azure/functions-core'; import { HeaderName } from '../constants'; import { fromTypedData } from '../converters/RpcConverters'; import { fromNullableMapping, fromRpcHttpBody } from '../converters/RpcHttpConverters'; @@ -29,15 +29,15 @@ export class Request implements HttpRequest { #cachedUser?: HttpRequestUser | null; - constructor(rpcHttp: rpc.IRpcHttp) { + constructor(rpcHttp: RpcHttpData) { this.method = rpcHttp.method; this.url = rpcHttp.url; this.originalUrl = rpcHttp.url; this.headers = fromNullableMapping(rpcHttp.nullableHeaders, rpcHttp.headers); this.query = fromNullableMapping(rpcHttp.nullableQuery, rpcHttp.query); this.params = fromNullableMapping(rpcHttp.nullableParams, rpcHttp.params); - this.body = fromTypedData(rpcHttp.body); - this.rawBody = fromRpcHttpBody(rpcHttp.body); + this.body = fromTypedData(rpcHttp.body); + this.rawBody = fromRpcHttpBody(rpcHttp.body); } get user(): HttpRequestUser | null { diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..12a1c1a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import * as coreTypes from '@azure/functions-core'; +import { CoreInvocationContext, setProgrammingModel } from '@azure/functions-core'; +import { version } from './constants'; +import { InvocationModel } from './InvocationModel'; + +class ProgrammingModel implements coreTypes.ProgrammingModel { + name = '@azure/functions'; + version = version; + getInvocationModel(coreCtx: CoreInvocationContext): InvocationModel { + return new InvocationModel(coreCtx); + } +} + +export function setup() { + setProgrammingModel(new ProgrammingModel()); +} diff --git a/src/loadScriptFile.ts b/src/loadScriptFile.ts deleted file mode 100644 index 4a28121..0000000 --- a/src/loadScriptFile.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as url from 'url'; -import { PackageJson } from './parsers/parsePackageJson'; -import { InternalException } from './utils/InternalException'; - -export async function loadScriptFile(filePath: string, packageJson: PackageJson): Promise { - let script: unknown; - if (isESModule(filePath, packageJson)) { - const fileUrl = url.pathToFileURL(filePath); - if (fileUrl.href) { - // use eval so it doesn't get compiled into a require() - script = await eval('import(fileUrl.href)'); - } else { - throw new InternalException(`'${filePath}' could not be converted to file URL (${fileUrl.href})`); - } - } else { - script = require(/* webpackIgnore: true */ filePath); - } - return script; -} - -export function isESModule(filePath: string, packageJson: PackageJson): boolean { - if (filePath.endsWith('.mjs')) { - return true; - } - if (filePath.endsWith('.cjs')) { - return false; - } - return packageJson.type === 'module'; -} diff --git a/src/nodejsWorker.ts b/src/nodejsWorker.ts deleted file mode 100644 index 76621bb..0000000 --- a/src/nodejsWorker.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -const logPrefix = 'LanguageWorkerConsoleLog'; -const errorPrefix = logPrefix + '[error] '; -const warnPrefix = logPrefix + '[warn] '; -const supportedVersions: string[] = ['v14', 'v16', 'v18']; -const devOnlyVersions: string[] = ['v15', 'v17']; -let worker; - -// Try validating node version -// NOTE: This method should be manually tested if changed as it is in a sensitive code path -// and is JavaScript that runs on at least node version 0.10.28 -function validateNodeVersion(version: string) { - let errorMessage: string | undefined; - let warningMessage: string | undefined; - try { - const versionSplit = version.split('.'); - const major = versionSplit[0]; - // process.version returns invalid output - if (versionSplit.length != 3) { - errorMessage = "Could not parse Node.js version: '" + version + "'"; - // Unsupported version note: Documentation about Node's stable versions here: https://github.com/nodejs/Release#release-plan and an explanation here: https://medium.com/swlh/understanding-how-node-releases-work-in-2018-6fd356816db4 - } else if (process.env.AZURE_FUNCTIONS_ENVIRONMENT == 'Development' && devOnlyVersions.indexOf(major) >= 0) { - warningMessage = - 'Node.js version used (' + - version + - ') is not officially supported. You may use it during local development, but must use an officially supported version on Azure:' + - ' https://aka.ms/functions-node-versions'; - } else if (supportedVersions.indexOf(major) < 0) { - errorMessage = - 'Incompatible Node.js version' + - ' (' + - version + - ').' + - ' Refer to our documentation to see the Node.js versions supported by each version of Azure Functions: https://aka.ms/functions-node-versions'; - } - // Unknown error - } catch (err) { - const unknownError = 'Error in validating Node.js version. '; - console.error(errorPrefix + unknownError + err); - throw unknownError + err; - } - // Throw error for known version errors - if (errorMessage) { - console.error(errorPrefix + errorMessage); - throw new Error(errorMessage); - } - if (warningMessage) { - console.warn(warnPrefix + warningMessage); - } -} - -validateNodeVersion(process.version); - -// Try requiring bundle -try { - worker = require('./worker-bundle.js'); - worker = worker.worker; -} catch (err) { - console.log(logPrefix + "Couldn't require bundle, falling back to Worker.js. " + err); - worker = require('./Worker.js'); -} - -worker.startNodeWorker(process.argv); diff --git a/src/parsers/parsePackageJson.ts b/src/parsers/parsePackageJson.ts deleted file mode 100644 index 9cad9be..0000000 --- a/src/parsers/parsePackageJson.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { pathExists, readJson } from 'fs-extra'; -import * as path from 'path'; -import { ensureErrorType } from '../utils/ensureErrorType'; - -export interface PackageJson { - type?: string; - main?: string; -} - -/** - * @returns A parsed & sanitized package.json - */ -export async function parsePackageJson(dir: string): Promise { - try { - const filePath = path.join(dir, 'package.json'); - if (!(await pathExists(filePath))) { - throw new Error('file does not exist'); - } - - const data: unknown = await readJson(filePath); - if (typeof data !== 'object' || data === null || Array.isArray(data)) { - throw new Error('file content is not an object'); - } - - const stringFields = ['main', 'type']; - for (const field of stringFields) { - if (field in data && typeof data[field] !== 'string') { - // ignore fields with an unexpected type - delete data[field]; - } - } - return data; - } catch (err) { - const error: Error = ensureErrorType(err); - if (error.name === 'SyntaxError') { - error.message = `file content is not valid JSON: ${error.message}`; - } - throw error; - } -} diff --git a/src/setupCoreModule.ts b/src/setupCoreModule.ts deleted file mode 100644 index 3b16c3a..0000000 --- a/src/setupCoreModule.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { HookCallback } from '@azure/functions-core'; -import { version } from './constants'; -import { Disposable } from './Disposable'; -import { WorkerChannel } from './WorkerChannel'; -import Module = require('module'); - -/** - * Intercepts the default "require" method so that we can provide our own "built-in" module - * This module is essentially the publicly accessible API for our worker - * This module is available to users only at runtime, not as an installable npm package - */ -export function setupCoreModule(channel: WorkerChannel): void { - const coreApi = { - registerHook: (hookName: string, callback: HookCallback) => channel.registerHook(hookName, callback), - Disposable, - version: version, - }; - - Module.prototype.require = new Proxy(Module.prototype.require, { - apply(target, thisArg, argArray) { - if (argArray[0] === '@azure/functions-core') { - return coreApi; - } else { - return Reflect.apply(target, thisArg, argArray); - } - }, - }); -} diff --git a/src/setupEventStream.ts b/src/setupEventStream.ts deleted file mode 100644 index 735daa5..0000000 --- a/src/setupEventStream.ts +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; -import { EventHandler, SupportedRequest } from './eventHandlers/EventHandler'; -import { FunctionEnvironmentReloadHandler } from './eventHandlers/FunctionEnvironmentReloadHandler'; -import { FunctionLoadHandler } from './eventHandlers/FunctionLoadHandler'; -import { InvocationHandler } from './eventHandlers/InvocationHandler'; -import { WorkerInitHandler } from './eventHandlers/WorkerInitHandler'; -import { ensureErrorType } from './utils/ensureErrorType'; -import { InternalException } from './utils/InternalException'; -import { systemError } from './utils/Logger'; -import { nonNullProp } from './utils/nonNull'; -import { WorkerChannel } from './WorkerChannel'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -/** - * Configures handlers for incoming gRPC messages on the client - * - * This should have a way to handle all incoming gRPC messages. - * This includes all incoming StreamingMessage types (exclude *Response types and RpcLog type) - */ -export function setupEventStream(workerId: string, channel: WorkerChannel): void { - channel.eventStream.on('data', (msg) => { - void handleMessage(workerId, channel, msg); - }); - - channel.eventStream.on('error', function (err) { - systemError(`Worker ${workerId} encountered event stream error: `, err); - throw new InternalException(err); - }); - - // wrap event stream write to validate message correctness - const oldWrite = channel.eventStream.write; - channel.eventStream.write = function checkWrite(msg) { - const msgError = rpc.StreamingMessage.verify(msg); - if (msgError) { - systemError(`Worker ${workerId} malformed message`, msgError); - throw new InternalException(msgError); - } - oldWrite.apply(channel.eventStream, [msg]); - }; -} - -async function handleMessage(workerId: string, channel: WorkerChannel, inMsg: rpc.StreamingMessage): Promise { - const outMsg: rpc.IStreamingMessage = { - requestId: inMsg.requestId, - }; - - let eventHandler: EventHandler | undefined; - let request: SupportedRequest | undefined; - try { - const eventName = inMsg.content; - switch (eventName) { - case 'functionEnvironmentReloadRequest': - eventHandler = new FunctionEnvironmentReloadHandler(); - break; - case 'functionLoadRequest': - eventHandler = new FunctionLoadHandler(); - break; - case 'invocationRequest': - eventHandler = new InvocationHandler(); - break; - case 'workerInitRequest': - eventHandler = new WorkerInitHandler(); - break; - case 'workerStatusRequest': - // Worker sends the host empty response to evaluate the worker's latency - // The response doesn't even allow a `result` property, which is why we don't implement an EventHandler class - outMsg.workerStatusResponse = {}; - channel.eventStream.write(outMsg); - return; - case 'closeSharedMemoryResourcesRequest': - case 'fileChangeEventRequest': - case 'functionLoadRequestCollection': - case 'functionsMetadataRequest': - case 'invocationCancel': - case 'startStream': - case 'workerHeartbeat': - case 'workerTerminate': - // Not yet implemented - return; - default: - throw new InternalException(`Worker ${workerId} had no handler for message '${eventName}'`); - } - - request = nonNullProp(inMsg, eventName); - const response = await eventHandler.handleEvent(channel, request); - response.result = { status: rpc.StatusResult.Status.Success }; - outMsg[eventHandler.responseName] = response; - } catch (err) { - const error = ensureErrorType(err); - if (error.isAzureFunctionsInternalException) { - channel.log({ - message: error.message, - level: LogLevel.Error, - logCategory: LogCategory.System, - }); - } - - if (eventHandler && request) { - const response = eventHandler.getDefaultResponse(request); - response.result = { - status: rpc.StatusResult.Status.Failure, - exception: { - message: error.message, - stackTrace: error.stack, - }, - }; - outMsg[eventHandler.responseName] = response; - } - } - - if (eventHandler) { - channel.eventStream.write(outMsg); - } -} diff --git a/src/startApp.ts b/src/startApp.ts deleted file mode 100644 index 3e03c25..0000000 --- a/src/startApp.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AppStartContext } from '@azure/functions-core'; -import { pathExists } from 'fs-extra'; -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; -import { loadScriptFile } from './loadScriptFile'; -import { ensureErrorType } from './utils/ensureErrorType'; -import { nonNullProp } from './utils/nonNull'; -import { WorkerChannel } from './WorkerChannel'; -import path = require('path'); -import LogLevel = rpc.RpcLog.Level; -import LogCategory = rpc.RpcLog.RpcLogCategory; - -/** - * Starting an app can happen in two places, depending on if the worker was specialized or not - * 1. The worker can start in "normal" mode, meaning `workerInitRequest` will reference the user's app - * 2. The worker can start in "placeholder" mode, meaning `workerInitRequest` will reference a dummy app to "warm up" the worker and `functionEnvironmentReloadRequest` will be sent with the user's actual app. - * This process is called worker specialization and it helps with cold start times. - * The dummy app should never have actual startup code, so it should be safe to call `startApp` twice in this case - * Worker specialization happens only once, so we don't need to worry about cleaning up resources from previous `functionEnvironmentReloadRequest`s. - */ -export async function startApp(functionAppDirectory: string, channel: WorkerChannel): Promise { - await channel.updatePackageJson(functionAppDirectory); - await loadEntryPointFile(functionAppDirectory, channel); - const appStartContext: AppStartContext = { - hookData: channel.appLevelOnlyHookData, - appHookData: channel.appHookData, - functionAppDirectory, - hostVersion: nonNullProp(channel, 'hostVersion'), - }; - await channel.executeHooks('appStart', appStartContext); -} - -async function loadEntryPointFile(functionAppDirectory: string, channel: WorkerChannel): Promise { - const entryPointFile = channel.packageJson.main; - if (entryPointFile) { - channel.log({ - message: `Loading entry point "${entryPointFile}"`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }); - try { - const entryPointFullPath = path.join(functionAppDirectory, entryPointFile); - if (!(await pathExists(entryPointFullPath))) { - throw new Error(`file does not exist`); - } - - await loadScriptFile(entryPointFullPath, channel.packageJson); - channel.log({ - message: `Loaded entry point "${entryPointFile}"`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }); - } catch (err) { - const error = ensureErrorType(err); - error.isAzureFunctionsInternalException = true; - error.message = `Worker was unable to load entry point "${entryPointFile}": ${error.message}`; - throw error; - } - } -} diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts deleted file mode 100644 index 55f26e2..0000000 --- a/src/utils/Logger.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -/** - * Use these methods only if you want to guarantee the messages reach the host despite potential performance impact. - * Otherwise, please stick to utilizing the gRPC channel to propagate these messages with category: RpcLogCategory.System - **/ - -const logPrefix = 'LanguageWorkerConsoleLog'; - -export function systemLog(message?: any, ...optionalParams: any[]) { - console.log(logPrefix + removeNewLines(message), ...optionalParams); -} - -export function systemWarn(message?: any, ...optionalParams: any[]) { - console.warn(logPrefix + '[warn] ' + removeNewLines(message), ...optionalParams); -} - -export function systemError(message?: any, ...optionalParams: any[]) { - console.error(logPrefix + '[error] ' + removeNewLines(message), ...optionalParams); -} - -function removeNewLines(message?: any): string { - if (message && typeof message === 'string') { - message = message.replace(/(\r\n|\n|\r)/gm, ' '); - } - return message; -} diff --git a/src/utils/blockedMonitor.ts b/src/utils/blockedMonitor.ts deleted file mode 100644 index c408f6a..0000000 --- a/src/utils/blockedMonitor.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { AzureFunctionsRpcMessages as rpc } from './../../azure-functions-language-worker-protobuf/src/rpc'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; -import blockedAt = require('blocked-at'); - -export function startBlockedMonitor( - channel: { log: (log: rpc.IRpcLog) => void }, - threshold = 500, - intreval = 10000 -): NodeJS.Timer { - function logBlockedWarning(message: string) { - channel.log({ - message, - level: LogLevel.Warning, - logCategory: LogCategory.System, - }); - } - - logBlockedWarning( - `Monitoring for blocking code is turned on, with a threshold of ${threshold} ms. This will have a negative impact on performance. Adjust "AZURE_FUNCTIONS_NODE_BLOCK_LOG" to turn it off. ` + - 'IMPORTANT NOTE: The stack traces are only an approximation and you should analyze all synchronous operations' - ); - - let blockedHistory: { time: string; duration: number; stack: string[] }[] = []; - - //threshold - minimum miliseconds of blockage to report. - //other parameters are default, more details on https://github.com/naugtur/blocked-at. - blockedAt( - (ms, stack) => { - const date = new Date(); - blockedHistory.push({ time: date.toISOString(), duration: ms, stack: stack }); - }, - { threshold: threshold } - ); - - // Log blockedHistory if it's not empty each 10 seconds - return setInterval(() => { - if (blockedHistory.length > 0) { - logBlockedWarning(`Blocking code monitoring history: ${JSON.stringify(blockedHistory)}`); - blockedHistory = []; - } - }, intreval); -} diff --git a/test/Context.test.ts b/test/Context.test.ts index 67392f3..65e4af5 100644 --- a/test/Context.test.ts +++ b/test/Context.test.ts @@ -1,14 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. +import { RpcInvocationRequest, RpcParameterBinding } from '@azure/functions-core'; import { expect } from 'chai'; import 'mocha'; import * as sinon from 'sinon'; -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; import { CreateContextAndInputs } from '../src/Context'; import { FunctionInfo } from '../src/FunctionInfo'; -const timerTriggerInput: rpc.IParameterBinding = { +const timerTriggerInput: RpcParameterBinding = { name: 'myTimer', data: { json: JSON.stringify({ @@ -25,13 +25,15 @@ const timerTriggerInput: rpc.IParameterBinding = { describe('Context', () => { let _logger: any; + let doneEmitter: any; beforeEach(() => { _logger = sinon.spy(); + doneEmitter = sinon.spy(); }); it('camelCases timer trigger input when appropriate', async () => { - const msg: rpc.IInvocationRequest = { + const msg: RpcInvocationRequest = { functionId: 'id', invocationId: '1', inputData: [timerTriggerInput], @@ -47,7 +49,7 @@ describe('Context', () => { }, }, }); - const workerOutputs = CreateContextAndInputs(info, msg, _logger); + const workerOutputs = CreateContextAndInputs(info, msg, _logger, doneEmitter); const myTimerWorker = workerOutputs.inputs[0]; expect(myTimerWorker.schedule).to.be.empty; expect(myTimerWorker.scheduleStatus.last).to.equal('2016-10-04T10:15:00+00:00'); @@ -57,7 +59,7 @@ describe('Context', () => { }); it('Does not add sys to bindingData for non-http', async () => { - const msg: rpc.IInvocationRequest = { + const msg: RpcInvocationRequest = { functionId: 'id', invocationId: '1', inputData: [timerTriggerInput], @@ -74,14 +76,14 @@ describe('Context', () => { }, }); - const { context } = CreateContextAndInputs(info, msg, _logger); + const { context } = CreateContextAndInputs(info, msg, _logger, doneEmitter); expect(context.bindingData.sys).to.be.undefined; expect(context.bindingData.invocationId).to.equal('1'); expect(context.invocationId).to.equal('1'); }); it('Adds correct sys properties for bindingData and http', async () => { - const inputDataValue: rpc.IParameterBinding = { + const inputDataValue: RpcParameterBinding = { name: 'req', data: { http: { @@ -91,7 +93,7 @@ describe('Context', () => { }, }, }; - const msg: rpc.IInvocationRequest = { + const msg: RpcInvocationRequest = { functionId: 'id', invocationId: '1', inputData: [inputDataValue], @@ -108,7 +110,7 @@ describe('Context', () => { }, }); - const { context } = CreateContextAndInputs(info, msg, _logger); + const { context } = CreateContextAndInputs(info, msg, _logger, doneEmitter); const { bindingData } = context; expect(bindingData.sys.methodName).to.equal('test'); expect(bindingData.sys.randGuid).to.not.be.undefined; @@ -118,7 +120,7 @@ describe('Context', () => { }); it('Adds correct header and query properties for bindingData and http using nullable values', async () => { - const inputDataValue: rpc.IParameterBinding = { + const inputDataValue: RpcParameterBinding = { name: 'req', data: { http: { @@ -144,7 +146,7 @@ describe('Context', () => { }, }, }; - const msg: rpc.IInvocationRequest = { + const msg: RpcInvocationRequest = { functionId: 'id', invocationId: '1', inputData: [inputDataValue], @@ -161,7 +163,7 @@ describe('Context', () => { }, }); - const { context } = CreateContextAndInputs(info, msg, _logger); + const { context } = CreateContextAndInputs(info, msg, _logger, doneEmitter); const { bindingData } = context; expect(bindingData.invocationId).to.equal('1'); expect(bindingData.headers.header1).to.equal('value1'); @@ -172,7 +174,7 @@ describe('Context', () => { }); it('Adds correct header and query properties for bindingData and http using non-nullable values', async () => { - const inputDataValue: rpc.IParameterBinding = { + const inputDataValue: RpcParameterBinding = { name: 'req', data: { http: { @@ -188,7 +190,7 @@ describe('Context', () => { }, }, }; - const msg: rpc.IInvocationRequest = { + const msg: RpcInvocationRequest = { functionId: 'id', invocationId: '1', inputData: [inputDataValue], @@ -205,7 +207,7 @@ describe('Context', () => { }, }); - const { context } = CreateContextAndInputs(info, msg, _logger); + const { context } = CreateContextAndInputs(info, msg, _logger, doneEmitter); const { bindingData } = context; expect(bindingData.invocationId).to.equal('1'); expect(bindingData.headers.header1).to.equal('value1'); diff --git a/test/FunctionInfo.test.ts b/test/FunctionInfo.test.ts index 2b4af52..ca020d7 100644 --- a/test/FunctionInfo.test.ts +++ b/test/FunctionInfo.test.ts @@ -1,15 +1,15 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. +import { RpcFunctionMetadata } from '@azure/functions-core'; import { expect } from 'chai'; import 'mocha'; -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; import { FunctionInfo } from '../src/FunctionInfo'; describe('FunctionInfo', () => { /** NullableBool */ it('gets $return output binding converter for http', () => { - const metadata: rpc.IRpcFunctionMetadata = { + const metadata: RpcFunctionMetadata = { bindings: { req: { type: 'httpTrigger', @@ -29,7 +29,7 @@ describe('FunctionInfo', () => { }); it('"hasHttpTrigger" is true for http', () => { - const metadata: rpc.IRpcFunctionMetadata = { + const metadata: RpcFunctionMetadata = { bindings: { req: { type: 'httpTrigger', @@ -45,7 +45,7 @@ describe('FunctionInfo', () => { }); it('gets $return output binding converter for TypedData', () => { - const metadata: rpc.IRpcFunctionMetadata = { + const metadata: RpcFunctionMetadata = { bindings: { input: { type: 'queue', diff --git a/test/FunctionLoader.test.ts b/test/FunctionLoader.test.ts deleted file mode 100644 index cfebdfd..0000000 --- a/test/FunctionLoader.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -import 'mocha'; -import * as mock from 'mock-require'; -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; -import { FunctionLoader } from '../src/FunctionLoader'; -const expect = chai.expect; -chai.use(chaiAsPromised); - -describe('FunctionLoader', () => { - let loader: FunctionLoader; - let context, logs; - - beforeEach(() => { - loader = new FunctionLoader(); - logs = []; - context = { - _inputs: [], - bindings: {}, - log: (message) => logs.push(message), - bind: (val, cb) => { - cb && cb(val); - }, - }; - }); - - it('throws unable to determine function entry point', async () => { - mock('test', {}); - await expect( - loader.load( - 'functionId', - { - scriptFile: 'test', - }, - {} - ) - ).to.be.rejectedWith( - "Unable to determine function entry point. If multiple functions are exported, you must indicate the entry point, either by naming it 'run' or 'index', or by naming it explicitly via the 'entryPoint' metadata property." - ); - }); - - it('does not load proxy function', async () => { - mock('test', {}); - await loader.load( - 'functionId', - { - isProxy: true, - }, - {} - ); - - expect(() => { - loader.getFunc('functionId'); - }).to.throw("Function code for 'functionId' is not loaded and cannot be invoked."); - }); - - it('throws unable to determine function entry point with entryPoint name', async () => { - mock('test', { test: {} }); - const entryPoint = 'wrongEntryPoint'; - await expect( - loader.load( - 'functionId', - { - scriptFile: 'test', - entryPoint: entryPoint, - }, - {} - ) - ).to.be.rejectedWith( - `Unable to determine function entry point: ${entryPoint}. If multiple functions are exported, you must indicate the entry point, either by naming it 'run' or 'index', or by naming it explicitly via the 'entryPoint' metadata property.` - ); - }); - - it('throws the resolved entry point is not a function', async () => { - mock('test', { test: {} }); - const entryPoint = 'test'; - await expect( - loader.load( - 'functionId', - { - scriptFile: 'test', - entryPoint: entryPoint, - }, - {} - ) - ).to.be.rejectedWith( - 'The resolved entry point is not a function and cannot be invoked by the functions runtime. Make sure the function has been correctly exported.' - ); - }); - - it("allows use of 'this' in loaded user function", async () => { - const FuncObject = /** @class */ (function () { - function FuncObject(this: any) { - this.prop = true; - } - FuncObject.prototype.index = function (ctx) { - ctx.bindings.prop = this.test(); - // eslint-disable-next-line deprecation/deprecation - ctx.done(); - }; - FuncObject.prototype.test = function () { - return this.prop; - }; - return FuncObject; - })(); - - mock('test', new FuncObject()); - - await loader.load( - 'functionId', - { - scriptFile: 'test', - entryPoint: 'test', - }, - {} - ); - - const userFunction = loader.getFunc('functionId'); - - userFunction(context, (results) => { - expect(results).to.eql({ prop: true }); - }); - }); - - it('allows to return a promise from async user function', async () => { - mock('test', { test: async () => {} }); - - await loader.load( - 'functionId', - { - scriptFile: 'test', - entryPoint: 'test', - }, - {} - ); - - const userFunction = loader.getFunc('functionId'); - const result = userFunction(); - - expect(result).to.be.not.an('undefined'); - expect(result.then).to.be.a('function'); - }); - - it("function returned is a clone so that it can't affect other executions", async () => { - mock('test', { test: async () => {} }); - - await loader.load( - 'functionId', - { - scriptFile: 'test', - entryPoint: 'test', - }, - {} - ); - - const userFunction = loader.getFunc('functionId'); - Object.assign(userFunction, { hello: 'world' }); - - const userFunction2 = loader.getFunc('functionId'); - - expect(userFunction).to.not.equal(userFunction2); - expect(userFunction['hello']).to.equal('world'); - expect(userFunction2['hello']).to.be.undefined; - }); - - afterEach(() => { - mock.stopAll(); - }); -}); diff --git a/test/Types.test.ts b/test/Types.test.ts index e4bd8ec..1e58961 100644 --- a/test/Types.test.ts +++ b/test/Types.test.ts @@ -4,12 +4,12 @@ import { expect } from 'chai'; import * as cp from 'child_process'; import 'mocha'; -import { ITestCallbackContext } from 'mocha'; +import { Context } from 'mocha'; import * as path from 'path'; describe('Public TypeScript types', () => { for (const tsVersion of ['3', '4']) { - it(`builds with TypeScript v${tsVersion}`, async function (this: ITestCallbackContext) { + it(`builds with TypeScript v${tsVersion}`, async function (this: Context) { this.timeout(10 * 1000); expect(await runTsBuild(tsVersion)).to.equal(0); }); @@ -17,7 +17,7 @@ describe('Public TypeScript types', () => { }); async function runTsBuild(tsVersion: string): Promise { - const repoRoot = path.join(__dirname, '..'); + const repoRoot = path.join(__dirname, '..', '..'); const tscPath = path.join(repoRoot, 'node_modules', `typescript${tsVersion}`, 'bin', 'tsc'); const projectFile = path.join(repoRoot, 'types', 'tsconfig.json'); return new Promise((resolve, reject) => { diff --git a/test/Worker.test.ts b/test/Worker.test.ts deleted file mode 100644 index a65cc72..0000000 --- a/test/Worker.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import 'mocha'; -import { startNodeWorker } from '../src/Worker'; - -describe('Worker', () => { - it('throws error on incorrect args: grpcMaxMessageLength 0', () => { - const args = [ - '/node', - 'nodejsWorker.js', - '--host', - '120.0.0.0', - '--port', - '8000', - '--workerId', - 'bd2e3e80-46ba', - '--requestId', - 'bd2e3e80-46ba', - '--grpcMaxMessageLength', - '0', - ]; - expect(() => { - startNodeWorker(args); - }).to.throw("gRPC client connection info is missing or incorrect ('grpcMaxMessageLength' is 0)."); - }); - - it('throws error on incorrect args: grpcMaxMessageLength 0 and null requestId', () => { - const args = [ - '/node', - 'nodejsWorker.js', - '--host', - '120.0.0.0', - '--port', - '8000', - '--workerId', - 'bd2e3e80-46ba', - '--grpcMaxMessageLength', - '0', - ]; - expect(() => { - startNodeWorker(args); - }).to.throw( - "gRPC client connection info is missing or incorrect ('requestId' is undefined, 'grpcMaxMessageLength' is 0)." - ); - }); -}); diff --git a/test/blockMonitorTest.ts b/test/blockMonitorTest.ts deleted file mode 100644 index 79d7915..0000000 --- a/test/blockMonitorTest.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import 'mocha'; -import { AzureFunctionsRpcMessages as rpc } from './../azure-functions-language-worker-protobuf/src/rpc'; -import { startBlockedMonitor } from './../src/utils/blockedMonitor'; -import LogLevel = rpc.RpcLog.Level; - -describe('Event loop blocking operation monitor', () => { - it('startBlockMonitor logs warning', async () => { - console.log('start ' + new Date().getSeconds() + ':' + new Date().getMilliseconds()); - let timer: NodeJS.Timer | null = null; - let isTimerDestroyed = false; - const logFun = function (log: rpc.IRpcLog): void { - expect(log.level).to.equal(LogLevel.Warning); - if (log.message && log.message.startsWith('Blocking code monitoring history')) { - if (timer) { - clearInterval(timer); - isTimerDestroyed = true; - } - } - }; - - timer = startBlockedMonitor({ log: logFun }, 100, 100); - await new Promise((resolve) => { - //Adding new event to event loop to start monitoring - setTimeout(() => { - resolve(true); - }, 1); - }); - const end = Date.now() + 500; - while (Date.now() < end) {} // blocking code - - await new Promise((resolve) => { - //assert - setTimeout(() => { - if (isTimerDestroyed) { - resolve(true); - } - }, 500); - }); - }); -}); diff --git a/test/converters/BindingConverters.test.ts b/test/converters/BindingConverters.test.ts index d6f8025..45196cc 100644 --- a/test/converters/BindingConverters.test.ts +++ b/test/converters/BindingConverters.test.ts @@ -1,10 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. +import { RpcBindingInfo, RpcFunctionMetadata, RpcInvocationRequest, RpcTypedData } from '@azure/functions-core'; import { expect } from 'chai'; import { fromString } from 'long'; import 'mocha'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; import { getBindingDefinitions, getNormalizedBindingData } from '../../src/converters/BindingConverters'; import { fromTypedData } from '../../src/converters/RpcConverters'; import { toRpcHttp } from '../../src/converters/RpcHttpConverters'; @@ -12,8 +12,8 @@ import { FunctionInfo } from '../../src/FunctionInfo'; describe('Binding Converters', () => { it('normalizes binding trigger metadata for HTTP', () => { - const mockRequest: rpc.ITypedData = toRpcHttp({ url: 'https://mock' }); - const triggerDataMock: { [k: string]: rpc.ITypedData } = { + const mockRequest: RpcTypedData = toRpcHttp({ url: 'https://mock' }); + const triggerDataMock: { [k: string]: RpcTypedData } = { Headers: { json: JSON.stringify({ Connection: 'Keep-Alive' }), }, @@ -41,7 +41,7 @@ describe('Binding Converters', () => { }, }; - const request: rpc.IInvocationRequest = { + const request: RpcInvocationRequest = { triggerMetadata: triggerDataMock, invocationId: '12341', }; @@ -66,7 +66,7 @@ describe('Binding Converters', () => { }); it('normalizes binding trigger metadata containing arrays', () => { - const triggerDataMock: { [k: string]: rpc.ITypedData } = { + const triggerDataMock: { [k: string]: RpcTypedData } = { EnqueuedMessages: { json: JSON.stringify(['Hello 1', 'Hello 2']), }, @@ -80,7 +80,7 @@ describe('Binding Converters', () => { json: JSON.stringify({ MethodName: 'test-js', UtcNow: '2018', RandGuid: '3212' }), }, }; - const request: rpc.IInvocationRequest = { + const request: RpcInvocationRequest = { triggerMetadata: triggerDataMock, invocationId: '12341', }; @@ -111,22 +111,22 @@ describe('Binding Converters', () => { }); it('catologues binding definitions', () => { - const functionMetaData: rpc.IRpcFunctionMetadata = { + const functionMetaData: RpcFunctionMetadata = { name: 'MyFunction', directory: '.', scriptFile: 'index.js', bindings: { req: { type: 'httpTrigger', - direction: rpc.BindingInfo.Direction.in, + direction: RpcBindingInfo.Direction.in, }, res: { type: 'http', - direction: rpc.BindingInfo.Direction.out, + direction: RpcBindingInfo.Direction.out, }, firstQueueOutput: { type: 'queue', - direction: rpc.BindingInfo.Direction.out, + direction: RpcBindingInfo.Direction.out, }, noDirection: { type: 'queue', diff --git a/test/converters/RpcConverters.test.ts b/test/converters/RpcConverters.test.ts index bd31a0c..b921c3c 100644 --- a/test/converters/RpcConverters.test.ts +++ b/test/converters/RpcConverters.test.ts @@ -1,9 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. +import { RpcTraceContext } from '@azure/functions-core'; import { expect } from 'chai'; import 'mocha'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; import { fromRpcTraceContext, toNullableBool, @@ -35,7 +35,7 @@ describe('Rpc Converters', () => { const tracestatevalue = 'traces'; const attributesvalue = { traceparent: 'traceparent', tracestate: 'tracestate' }; - const input = { + const input = { traceParent: traceparentvalue, traceState: tracestatevalue, attributes: attributesvalue, diff --git a/test/converters/RpcHttpConverters.test.ts b/test/converters/RpcHttpConverters.test.ts index d044103..0c5df45 100644 --- a/test/converters/RpcHttpConverters.test.ts +++ b/test/converters/RpcHttpConverters.test.ts @@ -2,9 +2,9 @@ // Licensed under the MIT License. import { Cookie } from '@azure/functions'; +import { RpcHttpCookie } from '@azure/functions-core'; import { expect } from 'chai'; import 'mocha'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; import { toRpcHttp, toRpcHttpCookieList } from '../../src/converters/RpcHttpConverters'; describe('Rpc Converters', () => { @@ -69,16 +69,16 @@ describe('Rpc Converters', () => { const rpcCookies = toRpcHttpCookieList(cookieInputs); expect(rpcCookies[0].name).to.equal('none-cookie'); - expect(rpcCookies[0].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.ExplicitNone); + expect(rpcCookies[0].sameSite).to.equal(RpcHttpCookie.SameSite.ExplicitNone); expect(rpcCookies[1].name).to.equal('lax-cookie'); - expect(rpcCookies[1].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.Lax); + expect(rpcCookies[1].sameSite).to.equal(RpcHttpCookie.SameSite.Lax); expect(rpcCookies[2].name).to.equal('strict-cookie'); - expect(rpcCookies[2].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.Strict); + expect(rpcCookies[2].sameSite).to.equal(RpcHttpCookie.SameSite.Strict); expect(rpcCookies[3].name).to.equal('default-cookie'); - expect(rpcCookies[3].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.None); + expect(rpcCookies[3].sameSite).to.equal(RpcHttpCookie.SameSite.None); }); it('throws on invalid input', () => { diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E.sln b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E.sln deleted file mode 100644 index 601ee0c..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2026 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Functions.NodejsWorker.E2E", "Azure.Functions.NodejsWorker.E2E\Azure.Functions.NodejsWorker.E2E.csproj", "{E7229DBA-DCF2-44B9-ACB8-D6E8AE906AB3}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E7229DBA-DCF2-44B9-ACB8-D6E8AE906AB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E7229DBA-DCF2-44B9-ACB8-D6E8AE906AB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E7229DBA-DCF2-44B9-ACB8-D6E8AE906AB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E7229DBA-DCF2-44B9-ACB8-D6E8AE906AB3}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {96355275-963C-4A13-92EB-5F7CB415269F} - EndGlobalSection -EndGlobal diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E.csproj b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E.csproj deleted file mode 100644 index 24077c3..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net6.0 - - false - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Constants.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Constants.cs deleted file mode 100644 index da2b6e9..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Constants.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - public static class Constants - { - public static string FunctionsHostUrl = Environment.GetEnvironmentVariable("FunctionAppUrl") ?? "http://localhost:7071"; - - //Queue tests - public static class Queue { - public static string StorageConnectionStringSetting = Environment.GetEnvironmentVariable("AzureWebJobsStorage"); - public static string OutputBindingName = "test-output-node"; - public static string InputBindingName = "test-input-node"; - } - - // CosmosDB tests - public static class CosmosDB { - public static string CosmosDBConnectionStringSetting = Environment.GetEnvironmentVariable("AzureWebJobsCosmosDBConnectionString"); - public static string DbName = "ItemDb"; - public static string InputCollectionName = "ItemCollectionIn"; - public static string OutputCollectionName = "ItemCollectionOut"; - public static string LeaseCollectionName = "leases"; - } - - // EventHubs - public static class EventHubs { - public static string EventHubsConnectionStringSetting = Environment.GetEnvironmentVariable("AzureWebJobsEventHubSender"); - - public static class Json_Test { - public static string OutputName = "test-output-object-node"; - public static string InputName = "test-input-object-node"; - } - - public static class String_Test { - public static string OutputName = "test-output-string-node"; - public static string InputName = "test-input-string-node"; - } - - public static class Cardinality_One_Test { - public static string InputName = "test-input-one-node"; - public static string OutputName = "test-output-one-node"; - } - } - - // Xunit Fixtures and Collections - public const string FunctionAppCollectionName = "FunctionAppCollection"; - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/CosmosDBEndToEndTests.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/CosmosDBEndToEndTests.cs deleted file mode 100644 index 79aad04..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/CosmosDBEndToEndTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using Xunit; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - [Collection(Constants.FunctionAppCollectionName)] - public class CosmosDBEndToEndTests - { - private readonly FunctionAppFixture _fixture; - - public CosmosDBEndToEndTests(FunctionAppFixture fixture) - { - this._fixture = fixture; - } - - [Fact] - public async Task CosmosDBTriggerAndOutput_Succeeds() - { - string expectedDocId = Guid.NewGuid().ToString(); - try - { - //Setup - await CosmosDBHelpers.CreateDocumentCollections(); - - //Trigger - await CosmosDBHelpers.CreateDocument(expectedDocId); - - //Read - var documentId = await CosmosDBHelpers.ReadDocument(expectedDocId); - Assert.Equal(expectedDocId, documentId); - } - finally - { - //Clean up - await CosmosDBHelpers.DeleteTestDocuments(expectedDocId); - } - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/EventHubsEndToEndTests.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/EventHubsEndToEndTests.cs deleted file mode 100644 index ac445c6..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/EventHubsEndToEndTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Xunit; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - [Collection(Constants.FunctionAppCollectionName)] - public class EventHubsEndToEndTests - { - private readonly FunctionAppFixture _fixture; - - public EventHubsEndToEndTests(FunctionAppFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public async Task EventHubTriggerAndOutputJSON_Succeeds() - { - string expectedEventId = Guid.NewGuid().ToString(); - try - { - await SetupQueue(Constants.EventHubs.Json_Test.OutputName); - - // Need to setup EventHubs: test-inputjson-java and test-outputjson-java - await EventHubsHelpers.SendJSONMessagesAsync(expectedEventId, Constants.EventHubs.Json_Test.InputName); - - //Verify - var queueMessage = await StorageHelpers.ReadFromQueue(Constants.EventHubs.Json_Test.OutputName); - JObject json = JObject.Parse(queueMessage); - Assert.Contains(expectedEventId, json["value"].ToString()); - } - finally - { - //Clear queue - await StorageHelpers.ClearQueue(Constants.EventHubs.Json_Test.OutputName); - } - } - - [Fact] - public async Task EventHubTriggerAndOutputString_Succeeds() - { - string expectedEventId = Guid.NewGuid().ToString(); - try - { - await SetupQueue(Constants.EventHubs.String_Test.OutputName); - - // Need to setup EventHubs: test-input-one-node - await EventHubsHelpers.SendMessagesAsync(expectedEventId, Constants.EventHubs.String_Test.InputName); - - //Verify - var queueMessage = await StorageHelpers.ReadFromQueue(Constants.EventHubs.String_Test.OutputName); - Assert.Contains(expectedEventId, queueMessage); - } - finally - { - //Clear queue - await StorageHelpers.ClearQueue(Constants.EventHubs.String_Test.OutputName); - } - } - - [Fact] - public async Task EventHubTriggerCardinalityOne_Succeeds() - { - string expectedEventId = Guid.NewGuid().ToString(); - try - { - await SetupQueue(Constants.EventHubs.Cardinality_One_Test.OutputName); - - // Need to setup EventHubs: test-inputOne-java and test-outputone-java - await EventHubsHelpers.SendMessagesAsync(expectedEventId, Constants.EventHubs.Cardinality_One_Test.InputName); - - //Verify - IEnumerable queueMessages = await StorageHelpers.ReadMessagesFromQueue(Constants.EventHubs.Cardinality_One_Test.OutputName); - Assert.True(queueMessages.All(msg => msg.Contains(expectedEventId))); - } - finally - { - //Clear queue - await StorageHelpers.ClearQueue(Constants.EventHubs.Cardinality_One_Test.OutputName); - } - } - - private static async Task SetupQueue(string queueName) - { - //Clear queue - await StorageHelpers.ClearQueue(queueName); - - //Set up and trigger - await StorageHelpers.CreateQueue(queueName); - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Fixtures/FixtureHelpers.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Fixtures/FixtureHelpers.cs deleted file mode 100644 index f902041..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Fixtures/FixtureHelpers.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - public static class FixtureHelpers - { - public static Process GetFuncHostProcess(bool enableAuth = false) - { - var funcProcess = new Process(); - var rootDir = Path.GetFullPath(Path.Combine("..", "..", "..", "..", "..", "..", "..")); - var funcName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "func.exe": "func"; - - funcProcess.StartInfo.UseShellExecute = false; - funcProcess.StartInfo.RedirectStandardError = true; - funcProcess.StartInfo.RedirectStandardOutput = true; - funcProcess.StartInfo.CreateNoWindow = true; - funcProcess.StartInfo.WorkingDirectory = Path.Combine(rootDir, "test", "end-to-end", "testFunctionApp"); - funcProcess.StartInfo.FileName = Path.Combine(rootDir, "Azure.Functions.Cli", funcName); - funcProcess.StartInfo.ArgumentList.Add("start"); - if (enableAuth) - { - funcProcess.StartInfo.ArgumentList.Add("--enableAuth"); - } - - return funcProcess; - } - - public static void StartProcessWithLogging(Process funcProcess) - { - funcProcess.ErrorDataReceived += (sender, e) => Console.WriteLine(e?.Data); - funcProcess.OutputDataReceived += (sender, e) => Console.WriteLine(e?.Data); - - funcProcess.Start(); - - funcProcess.BeginErrorReadLine(); - funcProcess.BeginOutputReadLine(); - } - - public static void KillExistingFuncHosts() - { - foreach (var func in Process.GetProcessesByName("func")) - { - func.Kill(); - } - } - } -} diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Fixtures/FunctionAppFixture.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Fixtures/FunctionAppFixture.cs deleted file mode 100644 index 26371f1..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Fixtures/FunctionAppFixture.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Diagnostics; -using System.Threading; -using Xunit; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - public class FunctionAppFixture : IDisposable - { - private readonly ILogger _logger; - private bool _disposed; - private Process _funcProcess; - - public FunctionAppFixture() - { - // initialize logging -#pragma warning disable CS0618 // Type or member is obsolete - ILoggerFactory loggerFactory = new LoggerFactory().AddConsole(); -#pragma warning restore CS0618 // Type or member is obsolete - _logger = loggerFactory.CreateLogger(); - - // start host via CLI if testing locally - if (Constants.FunctionsHostUrl.Contains("localhost")) - { - // kill existing func processes - _logger.LogInformation("Shutting down any running functions hosts.."); - FixtureHelpers.KillExistingFuncHosts(); - - // start functions process - _logger.LogInformation($"Starting functions host for {Constants.FunctionAppCollectionName}.."); - _funcProcess = FixtureHelpers.GetFuncHostProcess(); - - FixtureHelpers.StartProcessWithLogging(_funcProcess); - - Thread.Sleep(TimeSpan.FromSeconds(30)); - } - } - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - _logger.LogInformation("FunctionAppFixture disposing."); - - if (_funcProcess != null) - { - _logger.LogInformation($"Shutting down functions host for {Constants.FunctionAppCollectionName}"); - _funcProcess.Kill(); - _funcProcess.Dispose(); - } - } - - _disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - } - } - - [CollectionDefinition(Constants.FunctionAppCollectionName)] - public class FunctionAppCollection : ICollectionFixture - { - // This class has no code, and is never created. Its purpose is simply - // to be the place to apply [CollectionDefinition] and all the - // ICollectionFixture<> interfaces. - } -} diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/CosmosDBHelpers.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/CosmosDBHelpers.cs deleted file mode 100644 index f0f0ef4..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/CosmosDBHelpers.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - public class TestDocument - { - public string id { get; set; } - public string name { get; set; } - } - - public static class CosmosDBHelpers - { - private static DocumentClient _docDbClient; - private static Uri inputCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName); - private static Uri outputCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName); - private static Uri leasesCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.LeaseCollectionName); - - static CosmosDBHelpers() - { - var builder = new System.Data.Common.DbConnectionStringBuilder(); - builder.ConnectionString = Constants.CosmosDB.CosmosDBConnectionStringSetting; - var serviceUri = new Uri(builder["AccountEndpoint"].ToString()); - _docDbClient = new DocumentClient(serviceUri, builder["AccountKey"].ToString()); - } - - // keep - public async static Task CreateDocument(string docId) - { - Document documentToTest = new Document() - { - Id = docId - }; - - Document insertedDoc = await _docDbClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName), documentToTest); - } - - public async static Task CreateDocument(TestDocument testDocument) - { - Document insertedDoc = await _docDbClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName), testDocument); - } - - // keep - public async static Task ReadDocument(string docId) - { - var docUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName, docId); - Document retrievedDocument = null; - await Utilities.RetryAsync(async () => - { - try - { - retrievedDocument = await _docDbClient.ReadDocumentAsync(docUri, new RequestOptions { PartitionKey = new PartitionKey(docId) }); - return true; - } - catch (DocumentClientException ex) when (ex.Error.Code == "NotFound" || ex.Error.Code == "Not Found") - { - return false; - } - }, 120000, 4000); - return retrievedDocument.Id; - } - - // keep - public async static Task DeleteTestDocuments(string docId) - { - var inputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName, docId); - await DeleteDocument(inputDocUri); - var outputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName, docId); - await DeleteDocument(outputDocUri); - } - - private async static Task DeleteDocument(Uri docUri) - { - try - { - await _docDbClient.DeleteDocumentAsync(docUri); - } - catch (Exception) - { - //ignore - } - } - - // keep - public async static Task CreateDocumentCollections() - { - Database db = await _docDbClient.CreateDatabaseIfNotExistsAsync(new Database { Id = Constants.CosmosDB.DbName }); - Uri dbUri = UriFactory.CreateDatabaseUri(db.Id); - - await CreateCollection(dbUri, Constants.CosmosDB.InputCollectionName); - await CreateCollection(dbUri, Constants.CosmosDB.OutputCollectionName); - await CreateCollection(dbUri, Constants.CosmosDB.LeaseCollectionName); - - } - public async static Task DeleteDocumentCollections() - { - await DeleteCollection(inputCollectionsUri); - await DeleteCollection(outputCollectionsUri); - await DeleteCollection(leasesCollectionsUri); - } - - private async static Task DeleteCollection(Uri collectionUri) - { - try - { - await _docDbClient.DeleteDocumentCollectionAsync(collectionUri); - } - catch (Exception) - { - //Ignore - } - } - - private async static Task CreateCollection(Uri dbUri, string collectioName) - { - DocumentCollection collection = new DocumentCollection() { Id = collectioName }; - await _docDbClient.CreateDocumentCollectionIfNotExistsAsync(dbUri, collection, - new RequestOptions() - { - OfferThroughput = 400 - }); - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/EventHubsHelpers.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/EventHubsHelpers.cs deleted file mode 100644 index 74f393d..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/EventHubsHelpers.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Microsoft.Azure.EventHubs; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - public class EventHubsHelpers - { - public static async Task SendJSONMessagesAsync(string eventId, string eventHubName) - { - // write 3 events - List events = new List(); - string[] ids = new string[3]; - for (int i = 0; i < 3; i++) - { - ids[i] = eventId + $"TestEvent{i}"; - JObject jo = new JObject - { - { "value", ids[i] } - }; - var evt = new EventData(Encoding.UTF8.GetBytes(jo.ToString(Formatting.None))); - evt.Properties.Add("TestIndex", i); - events.Add(evt); - } - - EventHubsConnectionStringBuilder builder = new EventHubsConnectionStringBuilder(Constants.EventHubs.EventHubsConnectionStringSetting); - builder.EntityPath = eventHubName; - EventHubClient eventHubClient = EventHubClient.CreateFromConnectionString(builder.ToString()); - await eventHubClient.SendAsync(events); - } - - public static async Task SendMessagesAsync(string eventId, string evenHubName) - { - // write 3 events - List events = new List(); - string[] ids = new string[3]; - for (int i = 0; i < 3; i++) - { - ids[i] = eventId + $"TestEvent{i}"; - var evt = new EventData(Encoding.UTF8.GetBytes(ids[i])); - evt.Properties.Add("TestIndex", i); - events.Add(evt); - } - - EventHubsConnectionStringBuilder builder = new EventHubsConnectionStringBuilder(Constants.EventHubs.EventHubsConnectionStringSetting); - builder.EntityPath = evenHubName; - EventHubClient eventHubClient = EventHubClient.CreateFromConnectionString(builder.ToString()); - await eventHubClient.SendAsync(events); - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/HttpHelpers.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/HttpHelpers.cs deleted file mode 100644 index 356f52d..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/HttpHelpers.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - class HttpHelpers - { - public static async Task InvokeHttpTrigger(string functionName, string queryString = "") - { - // Basic http request - HttpRequestMessage request = GetTestRequest(functionName, queryString); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain")); - return await GetResponseMessage(request); - } - - public static async Task InvokeHttpTriggerWithBody(string functionName, string body, HttpStatusCode expectedStatusCode, string mediaType, int expectedCode = 0) - { - HttpRequestMessage request = GetTestRequest(functionName); - request.Content = new StringContent(body); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType)); - return await GetResponseMessage(request); - } - - private static HttpRequestMessage GetTestRequest(string functionName, string queryString = "") - { - return new HttpRequestMessage - { - RequestUri = new Uri($"{Constants.FunctionsHostUrl}/api/{functionName}{queryString}"), - Method = HttpMethod.Post - }; - } - - private static async Task GetResponseMessage(HttpRequestMessage request) - { - HttpResponseMessage response = null; - using (var httpClient = new HttpClient()) - { - response = await httpClient.SendAsync(request); - } - - return response; - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/StorageHelpers.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/StorageHelpers.cs deleted file mode 100644 index 6d361fb..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Helpers/StorageHelpers.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Queue; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - class StorageHelpers - { - public static CloudStorageAccount _storageAccount = CloudStorageAccount.Parse(Constants.Queue.StorageConnectionStringSetting); - public static CloudQueueClient _queueClient = _storageAccount.CreateCloudQueueClient(); - - public async static Task DeleteQueue(string queueName) - { - CloudQueue queue = _queueClient.GetQueueReference(queueName); - await queue.DeleteAsync(); - } - - public async static Task ClearQueue(string queueName) - { - CloudQueue queue = _queueClient.GetQueueReference(queueName); - if (await queue.ExistsAsync()) - { - await queue.ClearAsync(); - } - } - - public async static Task CreateQueue(string queueName) - { - CloudQueue queue = _queueClient.GetQueueReference(queueName); - await queue.CreateIfNotExistsAsync(); - } - - public async static Task InsertIntoQueue(string queueName, string queueMessage) - { - CloudQueue queue = _queueClient.GetQueueReference(queueName); - await queue.CreateIfNotExistsAsync(); - CloudQueueMessage message = new CloudQueueMessage(queueMessage); - await queue.AddMessageAsync(message); - return message.Id; - } - - public async static Task ReadFromQueue(string queueName) - { - CloudQueue queue = _queueClient.GetQueueReference(queueName); - CloudQueueMessage retrievedMessage = null; - await Utilities.RetryAsync(async () => - { - retrievedMessage = await queue.GetMessageAsync(); - return retrievedMessage != null; - }); - await queue.DeleteMessageAsync(retrievedMessage); - return retrievedMessage.AsString; - } - - public async static Task> ReadMessagesFromQueue(string queueName) - { - CloudQueue queue = _queueClient.GetQueueReference(queueName); - IEnumerable retrievedMessages = null; - List messages = new List(); - await Utilities.RetryAsync(async () => - { - retrievedMessages = await queue.GetMessagesAsync(3); - return retrievedMessages != null; - }); - foreach(CloudQueueMessage msg in retrievedMessages) - { - messages.Add(msg.AsString); - await queue.DeleteMessageAsync(msg); - } - return messages; - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/HttpEndToEndTests.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/HttpEndToEndTests.cs deleted file mode 100644 index 9c67d14..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/HttpEndToEndTests.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - [Collection(Constants.FunctionAppCollectionName)] - public class HttpEndToEndTests - { - private readonly FunctionAppFixture _fixture; - - public HttpEndToEndTests(FunctionAppFixture fixture) - { - _fixture = fixture; - } - - [Theory] - [InlineData("HttpTrigger", "?name=Test", HttpStatusCode.OK, "Hello Test")] - [InlineData("HttpTrigger", "?name=John&lastName=Doe", HttpStatusCode.OK, "Hello John")] - [InlineData("HttpTriggerThrows", "", HttpStatusCode.InternalServerError, "")] - [InlineData("HttpTrigger", "", HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")] - public async Task HttpTriggerTests(string functionName, string queryString, HttpStatusCode expectedStatusCode, string expectedMessage) - { - // TODO: Verify exception on 500 after https://github.com/Azure/azure-functions-host/issues/3589 - HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionName, queryString); - string actualMessage = await response.Content.ReadAsStringAsync(); - - Assert.Equal(expectedStatusCode, response.StatusCode); - - if (!string.IsNullOrEmpty(expectedMessage)) { - Assert.False(string.IsNullOrEmpty(actualMessage)); - Assert.Contains(expectedMessage, actualMessage); - } - } - - [Theory] - [InlineData("HttpTriggerESModules", "?name=Test", HttpStatusCode.OK, "Hello Test")] - [InlineData("HttpTriggerESModules", "?name=Marie&lastName=Hoeger", HttpStatusCode.OK, "Hello Marie")] - [InlineData("HttpTriggerESModules", "", HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")] - public async Task HttpTriggerESModuleTests(string functionName, string queryString, HttpStatusCode expectedStatusCode, string expectedMessage) - { - // TODO: Verify exception on 500 after https://github.com/Azure/azure-functions-host/issues/3589 - HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionName, queryString); - string actualMessage = await response.Content.ReadAsStringAsync(); - - Assert.Equal(expectedStatusCode, response.StatusCode); - - if (!string.IsNullOrEmpty(expectedMessage)) { - Assert.False(string.IsNullOrEmpty(actualMessage)); - Assert.Contains(expectedMessage, actualMessage); - } - } - - [Theory] - [InlineData("HttpTriggerBodyAndRawBody", "{\"a\":1}", "application/json", HttpStatusCode.OK)] - [InlineData("HttpTriggerBodyAndRawBody", "{\"a\":1, \"b\":}", "application/json", HttpStatusCode.OK)] - [InlineData("HttpTriggerBodyAndRawBody", "{\"a\":1}", "application/octet-stream", HttpStatusCode.OK)] - [InlineData("HttpTriggerBodyAndRawBody", "abc", "text/plain", HttpStatusCode.OK)] - - public async Task HttpTriggerTestsWithCustomMediaType(string functionName, string body, string mediaType, HttpStatusCode expectedStatusCode) - { - HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody(functionName, body, expectedStatusCode, mediaType); - JObject responseBody = JObject.Parse(await response.Content.ReadAsStringAsync()); - - Assert.Equal(expectedStatusCode, response.StatusCode); - VerifyBodyAndRawBody(responseBody, body, mediaType); - } - - [Fact] - public async Task HttpTriggerWithCookieTests() - { - HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("HttpTriggerSetsCookie"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - List cookies = response.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value.ToList(); - Assert.Equal(5, cookies.Count); - Assert.Equal("mycookie=myvalue; max-age=200000; path=/", cookies[0]); - Assert.Equal("mycookie2=myvalue; max-age=200000; path=/", cookies[1]); - Assert.Equal("mycookie3-expires=myvalue3-expires; max-age=0; path=/", cookies[2]); - Assert.Equal("mycookie4-samesite-lax=myvalue; path=/; samesite=lax", cookies[3]); - Assert.Equal("mycookie5-samesite-strict=myvalue; path=/; samesite=strict", cookies[4]); - // Assert.Equal("mycookie4-samesite-none=myvalue; path=/; samesite=none", cookies[5]); - } - - [Fact] - public async Task HttpTriggerBindingDataTests() - { - HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("HttpTriggerBindingData", "?stringInput=hello&emptyStringInput="); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - string responseBody = await response.Content.ReadAsStringAsync(); - Assert.Equal("binding data exists", responseBody); - } - - private static void VerifyBodyAndRawBody(JObject result, string input, string mediaType) - { - if (mediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase)) - { - try - { - Assert.Equal(input, (string)result["reqRawBody"]); - Assert.True(JToken.DeepEquals((JObject)result["reqBody"], JObject.Parse(input))); - } - catch (InvalidCastException) // Invalid JSON - { - Assert.Equal(input, (string)result["reqRawBody"]); - Assert.Equal(input, (string)result["reqBody"]); - } - } - else if (IsMediaTypeOctetOrMultipart(mediaType)) - { - JObject reqBody = (JObject)result["reqBody"]; - byte[] responseBytes = reqBody["data"].ToObject(); - Assert.True(responseBytes.SequenceEqual(Encoding.UTF8.GetBytes(input))); - Assert.Equal(input, (string)result["reqRawBody"]); - } - else if (mediaType.Equals("text/plain", StringComparison.OrdinalIgnoreCase)) - { - Assert.Equal(input, (string)result["reqRawBody"]); - Assert.Equal(input, (string)result["reqBody"]); - } else { - Assert.Equal("Supported media types are 'text/plain' 'application/octet-stream', 'multipart/*', and 'application/json'", $"Found mediaType '{mediaType}'"); - } - } - - private static bool IsMediaTypeOctetOrMultipart(string mediaType) - { - return mediaType != null && (string.Equals(mediaType, "application/octet-stream", StringComparison.OrdinalIgnoreCase) - || mediaType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0); - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/StorageEndToEndTests.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/StorageEndToEndTests.cs deleted file mode 100644 index 646a4fc..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/StorageEndToEndTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using Xunit; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - [Collection(Constants.FunctionAppCollectionName)] - public class StorageEndToEndTests - { - private FunctionAppFixture _fixture; - - public StorageEndToEndTests(FunctionAppFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public async Task QueueTriggerAndOutput_Succeeds() - { - string expectedQueueMessage = Guid.NewGuid().ToString(); - //Clear queue - await StorageHelpers.ClearQueue(Constants.Queue.OutputBindingName); - await StorageHelpers.ClearQueue(Constants.Queue.InputBindingName); - - //Set up and trigger - await StorageHelpers.CreateQueue(Constants.Queue.OutputBindingName); - await StorageHelpers.InsertIntoQueue(Constants.Queue.InputBindingName, expectedQueueMessage); - - //Verify - var queueMessage = await StorageHelpers.ReadFromQueue(Constants.Queue.OutputBindingName); - Assert.Equal(expectedQueueMessage, queueMessage); - } - } -} \ No newline at end of file diff --git a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Utilities.cs b/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Utilities.cs deleted file mode 100644 index f152ad7..0000000 --- a/test/end-to-end/Azure.Functions.NodejsWorker.E2E/Azure.Functions.NodejsWorker.E2E/Utilities.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Threading.Tasks; - -namespace Azure.Functions.NodeJs.Tests.E2E -{ - public static class Utilities - { - public static async Task RetryAsync(Func> condition, int timeout = 60 * 1000, int pollingInterval = 2 * 1000, bool throwWhenDebugging = false, Func userMessageCallback = null) - { - DateTime start = DateTime.Now; - while (!await condition()) - { - await Task.Delay(pollingInterval); - - bool shouldThrow = !Debugger.IsAttached || (Debugger.IsAttached && throwWhenDebugging); - if (shouldThrow && (DateTime.Now - start).TotalMilliseconds > timeout) - { - string error = "Condition not reached within timeout."; - if (userMessageCallback != null) - { - error += " " + userMessageCallback(); - } - throw new ApplicationException(error); - } - } - } - } -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/CosmosDBTriggerAndOutput/function.json b/test/end-to-end/testFunctionApp/CosmosDBTriggerAndOutput/function.json deleted file mode 100644 index 60f0945..0000000 --- a/test/end-to-end/testFunctionApp/CosmosDBTriggerAndOutput/function.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "bindings": [ - { - "type": "cosmosDBTrigger", - "name": "itemIn", - "direction": "in", - "leaseCollectionName": "leases", - "connectionStringSetting": "AzureWebJobsCosmosDBConnectionString", - "databaseName": "ItemDb", - "collectionName": "ItemCollectionIn", - "createLeaseCollectionIfNotExists": true - }, - { - "type": "cosmosDB", - "name": "$return", - "direction": "out", - "leaseCollectionName": "leases", - "connectionStringSetting": "AzureWebJobsCosmosDBConnectionString", - "databaseName": "ItemDb", - "collectionName": "ItemCollectionOut" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/CosmosDBTriggerAndOutput/index.js b/test/end-to-end/testFunctionApp/CosmosDBTriggerAndOutput/index.js deleted file mode 100644 index 9a1408a..0000000 --- a/test/end-to-end/testFunctionApp/CosmosDBTriggerAndOutput/index.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = async function (context, documents) { - context.log.info("JavaScript Cosmos DB trigger function executed. Received document: " + documents); - if (!!documents && documents.length > 0) { - var document = documents[0] - context.log('Document Id: ', document.id); - document.Description = "testdescription"; - return document; - } -} diff --git a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputObject/function.json b/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputObject/function.json deleted file mode 100644 index 10341c0..0000000 --- a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputObject/function.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "bindings": [ - { - "type": "eventHubTrigger", - "name": "eventHubMessages", - "direction": "in", - "eventHubName": "test-input-object-node", - "connection": "AzureWebJobsEventHubSender", - "cardinality": "many" - }, - { - "type": "eventHub", - "name": "$return", - "eventHubName": "test-output-object-node", - "connection": "AzureWebJobsEventHubSender", - "direction": "out" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputObject/index.js b/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputObject/index.js deleted file mode 100644 index 8a2821d..0000000 --- a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputObject/index.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = async function (context, eventHubMessages) { - context.log(`JavaScript eventhub trigger function called for object message array ${eventHubMessages}`); - - eventHubMessages.forEach((message, index) => { - context.log(`Processed message ${JSON.stringify(message)}, value: ${message.value}`); - }); - - return eventHubMessages[0]; -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputString/function.json b/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputString/function.json deleted file mode 100644 index 5b3a04d..0000000 --- a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputString/function.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "bindings": [ - { - "type": "eventHubTrigger", - "name": "eventHubMessages", - "direction": "in", - "eventHubName": "test-input-string-node", - "connection": "AzureWebJobsEventHubSender", - "cardinality": "many", - "dataType": "string" - }, - { - "type": "eventHub", - "name": "$return", - "eventHubName": "test-output-string-node", - "connection": "AzureWebJobsEventHubSender", - "direction": "out" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputString/index.js b/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputString/index.js deleted file mode 100644 index 554913b..0000000 --- a/test/end-to-end/testFunctionApp/EventHubTriggerAndOutputString/index.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = async function (context, eventHubMessages) { - context.log(`JavaScript eventhub trigger function called for string message array ${eventHubMessages}`); - - eventHubMessages.forEach((message, index) => { - context.log(`Processed message ${message}`); - }); - - return eventHubMessages[0]; -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/EventHubVerifyOutputObject/function.json b/test/end-to-end/testFunctionApp/EventHubVerifyOutputObject/function.json deleted file mode 100644 index e8443a7..0000000 --- a/test/end-to-end/testFunctionApp/EventHubVerifyOutputObject/function.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "bindings": [ - { - "type": "eventHubTrigger", - "name": "eventHubMessages", - "direction": "in", - "eventHubName": "test-output-object-node", - "connection": "AzureWebJobsEventHubSender", - "cardinality": "one" - }, - { - "type": "queue", - "name": "$return", - "direction": "out", - "queueName": "test-output-object-node", - "connection": "AzureWebJobsStorage" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/EventHubVerifyOutputObject/index.js b/test/end-to-end/testFunctionApp/EventHubVerifyOutputObject/index.js deleted file mode 100644 index cf65b91..0000000 --- a/test/end-to-end/testFunctionApp/EventHubVerifyOutputObject/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = async function (context, eventHubMessage) { - context.log(`JavaScript EventHubVerifyOutputObject function called for message ${JSON.stringify(eventHubMessage)}`); - return eventHubMessage; -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/EventHubVerifyOutputString/function.json b/test/end-to-end/testFunctionApp/EventHubVerifyOutputString/function.json deleted file mode 100644 index 8457fb8..0000000 --- a/test/end-to-end/testFunctionApp/EventHubVerifyOutputString/function.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "bindings": [ - { - "type": "eventHubTrigger", - "name": "eventHubMessages", - "direction": "in", - "eventHubName": "test-output-string-node", - "connection": "AzureWebJobsEventHubSender", - "cardinality": "one" - }, - { - "type": "queue", - "name": "$return", - "direction": "out", - "queueName": "test-output-string-node", - "connection": "AzureWebJobsStorage" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/EventHubVerifyOutputString/index.js b/test/end-to-end/testFunctionApp/EventHubVerifyOutputString/index.js deleted file mode 100644 index a792ebb..0000000 --- a/test/end-to-end/testFunctionApp/EventHubVerifyOutputString/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = async function (context, eventHubMessage) { - context.log(`JavaScript EventHubVerifyStringObject function called for message ${eventHubMessage}`); - return eventHubMessage; -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTrigger/function.json b/test/end-to-end/testFunctionApp/HttpTrigger/function.json deleted file mode 100644 index 4ef0fff..0000000 --- a/test/end-to-end/testFunctionApp/HttpTrigger/function.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTrigger/index.js b/test/end-to-end/testFunctionApp/HttpTrigger/index.js deleted file mode 100644 index aa2c446..0000000 --- a/test/end-to-end/testFunctionApp/HttpTrigger/index.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "Hello " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerBindingData/function.json b/test/end-to-end/testFunctionApp/HttpTriggerBindingData/function.json deleted file mode 100644 index 9f2d4d8..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerBindingData/function.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerBindingData/index.js b/test/end-to-end/testFunctionApp/HttpTriggerBindingData/index.js deleted file mode 100644 index 35ef3a7..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerBindingData/index.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = async function (context) { - context.log('JavaScript HTTP trigger function processed a request.'); - const { methodName, utcNow, randGuid } = context.bindingData.sys; - const { invocationId, query, headers } = context.bindingData; - const bindingDataExists = - exists(methodName) - && exists(utcNow) - && exists(randGuid) - && exists(invocationId) - && exists(query) - && exists(headers) - && exists(query.stringInput) - && exists(query.emptyStringInput); // should be "" - if (bindingDataExists) { - context.res.body = "binding data exists" - } else { - context.res = { - status: 500 - } - } -}; - -function exists(property) { - return property !== null && property !== undefined; -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerBodyAndRawBody/function.json b/test/end-to-end/testFunctionApp/HttpTriggerBodyAndRawBody/function.json deleted file mode 100644 index 9f2d4d8..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerBodyAndRawBody/function.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerBodyAndRawBody/index.js b/test/end-to-end/testFunctionApp/HttpTriggerBodyAndRawBody/index.js deleted file mode 100644 index 7c3208d..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerBodyAndRawBody/index.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - context.res.body = { - reqBody: context.req.body, - reqRawBody: context.req.rawBody, - }; -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerESModules/function.json b/test/end-to-end/testFunctionApp/HttpTriggerESModules/function.json deleted file mode 100644 index 3ee0bfb..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerESModules/function.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ], - "scriptFile": "./index.mjs" -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerESModules/index.mjs b/test/end-to-end/testFunctionApp/HttpTriggerESModules/index.mjs deleted file mode 100644 index 61b8f73..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerESModules/index.mjs +++ /dev/null @@ -1,16 +0,0 @@ -export const httpTrigger = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "Hello " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerSetsCookie/function.json b/test/end-to-end/testFunctionApp/HttpTriggerSetsCookie/function.json deleted file mode 100644 index 4ef0fff..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerSetsCookie/function.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerSetsCookie/index.js b/test/end-to-end/testFunctionApp/HttpTriggerSetsCookie/index.js deleted file mode 100644 index f95e579..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerSetsCookie/index.js +++ /dev/null @@ -1,40 +0,0 @@ -module.exports = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - // TODO: Add this scenario - // { - // name: "mycookie6-samesite-none", - // value: "myvalue", - // sameSite: "None" - // }, - context.res = { - status: 200, - cookies: [ - { - name: "mycookie", - value: "myvalue", - maxAge: 200000 - }, - { - name: "mycookie2", - value: "myvalue", - path: "/", - maxAge: "200000" - }, - { - name: "mycookie3-expires", - value: "myvalue3-expires", - maxAge: 0 - }, - { - name: "mycookie4-samesite-lax", - value: "myvalue", - sameSite: "Lax" - }, - { - name: "mycookie5-samesite-strict", - value: "myvalue", - sameSite: "Strict" - } - ] - } -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerThrows/function.json b/test/end-to-end/testFunctionApp/HttpTriggerThrows/function.json deleted file mode 100644 index 4ef0fff..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerThrows/function.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/HttpTriggerThrows/index.js b/test/end-to-end/testFunctionApp/HttpTriggerThrows/index.js deleted file mode 100644 index 7a5eafe..0000000 --- a/test/end-to-end/testFunctionApp/HttpTriggerThrows/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - - throw new Error("Test Exception"); -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/QueueTriggerAndOutput/function.json b/test/end-to-end/testFunctionApp/QueueTriggerAndOutput/function.json deleted file mode 100644 index 814f5f1..0000000 --- a/test/end-to-end/testFunctionApp/QueueTriggerAndOutput/function.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "bindings": [ - { - "name": "myQueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "test-input-node", - "connection": "AzureWebJobsStorage" - }, - { - "name": "$return", - "type": "queue", - "direction": "out", - "queueName": "test-output-node", - "connection": "AzureWebJobsStorage" - } - ] -} \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/QueueTriggerAndOutput/index.js b/test/end-to-end/testFunctionApp/QueueTriggerAndOutput/index.js deleted file mode 100644 index afe4408..0000000 --- a/test/end-to-end/testFunctionApp/QueueTriggerAndOutput/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = async function (context, myQueueItem) { - context.log('JavaScript queue trigger function processed work item', myQueueItem); - return myQueueItem; -}; \ No newline at end of file diff --git a/test/end-to-end/testFunctionApp/host.json b/test/end-to-end/testFunctionApp/host.json deleted file mode 100644 index 1a03817..0000000 --- a/test/end-to-end/testFunctionApp/host.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": "2.0", - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[2.*, 3.0.0)" - } -} diff --git a/test/end-to-end/testFunctionApp/local.settings.json b/test/end-to-end/testFunctionApp/local.settings.json deleted file mode 100644 index dff5c27..0000000 --- a/test/end-to-end/testFunctionApp/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsEventHubSender":"", - "AzureWebJobsCosmosDBConnectionString":"", - "FUNCTIONS_WORKER_RUNTIME": "node" - } - } \ No newline at end of file diff --git a/test/eventHandlers/FunctionEnvironmentReloadHandler.test.ts b/test/eventHandlers/FunctionEnvironmentReloadHandler.test.ts deleted file mode 100644 index 71fac25..0000000 --- a/test/eventHandlers/FunctionEnvironmentReloadHandler.test.ts +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import 'mocha'; -import * as mock from 'mock-fs'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { WorkerChannel } from '../../src/WorkerChannel'; -import { beforeEventHandlerSuite } from './beforeEventHandlerSuite'; -import { TestEventStream } from './TestEventStream'; -import { Msg as WorkerInitMsg } from './WorkerInitHandler.test'; -import path = require('path'); -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -export namespace Msg { - export function reloadEnvVarsLog(numVars: number): rpc.IStreamingMessage { - return { - rpcLog: { - message: `Reloading environment variables. Found ${numVars} variables to reload.`, - level: LogLevel.Information, - logCategory: LogCategory.System, - }, - }; - } - - export const reloadSuccess: rpc.IStreamingMessage = { - requestId: 'id', - functionEnvironmentReloadResponse: { - result: { - status: rpc.StatusResult.Status.Success, - }, - }, - }; - - export const noHandlerRpcLog: rpc.IStreamingMessage = { - rpcLog: { - message: "Worker workerId had no handler for message 'undefined'", - level: LogLevel.Error, - logCategory: LogCategory.System, - }, - }; - - export function changingCwdLog(dir = '/'): rpc.IStreamingMessage { - return { - rpcLog: { - message: `Changing current working directory to ${dir}`, - level: LogLevel.Information, - logCategory: LogCategory.System, - }, - }; - } - - export function warning(message: string): rpc.IStreamingMessage { - return { - rpcLog: { - message, - level: LogLevel.Warning, - logCategory: LogCategory.System, - }, - }; - } - - export const noPackageJsonWarning: rpc.IStreamingMessage = warning( - `Worker failed to load package.json: file does not exist` - ); -} - -describe('FunctionEnvironmentReloadHandler', () => { - let stream: TestEventStream; - let channel: WorkerChannel; - - // Reset `process.env` and process.cwd() after this test suite so it doesn't affect other tests - let originalEnv: NodeJS.ProcessEnv; - let originalCwd: string; - before(() => { - originalEnv = { ...process.env }; - originalCwd = process.cwd(); - ({ stream, channel } = beforeEventHandlerSuite()); - channel.hostVersion = '2.7.0'; - }); - - after(() => { - Object.assign(process.env, originalEnv); - }); - - afterEach(async () => { - mock.restore(); - process.chdir(originalCwd); - await stream.afterEachEventHandlerTest(); - }); - - it('reloads environment variables', async () => { - process.env.PlaceholderVariable = 'TRUE'; - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - environmentVariables: { - hello: 'world', - SystemDrive: 'Q:', - }, - functionAppDirectory: null, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(2), Msg.reloadSuccess); - expect(process.env.hello).to.equal('world'); - expect(process.env.SystemDrive).to.equal('Q:'); - expect(process.env.PlaceholderVariable).to.be.undefined; - }); - - it('preserves OS-specific casing behavior of environment variables', async () => { - process.env.PlaceholderVariable = 'TRUE'; - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - environmentVariables: { - hello: 'world', - SystemDrive: 'Q:', - }, - functionAppDirectory: null, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(2), Msg.reloadSuccess); - expect(process.env.hello).to.equal('world'); - expect(process.env.SystemDrive).to.equal('Q:'); - expect(process.env.PlaceholderVariable).to.be.undefined; - expect(process.env.placeholdervariable).to.be.undefined; - if (process.platform === 'win32') { - expect(process.env.HeLlO).to.equal('world'); - expect(process.env.systemdrive).to.equal('Q:'); - } else { - expect(process.env.HeLlO).to.be.undefined; - expect(process.env.systemdrive).to.be.undefined; - } - }); - - it('reloading environment variables removes existing environment variables', async () => { - process.env.PlaceholderVariable = 'TRUE'; - process.env.NODE_ENV = 'Debug'; - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - environmentVariables: {}, - functionAppDirectory: null, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(0), Msg.reloadSuccess); - expect(process.env).to.be.empty; - }); - - it('reloads empty environment variables', async () => { - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - environmentVariables: {}, - functionAppDirectory: null, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(0), Msg.reloadSuccess); - - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: null, - }); - - await stream.assertCalledWith(Msg.noHandlerRpcLog); - - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - environmentVariables: null, - functionAppDirectory: null, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(0), Msg.reloadSuccess); - }); - - it('reloads environment variable and keeps cwd without functionAppDirectory', async () => { - const cwd = process.cwd(); - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - environmentVariables: { - hello: 'world', - SystemDrive: 'Q:', - }, - functionAppDirectory: null, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(2), Msg.reloadSuccess); - expect(process.env.hello).to.equal('world'); - expect(process.env.SystemDrive).to.equal('Q:'); - expect(process.cwd() == cwd); - }); - - it('reloads environment variable and changes functionAppDirectory', async () => { - const cwd = process.cwd(); - const newDir = '/'; - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - environmentVariables: { - hello: 'world', - SystemDrive: 'Q:', - }, - functionAppDirectory: newDir, - }, - }); - - await stream.assertCalledWith( - Msg.reloadEnvVarsLog(2), - Msg.changingCwdLog(), - Msg.noPackageJsonWarning, - Msg.reloadSuccess - ); - expect(process.env.hello).to.equal('world'); - expect(process.env.SystemDrive).to.equal('Q:'); - expect(process.cwd() != newDir); - expect(process.cwd() == newDir); - process.chdir(cwd); - }); - - it('reloads package.json', async () => { - const cwd = process.cwd(); - const oldDir = 'oldDir'; - const oldDirAbsolute = path.join(cwd, oldDir); - const newDir = 'newDir'; - const newDirAbsolute = path.join(cwd, newDir); - const oldPackageJson = { - type: 'module', - hello: 'world', - }; - const newPackageJson = { - type: 'commonjs', - notHello: 'notWorld', - }; - mock({ - [oldDir]: { - 'package.json': JSON.stringify(oldPackageJson), - }, - [newDir]: { - 'package.json': JSON.stringify(newPackageJson), - }, - }); - - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - functionAppDirectory: oldDirAbsolute, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(0), Msg.changingCwdLog(oldDirAbsolute), Msg.reloadSuccess); - expect(channel.packageJson).to.deep.equal(oldPackageJson); - - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - functionAppDirectory: newDirAbsolute, - }, - }); - await stream.assertCalledWith(Msg.reloadEnvVarsLog(0), Msg.changingCwdLog(newDirAbsolute), Msg.reloadSuccess); - expect(channel.packageJson).to.deep.equal(newPackageJson); - }); - - it('correctly loads package.json in specialization scenario', async () => { - const cwd = process.cwd(); - const tempDir = 'temp'; - const appDir = 'app'; - const packageJson = { - type: 'module', - hello: 'world', - }; - - mock({ - [tempDir]: {}, - [appDir]: { - 'package.json': JSON.stringify(packageJson), - }, - }); - - stream.addTestMessage(WorkerInitMsg.init(path.join(cwd, tempDir))); - await stream.assertCalledWith( - WorkerInitMsg.receivedInitLog, - WorkerInitMsg.warning(`Worker failed to load package.json: file does not exist`), - WorkerInitMsg.response - ); - expect(channel.packageJson).to.be.empty; - - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - functionAppDirectory: path.join(cwd, appDir), - }, - }); - await stream.assertCalledWith( - Msg.reloadEnvVarsLog(0), - Msg.changingCwdLog(path.join(cwd, appDir)), - Msg.reloadSuccess - ); - expect(channel.packageJson).to.deep.equal(packageJson); - }); - - for (const extension of ['.js', '.mjs', '.cjs']) { - it(`Loads entry point (${extension}) in specialization scenario`, async () => { - const cwd = process.cwd(); - const tempDir = 'temp'; - const fileName = `entryPointFiles/doNothing${extension}`; - const expectedPackageJson = { - main: fileName, - }; - mock({ - [tempDir]: {}, - [__dirname]: { - 'package.json': JSON.stringify(expectedPackageJson), - // 'require' and 'mockFs' don't play well together so we need these files in both the mock and real file systems - entryPointFiles: mock.load(path.join(__dirname, 'entryPointFiles')), - }, - }); - - stream.addTestMessage(WorkerInitMsg.init(path.join(cwd, tempDir))); - await stream.assertCalledWith( - WorkerInitMsg.receivedInitLog, - WorkerInitMsg.warning('Worker failed to load package.json: file does not exist'), - WorkerInitMsg.response - ); - - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - functionAppDirectory: __dirname, - }, - }); - await stream.assertCalledWith( - Msg.reloadEnvVarsLog(0), - Msg.changingCwdLog(__dirname), - WorkerInitMsg.loadingEntryPoint(fileName), - WorkerInitMsg.loadedEntryPoint(fileName), - Msg.reloadSuccess - ); - }); - } -}); diff --git a/test/eventHandlers/FunctionLoadHandler.test.ts b/test/eventHandlers/FunctionLoadHandler.test.ts deleted file mode 100644 index 5869b80..0000000 --- a/test/eventHandlers/FunctionLoadHandler.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import 'mocha'; -import * as sinon from 'sinon'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { FunctionLoader } from '../../src/FunctionLoader'; -import { PackageJson } from '../../src/parsers/parsePackageJson'; -import { beforeEventHandlerSuite } from './beforeEventHandlerSuite'; -import { TestEventStream } from './TestEventStream'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -describe('FunctionLoadHandler', () => { - let stream: TestEventStream; - let loader: sinon.SinonStubbedInstance; - - before(() => { - ({ stream, loader } = beforeEventHandlerSuite()); - }); - - afterEach(async () => { - await stream.afterEachEventHandlerTest(); - }); - - it('responds to function load', async () => { - stream.addTestMessage({ - requestId: 'id', - functionLoadRequest: { - functionId: 'funcId', - metadata: {}, - }, - }); - await stream.assertCalledWith({ - requestId: 'id', - functionLoadResponse: { - functionId: 'funcId', - result: { - status: rpc.StatusResult.Status.Success, - }, - }, - }); - }); - - it('handles function load exception', async () => { - const err = new Error('Function throws error'); - err.stack = ''; - - const originalLoader = loader.load; - try { - loader.load = sinon.stub<[string, rpc.IRpcFunctionMetadata, PackageJson], Promise>().throws(err); - - stream.addTestMessage({ - requestId: 'id', - functionLoadRequest: { - functionId: 'funcId', - metadata: { - name: 'testFuncName', - }, - }, - }); - - const message = "Worker was unable to load function testFuncName: 'Function throws error'"; - - const errorRpcLog: rpc.IStreamingMessage = { - rpcLog: { - message, - level: LogLevel.Error, - logCategory: LogCategory.System, - }, - }; - - await stream.assertCalledWith(errorRpcLog, { - requestId: 'id', - functionLoadResponse: { - functionId: 'funcId', - result: { - status: rpc.StatusResult.Status.Failure, - exception: { - message, - stackTrace: '', - }, - }, - }, - }); - } finally { - loader.load = originalLoader; - } - }); -}); diff --git a/test/eventHandlers/InvocationHandler.test.ts b/test/eventHandlers/InvocationHandler.test.ts deleted file mode 100644 index 7097002..0000000 --- a/test/eventHandlers/InvocationHandler.test.ts +++ /dev/null @@ -1,1055 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -/* eslint-disable deprecation/deprecation */ - -import { AzureFunction, Context } from '@azure/functions'; -import * as coreTypes from '@azure/functions-core'; -import { expect } from 'chai'; -import 'mocha'; -import * as sinon from 'sinon'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { FunctionInfo } from '../../src/FunctionInfo'; -import { FunctionLoader } from '../../src/FunctionLoader'; -import { WorkerChannel } from '../../src/WorkerChannel'; -import { Msg as AppStartMsg } from '../startApp.test'; -import { beforeEventHandlerSuite } from './beforeEventHandlerSuite'; -import { TestEventStream } from './TestEventStream'; -import { Msg as WorkerInitMsg } from './WorkerInitHandler.test'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -namespace Binding { - export const httpInput = { - type: 'httpTrigger', - direction: 0, - dataType: 1, - }; - export const httpOutput = { - type: 'http', - direction: 1, - dataType: 1, - }; - export const queueOutput = { - type: 'queue', - direction: 1, - dataType: 1, - }; - - export const httpReturn = { - bindings: { - req: httpInput, - $return: httpOutput, - }, - name: 'testFuncName', - }; - export const httpRes = { - bindings: { - req: httpInput, - res: httpOutput, - }, - name: 'testFuncName', - }; - export const activity = { - bindings: { - name: { - type: 'activityTrigger', - direction: 1, - dataType: 1, - }, - }, - name: 'testFuncName', - }; - export const queue = { - bindings: { - testOutput: { - type: 'queue', - direction: 1, - dataType: 1, - }, - }, - name: 'testFuncName', - }; -} - -const testError = new Error('testErrorMessage'); -testError.stack = 'testErrorStack'; - -function addSuffix(asyncFunc: AzureFunction, callbackFunc: AzureFunction): [AzureFunction, string][] { - return [ - [asyncFunc, ' (async)'], - [callbackFunc, ' (context.done)'], - ]; -} - -let hookData: string; - -namespace TestFunc { - const basicAsync = async (context: Context) => { - context.log('testUserLog'); - }; - const basicCallback = (context: Context) => { - context.log('testUserLog'); - context.done(); - }; - export const basic = addSuffix(basicAsync, basicCallback); - - const returnHttpAsync = async (_context: Context) => { - return { body: { hello: 'world' } }; - }; - const returnHttpCallback = (context: Context) => { - context.done(null, { body: { hello: 'world' } }); - }; - export const returnHttp = addSuffix(returnHttpAsync, returnHttpCallback); - - const returnArrayAsync = async (_context: Context) => { - return ['hello, seattle!', 'hello, tokyo!']; - }; - const returnArrayCallback = (context: Context) => { - context.done(null, ['hello, seattle!', 'hello, tokyo!']); - }; - export const returnArray = addSuffix(returnArrayAsync, returnArrayCallback); - - const resHttpAsync = async (_context: Context) => { - return { res: { body: { hello: 'world' } } }; - }; - const resHttpCallback = (context: Context) => { - context.done(null, { res: { body: { hello: 'world' } } }); - }; - export const resHttp = addSuffix(resHttpAsync, resHttpCallback); - - const logHookDataAsync = async (context: Context) => { - hookData += 'invoc'; - context.log(hookData); - return 'hello'; - }; - const logHookDataCallback = (context: Context) => { - hookData += 'invoc'; - context.log(hookData); - context.done(null, 'hello'); - }; - export const logHookData = addSuffix(logHookDataAsync, logHookDataCallback); - - const logInputAsync = async (context: Context, input: any) => { - context.log(input); - }; - const logInputCallback = (context: Context, input: any) => { - context.log(input); - context.done(); - }; - export const logInput = addSuffix(logInputAsync, logInputCallback); - - const multipleBindingsAsync = async (context: Context) => { - context.bindings.queueOutput = 'queue message'; - context.bindings.overriddenQueueOutput = 'start message'; - return { - res: { body: { hello: 'world' } }, - overriddenQueueOutput: 'override', - }; - }; - const multipleBindingsCallback = (context: Context) => { - context.bindings.queueOutput = 'queue message'; - context.bindings.overriddenQueueOutput = 'start message'; - context.done(null, { - res: { body: { hello: 'world' } }, - overriddenQueueOutput: 'override', - }); - }; - export const multipleBindings = addSuffix(multipleBindingsAsync, multipleBindingsCallback); - - const errorAsync = async (_context: Context) => { - throw testError; - }; - const errorCallback = (context: Context) => { - context.done(testError); - }; - export const error = addSuffix(errorAsync, errorCallback); - - const returnEmptyStringAsync = async (_context: Context) => { - return ''; - }; - const returnEmptyStringCallback = (context: Context) => { - context.done(null, ''); - }; - export const returnEmptyString = addSuffix(returnEmptyStringAsync, returnEmptyStringCallback); - - const returnZeroAsync = async (_context: Context) => { - return 0; - }; - const returnZeroCallback = (context: Context) => { - context.done(null, 0); - }; - export const returnZero = addSuffix(returnZeroAsync, returnZeroCallback); - - const returnFalseAsync = async (_context: Context) => { - return false; - }; - const returnFalseCallback = (context: Context) => { - context.done(null, false); - }; - export const returnFalse = addSuffix(returnFalseAsync, returnFalseCallback); -} - -namespace Msg { - export const asyncAndDoneLog: rpc.IStreamingMessage = { - rpcLog: { - category: 'testFuncName.Invocation', - invocationId: '1', - message: - "Error: Choose either to return a promise or call 'done'. Do not use both in your script. Learn more: https://go.microsoft.com/fwlink/?linkid=2097909", - level: LogLevel.Error, - logCategory: LogCategory.System, - }, - }; - export const duplicateDoneLog: rpc.IStreamingMessage = { - rpcLog: { - category: 'testFuncName.Invocation', - invocationId: '1', - message: "Error: 'done' has already been called. Please check your script for extraneous calls to 'done'.", - level: LogLevel.Error, - logCategory: LogCategory.System, - }, - }; - export const unexpectedLogAfterDoneLog: rpc.IStreamingMessage = { - rpcLog: { - category: 'testFuncName.Invocation', - invocationId: '1', - message: - "Warning: Unexpected call to 'log' on the context object after function execution has completed. Please check for asynchronous calls that are not awaited or calls to 'done' made before function execution completes. Function name: testFuncName. Invocation Id: 1. Learn more: https://go.microsoft.com/fwlink/?linkid=2097909", - level: LogLevel.Warning, - logCategory: LogCategory.System, - }, - }; - export function userTestLog(data = 'testUserLog'): rpc.IStreamingMessage { - return { - rpcLog: { - category: 'testFuncName.Invocation', - invocationId: '1', - message: data, - level: LogLevel.Information, - logCategory: LogCategory.User, - }, - }; - } - export const invocResFailed: rpc.IStreamingMessage = { - requestId: 'testReqId', - invocationResponse: { - invocationId: '1', - result: { - status: rpc.StatusResult.Status.Failure, - exception: { - message: 'testErrorMessage', - stackTrace: 'testErrorStack', - }, - }, - }, - }; - export function receivedInvocLog(): rpc.IStreamingMessage { - return { - rpcLog: { - category: 'testFuncName.Invocation', - invocationId: '1', - message: 'Received FunctionInvocationRequest', - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - } - export function executingHooksLog(count: number, hookName: string): rpc.IStreamingMessage { - return { - rpcLog: { - category: 'testFuncName.Invocation', - invocationId: '1', - message: `Executing ${count} "${hookName}" hooks`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - } - export function executedHooksLog(hookName: string): rpc.IStreamingMessage { - return { - rpcLog: { - category: 'testFuncName.Invocation', - invocationId: '1', - message: `Executed "${hookName}" hooks`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - } - export function invocResponse( - expectedOutputData?: rpc.IParameterBinding[] | null, - expectedReturnValue?: rpc.ITypedData | null - ) { - const msg: rpc.IStreamingMessage = {}; - msg.requestId = 'testReqId'; - msg.invocationResponse = { - invocationId: '1', - result: { - status: rpc.StatusResult.Status.Success, - }, - outputData: expectedOutputData, - }; - if (expectedReturnValue !== undefined) { - msg.invocationResponse.returnValue = expectedReturnValue; - } - return msg; - } -} - -namespace InputData { - export const http = { - name: 'req', - data: { - data: 'http', - http: { - body: { - string: 'blahh', - }, - rawBody: { - string: 'blahh', - }, - }, - }, - }; - - export const string = { - name: 'testInput', - data: { - data: 'string', - string: 'testStringData', - }, - }; -} - -describe('InvocationHandler', () => { - let stream: TestEventStream; - let loader: sinon.SinonStubbedInstance; - let channel: WorkerChannel; - let coreApi: typeof coreTypes; - let testDisposables: coreTypes.Disposable[] = []; - - before(async () => { - ({ stream, loader, channel } = beforeEventHandlerSuite()); - coreApi = await import('@azure/functions-core'); - }); - - beforeEach(async () => { - hookData = ''; - channel.appHookData = {}; - channel.appLevelOnlyHookData = {}; - }); - - afterEach(async () => { - await stream.afterEachEventHandlerTest(); - coreApi.Disposable.from(...testDisposables).dispose(); - testDisposables = []; - }); - - function sendInvokeMessage(inputData?: rpc.IParameterBinding[] | null): void { - stream.addTestMessage({ - requestId: 'testReqId', - invocationRequest: { - functionId: 'id', - invocationId: '1', - inputData: inputData, - }, - }); - } - - function getHttpResponse(rawBody?: string | {} | undefined, name = 'res'): rpc.IParameterBinding { - let body: rpc.ITypedData; - if (typeof rawBody === 'string') { - body = { string: rawBody }; - } else if (rawBody === undefined) { - body = { json: rawBody }; - } else { - body = { json: JSON.stringify(rawBody) }; - } - - return { - data: { - http: { - body, - cookies: [], - headers: {}, - statusCode: undefined, - }, - }, - name, - }; - } - - for (const [func, suffix] of TestFunc.basic) { - it('invokes function' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.userTestLog(), - Msg.invocResponse([getHttpResponse()]) - ); - }); - } - - for (const [func, suffix] of TestFunc.returnHttp) { - it('returns correct data with $return binding' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.httpReturn)); - sendInvokeMessage([InputData.http]); - const expectedOutput = getHttpResponse(undefined, '$return'); - const expectedReturnValue = { - http: { - body: { json: '{"hello":"world"}' }, - cookies: [], - headers: {}, - statusCode: undefined, - }, - }; - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.invocResponse([expectedOutput], expectedReturnValue) - ); - }); - } - - for (const [func, suffix] of TestFunc.returnArray) { - it('returns returned output if not http' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - sendInvokeMessage([]); - const expectedReturnValue = { - json: '["hello, seattle!","hello, tokyo!"]', - }; - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse([], expectedReturnValue)); - }); - } - - for (const [func, suffix] of TestFunc.returnArray) { - it('returned output is ignored if http' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([]); - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse([], undefined)); - }); - } - - for (const [func, suffix] of TestFunc.resHttp) { - it('serializes output binding data through context.done' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([InputData.http]); - const expectedOutput = [getHttpResponse({ hello: 'world' })]; - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse(expectedOutput)); - }); - } - - for (const [func, suffix] of TestFunc.multipleBindings) { - it('serializes multiple output bindings through context.done and context.bindings' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns( - new FunctionInfo({ - bindings: { - req: Binding.httpInput, - res: Binding.httpOutput, - queueOutput: Binding.queueOutput, - overriddenQueueOutput: Binding.queueOutput, - }, - name: 'testFuncName', - }) - ); - sendInvokeMessage([InputData.http]); - const expectedOutput = [ - getHttpResponse({ hello: 'world' }), - { - data: { - string: 'override', - }, - name: 'overriddenQueueOutput', - }, - { - data: { - string: 'queue message', - }, - name: 'queueOutput', - }, - ]; - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse(expectedOutput)); - }); - } - - for (const [func, suffix] of TestFunc.error) { - it('returns failed status for user error' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResFailed); - }); - } - - it('throws for malformed messages', () => { - expect(() => { - stream.write({ - functionLoadResponse: 1, - }); - }).to.throw('functionLoadResponse.object expected'); - }); - - it('empty function does not return invocation response', async () => { - loader.getFunc.returns(() => {}); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith(Msg.receivedInvocLog()); - }); - - it('logs error on calling context.done in async function', async () => { - loader.getFunc.returns(async (context: Context) => { - context.done(); - }); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.asyncAndDoneLog, - Msg.invocResponse([getHttpResponse()]) - ); - }); - - it('logs error on calling context.done more than once', async () => { - loader.getFunc.returns((context: Context) => { - context.done(); - context.done(); - }); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.duplicateDoneLog, - Msg.invocResponse([getHttpResponse()]) - ); - }); - - it('logs error on calling context.log after context.done', async () => { - loader.getFunc.returns((context: Context) => { - context.done(); - context.log('testUserLog'); - }); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.unexpectedLogAfterDoneLog, - Msg.userTestLog(), - Msg.invocResponse([getHttpResponse()]) - ); - }); - - it('logs error on calling context.log after async function', async () => { - let _context: Context; - loader.getFunc.returns(async (context: Context) => { - _context = context; - return 'hello'; - }); - loader.getInfo.returns(new FunctionInfo(Binding.httpRes)); - sendInvokeMessage([InputData.http]); - // wait for first two messages to ensure invocation happens - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse([getHttpResponse()])); - // then add extra context.log - _context!.log('testUserLog'); - await stream.assertCalledWith(Msg.unexpectedLogAfterDoneLog, Msg.userTestLog()); - }); - - for (const [func, suffix] of TestFunc.logHookData) { - it('preInvocationHook' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('preInvocation', () => { - hookData += 'pre'; - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.userTestLog('preinvoc'), - Msg.invocResponse([], { string: 'hello' }) - ); - expect(hookData).to.equal('preinvoc'); - }); - } - - for (const [func, suffix] of TestFunc.logInput) { - it('preInvocationHook respects change to inputs' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - expect(context.inputs.length).to.equal(1); - expect(context.inputs[0]).to.equal('testStringData'); - context.inputs = ['changedStringData']; - }) - ); - - sendInvokeMessage([InputData.string]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.userTestLog('changedStringData'), - Msg.invocResponse([]) - ); - }); - } - - it('preInvocationHook respects change to functionCallback', async () => { - loader.getFunc.returns(async (invocContext: Context) => { - invocContext.log('old function'); - }); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - expect(context.functionCallback).to.be.a('function'); - context.functionCallback = async (invocContext: Context) => { - invocContext.log('new function'); - }; - }) - ); - - sendInvokeMessage([InputData.string]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.userTestLog('new function'), - Msg.invocResponse([]) - ); - }); - - for (const [func, suffix] of TestFunc.logHookData) { - it('postInvocationHook' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - hookData += 'post'; - expect(context.result).to.equal('hello'); - expect(context.error).to.be.null; - context.invocationContext.log('hello from post'); - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.userTestLog('invoc'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.userTestLog('hello from post'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([], { string: 'hello' }) - ); - expect(hookData).to.equal('invocpost'); - }); - } - - for (const [func, suffix] of TestFunc.logHookData) { - it('postInvocationHook respects change to context.result' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - hookData += 'post'; - expect(context.result).to.equal('hello'); - expect(context.error).to.be.null; - context.result = 'world'; - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.userTestLog('invoc'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([], { string: 'world' }) - ); - expect(hookData).to.equal('invocpost'); - }); - } - - for (const [func, suffix] of TestFunc.error) { - it('postInvocationHook executes if function throws error' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - hookData += 'post'; - expect(context.result).to.be.null; - expect(context.error).to.equal(testError); - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResFailed - ); - expect(hookData).to.equal('post'); - }); - } - - for (const [func, suffix] of TestFunc.error) { - it('postInvocationHook respects change to context.error' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - hookData += 'post'; - expect(context.result).to.be.null; - expect(context.error).to.equal(testError); - context.error = null; - context.result = 'hello'; - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([], { string: 'hello' }) - ); - expect(hookData).to.equal('post'); - }); - } - - it('pre and post invocation hooks share data', async () => { - loader.getFunc.returns(async () => {}); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - context.hookData['hello'] = 'world'; - hookData += 'pre'; - }) - ); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - expect(context.hookData['hello']).to.equal('world'); - hookData += 'post'; - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([]) - ); - expect(hookData).to.equal('prepost'); - }); - - it('appHookData changes from appStart hooks are persisted in invocation hook contexts', async () => { - const functionAppDirectory = __dirname; - const expectedAppHookData = { - hello: 'world', - test: { - test2: 3, - }, - }; - const startFunc = sinon.spy((context: coreTypes.AppStartContext) => { - Object.assign(context.appHookData, expectedAppHookData); - hookData += 'appStart'; - }); - testDisposables.push(coreApi.registerHook('appStart', startFunc)); - - stream.addTestMessage(WorkerInitMsg.init(functionAppDirectory)); - - await stream.assertCalledWith( - WorkerInitMsg.receivedInitLog, - WorkerInitMsg.warning('Worker failed to load package.json: file does not exist'), - AppStartMsg.executingHooksLog(1, 'appStart'), - AppStartMsg.executedHooksLog('appStart'), - WorkerInitMsg.response - ); - expect(startFunc.callCount).to.be.equal(1); - - loader.getFunc.returns(async () => {}); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - expect(context.appHookData).to.deep.equal(expectedAppHookData); - hookData += 'preInvoc'; - }) - ); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - expect(context.appHookData).to.deep.equal(expectedAppHookData); - hookData += 'postInvoc'; - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([]) - ); - expect(hookData).to.equal('appStartpreInvocpostInvoc'); - }); - - it('hookData changes from appStart hooks are not persisted in invocation hook contexts', async () => { - const functionAppDirectory = __dirname; - const startFunc = sinon.spy((context: coreTypes.AppStartContext) => { - Object.assign(context.hookData, { - hello: 'world', - test: { - test2: 3, - }, - }); - hookData += 'appStart'; - }); - testDisposables.push(coreApi.registerHook('appStart', startFunc)); - - stream.addTestMessage(WorkerInitMsg.init(functionAppDirectory)); - - await stream.assertCalledWith( - WorkerInitMsg.receivedInitLog, - WorkerInitMsg.warning('Worker failed to load package.json: file does not exist'), - AppStartMsg.executingHooksLog(1, 'appStart'), - AppStartMsg.executedHooksLog('appStart'), - WorkerInitMsg.response - ); - expect(startFunc.callCount).to.be.equal(1); - - loader.getFunc.returns(async () => {}); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - expect(context.hookData).to.be.empty; - expect(context.appHookData).to.be.empty; - hookData += 'preInvoc'; - }) - ); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - expect(context.hookData).to.be.empty; - expect(context.appHookData).to.be.empty; - hookData += 'postInvoc'; - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([]) - ); - - expect(hookData).to.equal('appStartpreInvocpostInvoc'); - }); - - it('appHookData changes are persisted between invocation-level hooks', async () => { - const expectedAppHookData = { - hello: 'world', - test: { - test2: 3, - }, - }; - - loader.getFunc.returns(async () => {}); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - testDisposables.push( - coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - Object.assign(context.appHookData, expectedAppHookData); - hookData += 'pre'; - }) - ); - - testDisposables.push( - coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - expect(context.appHookData).to.deep.equal(expectedAppHookData); - hookData += 'post'; - }) - ); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([]) - ); - - expect(hookData).to.equal('prepost'); - }); - - it('appHookData changes are persisted across different invocations while hookData changes are not', async () => { - const expectedAppHookData = { - hello: 'world', - test: { - test2: 3, - }, - }; - const expectedInvocationHookData = { - hello2: 'world2', - test2: { - test4: 5, - }, - }; - - loader.getFunc.returns(async () => {}); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - const pre1 = coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - Object.assign(context.appHookData, expectedAppHookData); - Object.assign(context.hookData, expectedInvocationHookData); - hookData += 'pre1'; - }); - testDisposables.push(pre1); - - const post1 = coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - expect(context.appHookData).to.deep.equal(expectedAppHookData); - expect(context.hookData).to.deep.equal(expectedInvocationHookData); - hookData += 'post1'; - }); - testDisposables.push(post1); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([]) - ); - expect(hookData).to.equal('pre1post1'); - - pre1.dispose(); - post1.dispose(); - - const pre2 = coreApi.registerHook('preInvocation', (context: coreTypes.PreInvocationContext) => { - expect(context.appHookData).to.deep.equal(expectedAppHookData); - expect(context.hookData).to.be.empty; - hookData += 'pre2'; - }); - testDisposables.push(pre2); - - const post2 = coreApi.registerHook('postInvocation', (context: coreTypes.PostInvocationContext) => { - expect(context.appHookData).to.deep.equal(expectedAppHookData); - expect(context.hookData).to.be.empty; - hookData += 'post2'; - }); - testDisposables.push(post2); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.executingHooksLog(1, 'postInvocation'), - Msg.executedHooksLog('postInvocation'), - Msg.invocResponse([]) - ); - - expect(hookData).to.equal('pre1post1pre2post2'); - }); - - it('dispose hooks', async () => { - loader.getFunc.returns(async () => {}); - loader.getInfo.returns(new FunctionInfo(Binding.queue)); - - const disposableA: coreTypes.Disposable = coreApi.registerHook('preInvocation', () => { - hookData += 'a'; - }); - testDisposables.push(disposableA); - const disposableB: coreTypes.Disposable = coreApi.registerHook('preInvocation', () => { - hookData += 'b'; - }); - testDisposables.push(disposableB); - - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(2, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.invocResponse([]) - ); - expect(hookData).to.equal('ab'); - - disposableA.dispose(); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith( - Msg.receivedInvocLog(), - Msg.executingHooksLog(1, 'preInvocation'), - Msg.executedHooksLog('preInvocation'), - Msg.invocResponse([]) - ); - expect(hookData).to.equal('abb'); - - disposableB.dispose(); - sendInvokeMessage([InputData.http]); - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse([])); - expect(hookData).to.equal('abb'); - }); - - for (const [func, suffix] of TestFunc.returnEmptyString) { - it('returns and serializes falsy value in Durable: ""' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.activity)); - sendInvokeMessage([]); - const expectedReturnValue = { string: '' }; - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse([], expectedReturnValue)); - }); - } - - for (const [func, suffix] of TestFunc.returnZero) { - it('returns and serializes falsy value in Durable: 0' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.activity)); - sendInvokeMessage([]); - const expectedReturnValue = { int: 0 }; - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse([], expectedReturnValue)); - }); - } - - for (const [func, suffix] of TestFunc.returnFalse) { - it('returns and serializes falsy value in Durable: false' + suffix, async () => { - loader.getFunc.returns(func); - loader.getInfo.returns(new FunctionInfo(Binding.activity)); - sendInvokeMessage([]); - const expectedReturnValue = { json: 'false' }; - await stream.assertCalledWith(Msg.receivedInvocLog(), Msg.invocResponse([], expectedReturnValue)); - }); - } -}); diff --git a/test/eventHandlers/TestEventStream.ts b/test/eventHandlers/TestEventStream.ts deleted file mode 100644 index 97b9c09..0000000 --- a/test/eventHandlers/TestEventStream.ts +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import { EventEmitter } from 'events'; -import * as sinon from 'sinon'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { IEventStream } from '../../src/GrpcClient'; -import { Response } from '../../src/http/Response'; - -export class TestEventStream extends EventEmitter implements IEventStream { - written: sinon.SinonSpy; - constructor() { - super(); - this.written = sinon.spy(); - } - write(message: rpc.IStreamingMessage) { - this.written(message); - } - end(): void {} - - addTestMessage(msg: rpc.IStreamingMessage) { - this.emit('data', rpc.StreamingMessage.create(msg)); - } - - /** - * Waits up to a second for the expected number of messages to be written and then validates those messages - */ - async assertCalledWith(...expectedMsgs: (rpc.IStreamingMessage | RegExpStreamingMessage)[]): Promise { - try { - // Wait for up to a second for the expected number of messages to come in - const maxTime = Date.now() + 1000; - const interval = 10; - while (this.written.getCalls().length < expectedMsgs.length && Date.now() < maxTime) { - await new Promise((resolve) => setTimeout(resolve, interval)); - } - - const calls = this.written.getCalls(); - - // First, validate the "shortened" form of the messages. This will result in a more readable error for most test failures - const shortExpectedMsgs = expectedMsgs.map(getShortenedMsg); - const shortActualMsgs = calls.map((c) => getShortenedMsg(c.args[0])); - expect(shortActualMsgs).to.deep.equal(shortExpectedMsgs); - - // Next, do a more comprehensive check on the messages - expect(calls.length).to.equal( - expectedMsgs.length, - 'Message count does not match. This may be caused by the previous test writing extraneous messages.' - ); - for (let i = 0; i < expectedMsgs.length; i++) { - const call = calls[i]; - expect(call.args).to.have.length(1); - const actualMsg = convertHttpResponse(call.args[0]); - - let expectedMsg = expectedMsgs[i]; - if (expectedMsg instanceof RegExpStreamingMessage) { - expectedMsg.validateRegExpProps(actualMsg); - expectedMsg = expectedMsg.expectedMsg; - } - expectedMsg = convertHttpResponse(expectedMsg); - - expect(actualMsg).to.deep.equal(expectedMsg); - } - } finally { - this.written.resetHistory(); - } - } - - /** - * Verifies the test didn't send any extraneous messages - */ - async afterEachEventHandlerTest(): Promise { - // minor delay so that it's more likely extraneous messages are associated with this test as opposed to leaking into the next test - await new Promise((resolve) => setTimeout(resolve, 20)); - await this.assertCalledWith(); - } -} - -function getShortenedMsg(msg: rpc.IStreamingMessage | RegExpStreamingMessage): string { - msg = msg instanceof RegExpStreamingMessage ? msg.expectedMsg : msg; - if (msg.rpcLog?.message) { - return msg.rpcLog.message; - } else { - for (const [k, v] of Object.entries(msg)) { - // only interested in response messages - if (/response/i.test(k)) { - let result: string; - let errorMsg: string | undefined; - switch (v.result?.status) { - case rpc.StatusResult.Status.Success: - result = 'success'; - break; - case rpc.StatusResult.Status.Failure: - result = 'failed'; - errorMsg = v.result.exception?.message; - break; - case rpc.StatusResult.Status.Cancelled: - result = 'cancelled'; - break; - default: - result = 'unknown'; - break; - } - let shortMsg = `Message: "${k}". Result: "${result}"`; - if (errorMsg) { - shortMsg += ` Error: "${errorMsg}"`; - } - return shortMsg; - } - } - } - return 'Unknown message'; -} - -/** - * Converts the `HttpResponse` object in any invocation response message to a simpler object that's easier to verify with `deep.equal` - */ -function convertHttpResponse(msg: rpc.IStreamingMessage): rpc.IStreamingMessage { - if (msg.invocationResponse?.outputData) { - for (const entry of msg.invocationResponse.outputData) { - if (entry.data?.http instanceof Response) { - const res = entry.data.http; - entry.data.http = { - body: res.body, - cookies: res.cookies, - headers: res.headers, - statusCode: res.statusCode?.toString(), - }; - } - } - } - return msg; -} - -type RegExpProps = { [keyPath: string]: RegExp }; - -/** - * Allows you to use regular expressions to validate properties of the message instead of just deep equal - */ -export class RegExpStreamingMessage { - expectedMsg: rpc.IStreamingMessage; - #regExpProps: RegExpProps; - - constructor(expectedMsg: rpc.IStreamingMessage, regExpProps: RegExpProps) { - this.expectedMsg = expectedMsg; - this.#regExpProps = regExpProps; - } - - validateRegExpProps(actualMsg: rpc.IStreamingMessage) { - for (const [keyPath, regExp] of Object.entries(this.#regExpProps)) { - let lastKey: string = keyPath; - let lastObject: {} = actualMsg; - let value: unknown = actualMsg; - for (const subpath of keyPath.split('.')) { - if (typeof value === 'object' && value !== null) { - lastKey = subpath; - lastObject = value; - value = value[subpath]; - } else { - break; - } - } - expect(value).to.match(regExp); - - delete lastObject[lastKey]; - } - } -} diff --git a/test/eventHandlers/WorkerInitHandler.test.ts b/test/eventHandlers/WorkerInitHandler.test.ts deleted file mode 100644 index a1b5335..0000000 --- a/test/eventHandlers/WorkerInitHandler.test.ts +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import * as escapeStringRegexp from 'escape-string-regexp'; -import 'mocha'; -import { ITestCallbackContext } from 'mocha'; -import * as mockFs from 'mock-fs'; -import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc'; -import { logColdStartWarning } from '../../src/eventHandlers/WorkerInitHandler'; -import { WorkerChannel } from '../../src/WorkerChannel'; -import { beforeEventHandlerSuite } from './beforeEventHandlerSuite'; -import { RegExpStreamingMessage, TestEventStream } from './TestEventStream'; -import path = require('path'); -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -export namespace Msg { - export function init(functionAppDirectory: string = __dirname, hostVersion = '2.7.0'): rpc.IStreamingMessage { - return { - requestId: 'id', - workerInitRequest: { - capabilities: {}, - functionAppDirectory, - hostVersion, - }, - }; - } - - export const response: rpc.IStreamingMessage = { - requestId: 'id', - workerInitResponse: { - capabilities: { - RpcHttpBodyOnly: 'true', - RpcHttpTriggerMetadataRemoved: 'true', - IgnoreEmptyValuedRpcHttpHeaders: 'true', - UseNullableValueDictionaryForHttp: 'true', - WorkerStatus: 'true', - TypedDataCollection: 'true', - }, - result: { - status: rpc.StatusResult.Status.Success, - }, - }, - }; - - export function failedResponse(fileName: string, errorMessage: string): RegExpStreamingMessage { - const expectedMsg: rpc.IStreamingMessage = { - requestId: 'id', - workerInitResponse: { - result: { - status: rpc.StatusResult.Status.Failure, - exception: { - message: errorMessage, - }, - }, - }, - }; - return new RegExpStreamingMessage(expectedMsg, { - 'workerInitResponse.result.exception.stackTrace': new RegExp( - `Error: ${escapeStringRegexp(errorMessage)}\\s*at` - ), - }); - } - - export const receivedInitLog: rpc.IStreamingMessage = { - rpcLog: { - message: 'Received WorkerInitRequest', - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - - export function loadingEntryPoint(fileName: string): rpc.IStreamingMessage { - return { - rpcLog: { - message: `Loading entry point "${fileName}"`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - } - - export function loadedEntryPoint(fileName: string): rpc.IStreamingMessage { - return { - rpcLog: { - message: `Loaded entry point "${fileName}"`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - } - - export function warning(message: string): rpc.IStreamingMessage { - return { - rpcLog: { - message, - level: LogLevel.Warning, - logCategory: LogCategory.System, - }, - }; - } - - export function error(message: string): rpc.IStreamingMessage { - return { - rpcLog: { - message, - level: LogLevel.Error, - logCategory: LogCategory.System, - }, - }; - } - - export const coldStartWarning: rpc.IStreamingMessage = { - rpcLog: { - message: - 'package.json is not found at the root of the Function App in Azure Files - cold start for NodeJs can be affected.', - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; -} - -describe('WorkerInitHandler', () => { - let channel: WorkerChannel; - let stream: TestEventStream; - - before(() => { - ({ stream, channel } = beforeEventHandlerSuite()); - }); - - afterEach(async () => { - mockFs.restore(); - await stream.afterEachEventHandlerTest(); - }); - - it('responds to init', async () => { - mockFs({ [__dirname]: { 'package.json': '{}' } }); - stream.addTestMessage(Msg.init()); - await stream.assertCalledWith(Msg.receivedInitLog, Msg.response); - }); - - it('does not init for Node.js v8.x and v2 compatability = false', () => { - const version = process.version; - if (version.split('.')[0] === 'v8') { - expect(() => stream.addTestMessage(Msg.init())).to.throw( - `Incompatible Node.js version (${process.version}). The version of the Azure Functions runtime you are using (v3) supports Node.js v10.x and v12.x. Refer to our documentation to see the Node.js versions supported by each version of Azure Functions: https://aka.ms/functions-node-versions` - ); - } - }); - - it('logs AzureFiles cold start warning', async () => { - process.env.WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = 'test'; - process.env.WEBSITE_CONTENTSHARE = 'test'; - process.env.AzureWebJobsScriptRoot = 'test'; - - logColdStartWarning(channel, 10); - - await stream.assertCalledWith(Msg.coldStartWarning); - }); - - it('correctly loads package.json file', async () => { - const appDir = 'appDir'; - const expectedPackageJson = { - type: 'module', - }; - mockFs({ - [appDir]: { - 'package.json': JSON.stringify(expectedPackageJson), - }, - }); - - stream.addTestMessage(Msg.init(appDir)); - await stream.assertCalledWith(Msg.receivedInitLog, Msg.response); - expect(channel.packageJson).to.deep.equal(expectedPackageJson); - }); - - it('loads empty package.json', async () => { - const appDir = 'appDir'; - mockFs({ - [appDir]: { - 'not-package-json': 'some content', - }, - }); - - stream.addTestMessage(Msg.init(appDir)); - await stream.assertCalledWith( - Msg.receivedInitLog, - Msg.warning(`Worker failed to load package.json: file does not exist`), - Msg.response - ); - expect(channel.packageJson).to.be.empty; - }); - - it('ignores malformed package.json', async () => { - const appDir = 'appDir'; - mockFs({ - [appDir]: { - 'package.json': 'gArB@g3 dAtA', - }, - }); - - stream.addTestMessage(Msg.init(appDir)); - await stream.assertCalledWith( - Msg.receivedInitLog, - Msg.warning( - `Worker failed to load package.json: file content is not valid JSON: ${path.join( - appDir, - 'package.json' - )}: Unexpected token g in JSON at position 0` - ), - Msg.response - ); - expect(channel.packageJson).to.be.empty; - }); - - for (const extension of ['.js', '.mjs', '.cjs']) { - it(`Loads entry point (${extension}) in non-specialization scenario`, async () => { - const fileName = `entryPointFiles/doNothing${extension}`; - const expectedPackageJson = { - main: fileName, - }; - mockFs({ - [__dirname]: { - 'package.json': JSON.stringify(expectedPackageJson), - // 'require' and 'mockFs' don't play well together so we need these files in both the mock and real file systems - entryPointFiles: mockFs.load(path.join(__dirname, 'entryPointFiles')), - }, - }); - - stream.addTestMessage(Msg.init(__dirname)); - await stream.assertCalledWith( - Msg.receivedInitLog, - Msg.loadingEntryPoint(fileName), - Msg.loadedEntryPoint(fileName), - Msg.response - ); - }); - } - - it('Fails for missing entry point', async function (this: ITestCallbackContext) { - // Should be re-enabled after https://github.com/Azure/azure-functions-nodejs-worker/pull/577 - this.skip(); - - const fileName = 'entryPointFiles/missing.js'; - const expectedPackageJson = { - main: fileName, - }; - mockFs({ - [__dirname]: { - 'package.json': JSON.stringify(expectedPackageJson), - }, - }); - - stream.addTestMessage(Msg.init(__dirname)); - const errorMessage = `Worker was unable to load entry point "${fileName}": file does not exist`; - await stream.assertCalledWith( - Msg.receivedInitLog, - Msg.loadingEntryPoint(fileName), - Msg.error(errorMessage), - Msg.failedResponse(fileName, errorMessage) - ); - }); - - it('Fails for invalid entry point', async function (this: ITestCallbackContext) { - // Should be re-enabled after https://github.com/Azure/azure-functions-nodejs-worker/pull/577 - this.skip(); - - const fileName = 'entryPointFiles/throwError.js'; - const expectedPackageJson = { - main: fileName, - }; - mockFs({ - [__dirname]: { - 'package.json': JSON.stringify(expectedPackageJson), - // 'require' and 'mockFs' don't play well together so we need these files in both the mock and real file systems - entryPointFiles: mockFs.load(path.join(__dirname, 'entryPointFiles')), - }, - }); - - stream.addTestMessage(Msg.init(__dirname)); - const errorMessage = `Worker was unable to load entry point "${fileName}": test`; - await stream.assertCalledWith( - Msg.receivedInitLog, - Msg.loadingEntryPoint(fileName), - Msg.error(errorMessage), - Msg.failedResponse(fileName, errorMessage) - ); - }); -}); diff --git a/test/eventHandlers/WorkerStatusHandler.test.ts b/test/eventHandlers/WorkerStatusHandler.test.ts deleted file mode 100644 index a99de7f..0000000 --- a/test/eventHandlers/WorkerStatusHandler.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import 'mocha'; -import { beforeEventHandlerSuite } from './beforeEventHandlerSuite'; -import { TestEventStream } from './TestEventStream'; - -describe('WorkerStatusHandler', () => { - let stream: TestEventStream; - - before(() => { - ({ stream } = beforeEventHandlerSuite()); - }); - - afterEach(async () => { - await stream.afterEachEventHandlerTest(); - }); - - it('responds to worker status', async () => { - stream.addTestMessage({ - requestId: 'id', - workerStatusRequest: {}, - }); - await stream.assertCalledWith({ - requestId: 'id', - workerStatusResponse: {}, - }); - }); -}); diff --git a/test/eventHandlers/beforeEventHandlerSuite.ts b/test/eventHandlers/beforeEventHandlerSuite.ts deleted file mode 100644 index c543c4e..0000000 --- a/test/eventHandlers/beforeEventHandlerSuite.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as sinon from 'sinon'; -import { FunctionLoader } from '../../src/FunctionLoader'; -import { setupCoreModule } from '../../src/setupCoreModule'; -import { setupEventStream } from '../../src/setupEventStream'; -import { WorkerChannel } from '../../src/WorkerChannel'; -import { TestEventStream } from './TestEventStream'; - -export function beforeEventHandlerSuite() { - const stream = new TestEventStream(); - const loader = sinon.createStubInstance(FunctionLoader); - const channel = new WorkerChannel(stream, loader); - setupEventStream('workerId', channel); - setupCoreModule(channel); - return { stream, loader, channel }; -} diff --git a/test/eventHandlers/entryPointFiles/doNothing.cjs b/test/eventHandlers/entryPointFiles/doNothing.cjs deleted file mode 100644 index 2f6b2d6..0000000 --- a/test/eventHandlers/entryPointFiles/doNothing.cjs +++ /dev/null @@ -1 +0,0 @@ -// do nothing \ No newline at end of file diff --git a/test/eventHandlers/entryPointFiles/doNothing.js b/test/eventHandlers/entryPointFiles/doNothing.js deleted file mode 100644 index 2f6b2d6..0000000 --- a/test/eventHandlers/entryPointFiles/doNothing.js +++ /dev/null @@ -1 +0,0 @@ -// do nothing \ No newline at end of file diff --git a/test/eventHandlers/entryPointFiles/doNothing.mjs b/test/eventHandlers/entryPointFiles/doNothing.mjs deleted file mode 100644 index 2f6b2d6..0000000 --- a/test/eventHandlers/entryPointFiles/doNothing.mjs +++ /dev/null @@ -1 +0,0 @@ -// do nothing \ No newline at end of file diff --git a/test/eventHandlers/entryPointFiles/throwError.js b/test/eventHandlers/entryPointFiles/throwError.js deleted file mode 100644 index 478d9ec..0000000 --- a/test/eventHandlers/entryPointFiles/throwError.js +++ /dev/null @@ -1 +0,0 @@ -throw new Error('test'); \ No newline at end of file diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..4c17b44 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import * as globby from 'globby'; +import * as Mocha from 'mocha'; +import * as path from 'path'; +import { setupTestCoreApi } from './setupTestCoreApi'; + +export async function run(): Promise { + setupTestCoreApi(); + + const options: Mocha.MochaOptions = { + color: true, + reporter: 'mocha-multi-reporters', + reporterOptions: { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + mochaFile: path.resolve(__dirname, '..', '..', 'test', 'unit-test-results.xml'), + }, + }, + }; + + addEnvVarsToMochaOptions(options); + console.log(`Mocha options: ${JSON.stringify(options, undefined, 2)}`); + + const mocha = new Mocha(options); + + const files: string[] = await globby('**/**.test.js', { cwd: __dirname }); + + files.forEach((f) => mocha.addFile(path.resolve(__dirname, f))); + + const failures = await new Promise((resolve) => mocha.run(resolve)); + if (failures > 0) { + throw new Error(`${failures} tests failed.`); + } +} + +function addEnvVarsToMochaOptions(options: Mocha.MochaOptions): void { + for (const envVar of Object.keys(process.env)) { + const match: RegExpMatchArray | null = envVar.match(/^mocha_(.+)/i); + if (match) { + const [, option] = match; + let value: string | number = process.env[envVar] || ''; + if (typeof value === 'string' && !isNaN(parseInt(value))) { + value = parseInt(value); + } + (options)[option] = value; + } + } +} + +void run(); diff --git a/test/loadScriptFile.test.ts b/test/loadScriptFile.test.ts deleted file mode 100644 index d34e43e..0000000 --- a/test/loadScriptFile.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as chai from 'chai'; -import 'mocha'; -import { isESModule } from '../src/loadScriptFile'; -const expect = chai.expect; - -describe('loadScriptFile', () => { - it('respects .cjs extension', () => { - const result = isESModule('test.cjs', { - type: 'module', - }); - expect(result).to.be.false; - }); - - it('respects .mjs extension', () => { - const result = isESModule('test.mjs', { - type: 'commonjs', - }); - expect(result).to.be.true; - }); - - it('respects package.json module type', () => { - const result = isESModule('test.js', { - type: 'module', - }); - expect(result).to.be.true; - }); - - it('defaults to using commonjs', () => { - expect(isESModule('test.js', {})).to.be.false; - expect( - isESModule('test.js', { - type: 'commonjs', - }) - ).to.be.false; - }); -}); diff --git a/test/mochaReporterOptions.json b/test/mochaReporterOptions.json deleted file mode 100644 index 3c74a8c..0000000 --- a/test/mochaReporterOptions.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "reporterEnabled": "spec, mocha-junit-reporter", - "mochaJunitReporterReporterOptions": { - "mochaFile": "test/unit-test-results.xml" - } -} \ No newline at end of file diff --git a/test/parsers/parsePackageJson.test.ts b/test/parsers/parsePackageJson.test.ts deleted file mode 100644 index 0bbd56a..0000000 --- a/test/parsers/parsePackageJson.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as chai from 'chai'; -import { expect } from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -import 'mocha'; -import * as mockFs from 'mock-fs'; -import { parsePackageJson } from '../../src/parsers/parsePackageJson'; -chai.use(chaiAsPromised); - -describe('parsePackageJson', () => { - const testDir = 'testDir'; - - afterEach(async () => { - mockFs.restore(); - }); - - it('normal', async () => { - mockFs({ [testDir]: { 'package.json': '{ "main": "index.js", "type": "commonjs" }' } }); - await expect(parsePackageJson(testDir)).to.eventually.deep.equal({ main: 'index.js', type: 'commonjs' }); - }); - - it('invalid type', async () => { - mockFs({ [testDir]: { 'package.json': '{ "main": "index.js", "type": {} }' } }); - await expect(parsePackageJson(testDir)).to.eventually.deep.equal({ main: 'index.js' }); - }); - - it('invalid main', async () => { - mockFs({ [testDir]: { 'package.json': '{ "main": 55, "type": "commonjs" }' } }); - await expect(parsePackageJson(testDir)).to.eventually.deep.equal({ type: 'commonjs' }); - }); - - it('missing file', async () => { - await expect(parsePackageJson(testDir)).to.be.rejectedWith('file does not exist'); - }); - - it('empty', async () => { - mockFs({ [testDir]: { 'package.json': '' } }); - await expect(parsePackageJson(testDir)).to.be.rejectedWith(/^file content is not valid JSON:/); - }); - - it('missing bracket', async () => { - mockFs({ [testDir]: { 'package.json': '{' } }); - await expect(parsePackageJson(testDir)).to.be.rejectedWith(/^file content is not valid JSON:/); - }); - - it('null', async () => { - mockFs({ [testDir]: { 'package.json': 'null' } }); - await expect(parsePackageJson(testDir)).to.be.rejectedWith('file content is not an object'); - }); - - it('array', async () => { - mockFs({ [testDir]: { 'package.json': '[]' } }); - await expect(parsePackageJson(testDir)).to.be.rejectedWith('file content is not an object'); - }); -}); diff --git a/test/setupTestCoreApi.ts b/test/setupTestCoreApi.ts new file mode 100644 index 0000000..2247d1f --- /dev/null +++ b/test/setupTestCoreApi.ts @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import Module = require('module'); + +export function setupTestCoreApi(): void { + const coreApi = { + RpcLog: { Level, RpcLogCategory }, + RpcHttpCookie: { SameSite }, + RpcBindingInfo: { Direction, DataType }, + }; + + Module.prototype.require = new Proxy(Module.prototype.require, { + apply(target, thisArg, argArray) { + if (argArray[0] === '@azure/functions-core') { + return coreApi; + } else { + return Reflect.apply(target, thisArg, argArray); + } + }, + }); +} + +enum Level { + Trace = 0, + Debug = 1, + Information = 2, + Warning = 3, + Error = 4, + Critical = 5, + None = 6, +} + +enum RpcLogCategory { + User = 0, + System = 1, + CustomMetric = 2, +} + +enum SameSite { + None = 0, + Lax = 1, + Strict = 2, + ExplicitNone = 3, +} + +enum Direction { + in = 0, + out = 1, + inout = 2, +} + +enum DataType { + undefined = 0, + string = 1, + binary = 2, + stream = 3, +} diff --git a/test/startApp.test.ts b/test/startApp.test.ts deleted file mode 100644 index 297ed70..0000000 --- a/test/startApp.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. - -import * as coreTypes from '@azure/functions-core'; -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc'; -import { WorkerChannel } from '../src/WorkerChannel'; -import { beforeEventHandlerSuite } from './eventHandlers/beforeEventHandlerSuite'; -import { Msg as EnvReloadMsg } from './eventHandlers/FunctionEnvironmentReloadHandler.test'; -import { TestEventStream } from './eventHandlers/TestEventStream'; -import { Msg as WorkerInitMsg } from './eventHandlers/WorkerInitHandler.test'; -import LogCategory = rpc.RpcLog.RpcLogCategory; -import LogLevel = rpc.RpcLog.Level; - -export namespace Msg { - export function executingHooksLog(count: number, hookName: string): rpc.IStreamingMessage { - return { - rpcLog: { - category: undefined, - invocationId: undefined, - message: `Executing ${count} "${hookName}" hooks`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - } - export function executedHooksLog(hookName: string): rpc.IStreamingMessage { - return { - rpcLog: { - category: undefined, - invocationId: undefined, - message: `Executed "${hookName}" hooks`, - level: LogLevel.Debug, - logCategory: LogCategory.System, - }, - }; - } -} - -describe('startApp', () => { - let channel: WorkerChannel; - let stream: TestEventStream; - let coreApi: typeof coreTypes; - let testDisposables: coreTypes.Disposable[] = []; - let originalEnv: NodeJS.ProcessEnv; - let originalCwd: string; - - before(async () => { - originalCwd = process.cwd(); - originalEnv = { ...process.env }; - ({ stream, channel } = beforeEventHandlerSuite()); - coreApi = await import('@azure/functions-core'); - }); - - after(() => { - Object.assign(process.env, originalEnv); - }); - - afterEach(async () => { - await stream.afterEachEventHandlerTest(); - coreApi.Disposable.from(...testDisposables).dispose(); - testDisposables = []; - process.chdir(originalCwd); - channel.appHookData = {}; - channel.appLevelOnlyHookData = {}; - }); - - it('runs app start hooks in non-specialization scenario', async () => { - const hostVersion = '2.7.0'; - const functionAppDirectory = __dirname; - const expectedStartContext: coreTypes.AppStartContext = { - functionAppDirectory, - hostVersion, - hookData: {}, - appHookData: {}, - }; - - const startFunc = sinon.spy(); - testDisposables.push(coreApi.registerHook('appStart', startFunc)); - - stream.addTestMessage(WorkerInitMsg.init(functionAppDirectory, hostVersion)); - - await stream.assertCalledWith( - WorkerInitMsg.receivedInitLog, - WorkerInitMsg.warning('Worker failed to load package.json: file does not exist'), - Msg.executingHooksLog(1, 'appStart'), - Msg.executedHooksLog('appStart'), - WorkerInitMsg.response - ); - - expect(startFunc.callCount).to.be.equal(1); - expect(startFunc.args[0][0]).to.deep.equal(expectedStartContext); - }); - - it('runs app start hooks only once in specialiation scenario', async () => { - const hostVersion = '2.7.0'; - const functionAppDirectory = __dirname; - const expectedStartContext: coreTypes.AppStartContext = { - functionAppDirectory, - hostVersion, - hookData: {}, - appHookData: {}, - }; - const startFunc = sinon.spy(); - - stream.addTestMessage(WorkerInitMsg.init(functionAppDirectory, hostVersion)); - await stream.assertCalledWith( - WorkerInitMsg.receivedInitLog, - WorkerInitMsg.warning('Worker failed to load package.json: file does not exist'), - WorkerInitMsg.response - ); - - testDisposables.push(coreApi.registerHook('appStart', startFunc)); - - stream.addTestMessage({ - requestId: 'id', - functionEnvironmentReloadRequest: { - functionAppDirectory, - }, - }); - await stream.assertCalledWith( - EnvReloadMsg.reloadEnvVarsLog(0), - EnvReloadMsg.changingCwdLog(functionAppDirectory), - WorkerInitMsg.warning('Worker failed to load package.json: file does not exist'), - Msg.executingHooksLog(1, 'appStart'), - Msg.executedHooksLog('appStart'), - EnvReloadMsg.reloadSuccess - ); - - expect(startFunc.callCount).to.be.equal(1); - expect(startFunc.args[0][0]).to.deep.equal(expectedStartContext); - }); - - it('allows different appStart hooks to share data', async () => { - const functionAppDirectory = __dirname; - let hookData = ''; - testDisposables.push( - coreApi.registerHook('appStart', (context) => { - context.hookData.hello = 'world'; - hookData += 'start1'; - }) - ); - testDisposables.push( - coreApi.registerHook('appStart', (context) => { - expect(context.hookData.hello).to.equal('world'); - hookData += 'start2'; - }) - ); - - stream.addTestMessage(WorkerInitMsg.init(functionAppDirectory)); - - await stream.assertCalledWith( - WorkerInitMsg.receivedInitLog, - WorkerInitMsg.warning('Worker failed to load package.json: file does not exist'), - Msg.executingHooksLog(2, 'appStart'), - Msg.executedHooksLog('appStart'), - WorkerInitMsg.response - ); - - expect(hookData).to.equal('start1start2'); - }); -}); diff --git a/types-core/index.d.ts b/types-core/index.d.ts index 70750d6..30713ed 100644 --- a/types-core/index.d.ts +++ b/types-core/index.d.ts @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. -import { AzureFunction, Context } from '@azure/functions'; - /** * This module is shipped as a built-in part of the Azure Functions Node.js worker and is available at runtime */ @@ -10,28 +8,28 @@ declare module '@azure/functions-core' { /** * The version of the Node.js worker */ - export const version: string; + const version: string; /** * Register a hook to interact with the lifecycle of Azure Functions. * Hooks are executed in the order they were registered and will block execution if they throw an error */ - export function registerHook(hookName: 'preInvocation', callback: PreInvocationCallback): Disposable; - export function registerHook(hookName: 'postInvocation', callback: PostInvocationCallback): Disposable; - export function registerHook(hookName: 'appStart', callback: AppStartCallback): Disposable; - export function registerHook(hookName: string, callback: HookCallback): Disposable; + function registerHook(hookName: 'preInvocation', callback: PreInvocationCallback): Disposable; + function registerHook(hookName: 'postInvocation', callback: PostInvocationCallback): Disposable; + function registerHook(hookName: 'appStart', callback: AppStartCallback): Disposable; + function registerHook(hookName: string, callback: HookCallback): Disposable; - export type HookCallback = (context: HookContext) => void | Promise; - export type PreInvocationCallback = (context: PreInvocationContext) => void | Promise; - export type PostInvocationCallback = (context: PostInvocationContext) => void | Promise; - export type AppStartCallback = (context: AppStartContext) => void | Promise; + type HookCallback = (context: HookContext) => void | Promise; + type PreInvocationCallback = (context: PreInvocationContext) => void | Promise; + type PostInvocationCallback = (context: PostInvocationContext) => void | Promise; + type AppStartCallback = (context: AppStartContext) => void | Promise; - export type HookData = { [key: string]: any }; + type HookData = { [key: string]: any }; /** * Base interface for all hook context objects */ - export interface HookContext { + interface HookContext { /** * The recommended place to share data between hooks in the same scope (app-level vs invocation-level) */ @@ -46,11 +44,11 @@ declare module '@azure/functions-core' { * Context on a function that is about to be executed * This object will be passed to all pre invocation hooks */ - export interface PreInvocationContext extends HookContext { + interface PreInvocationContext extends HookContext { /** * The context object passed to the function */ - invocationContext: Context; + invocationContext: unknown; /** * The input values for this specific invocation. Changes to this array _will_ affect the inputs passed to your function @@ -60,18 +58,18 @@ declare module '@azure/functions-core' { /** * The function callback for this specific invocation. Changes to this value _will_ affect the function itself */ - functionCallback: AzureFunction; + functionCallback: FunctionCallback; } /** * Context on a function that has just executed * This object will be passed to all post invocation hooks */ - export interface PostInvocationContext extends HookContext { + interface PostInvocationContext extends HookContext { /** * The context object passed to the function */ - invocationContext: Context; + invocationContext: unknown; /** * The input values for this specific invocation @@ -93,7 +91,7 @@ declare module '@azure/functions-core' { * Context on a function app that is about to be started * This object will be passed to all app start hooks */ - export interface AppStartContext extends HookContext { + interface AppStartContext extends HookContext { /** * Absolute directory of the function app */ @@ -107,7 +105,7 @@ declare module '@azure/functions-core' { /** * Represents a type which can release resources, such as event listening or a timer. */ - export class Disposable { + class Disposable { /** * Combine many disposable-likes into one. You can use this method when having objects with a dispose function which aren't instances of `Disposable`. * @@ -129,4 +127,386 @@ declare module '@azure/functions-core' { */ dispose(): any; } + + /** + * Registers the main programming model to be used for a Node.js function app + * Only one programming model can be set. The last programming model registered will be used + * If not explicitly set, a default programming model included with the worker will be used + */ + function setProgrammingModel(programmingModel: ProgrammingModel): void; + + /** + * Returns the currently registered programming model + * If not explicitly set, a default programming model included with the worker will be used + */ + function getProgrammingModel(): ProgrammingModel; + + /** + * A set of information and methods that describe the model for handling a Node.js function app + * Currently, this is mainly focused on invocation + */ + interface ProgrammingModel { + /** + * A name for this programming model, generally only used for tracking purposes + */ + name: string; + + /** + * A version for this programming model, generally only used for tracking purposes + */ + version: string; + + /** + * Returns a new instance of the invocation model for each invocation + */ + getInvocationModel(coreContext: CoreInvocationContext): InvocationModel; + } + + /** + * Basic information and helper methods about an invocation provided from the core worker to the programming model + */ + interface CoreInvocationContext { + /** + * A guid unique to this invocation + */ + invocationId: string; + + /** + * The invocation request received by the worker from the host + */ + request: RpcInvocationRequest; + + /** + * Metadata about the function + */ + metadata: RpcFunctionMetadata; + + /** + * Describes the current state of invocation, or undefined if between states + */ + state?: InvocationState; + + /** + * The recommended way to log information + */ + log(level: RpcLog.Level, category: RpcLog.RpcLogCategory, message: string): void; + } + + type InvocationState = 'preInvocationHooks' | 'postInvocationHooks' | 'invocation'; + + /** + * A set of methods that describe the model for invoking a function + */ + interface InvocationModel { + /** + * Returns the context object and inputs to be passed to all following invocation methods + * This is run before preInvocation hooks + */ + getArguments(): Promise; + + /** + * The main method that executes the user's function callback + * This is run between preInvocation and postInvocation hooks + * @param context The context object returned in `getArguments`, potentially modified by preInvocation hooks + * @param inputs The input array returned in `getArguments`, potentially modified by preInvocation hooks + * @param callback The function callback to be executed + */ + invokeFunction(context: unknown, inputs: unknown[], callback: FunctionCallback): Promise; + + /** + * Returns the invocation response to send back to the host + * This is run after postInvocation hooks + * @param context The context object created in `getArguments` + * @param result The result of the function callback, potentially modified by postInvocation hooks + */ + getResponse(context: unknown, result: unknown): Promise; + } + + interface InvocationArguments { + /** + * This is usually the first argument passed to a function callback + */ + context: unknown; + + /** + * The remaining arguments passed to a function callback, generally describing the trigger/input bindings + */ + inputs: unknown[]; + } + + type FunctionCallback = (context: unknown, ...inputs: unknown[]) => unknown; + + // #region rpc types + interface RpcFunctionMetadata { + name?: string | null; + + directory?: string | null; + + scriptFile?: string | null; + + entryPoint?: string | null; + + bindings?: { [k: string]: RpcBindingInfo } | null; + + isProxy?: boolean | null; + + status?: RpcStatusResult | null; + + language?: string | null; + + rawBindings?: string[] | null; + + functionId?: string | null; + + managedDependencyEnabled?: boolean | null; + } + + interface RpcStatusResult { + status?: RpcStatusResult.Status | null; + + result?: string | null; + + exception?: RpcException | null; + + logs?: RpcLog[] | null; + } + + namespace RpcStatusResult { + enum Status { + Failure = 0, + Success = 1, + Cancelled = 2, + } + } + + interface RpcLog { + invocationId?: string | null; + + category?: string | null; + + level?: RpcLog.Level | null; + + message?: string | null; + + eventId?: string | null; + + exception?: RpcException | null; + + logCategory?: RpcLog.RpcLogCategory | null; + } + + namespace RpcLog { + enum Level { + Trace = 0, + Debug = 1, + Information = 2, + Warning = 3, + Error = 4, + Critical = 5, + None = 6, + } + + enum RpcLogCategory { + User = 0, + System = 1, + CustomMetric = 2, + } + } + + interface RpcException { + source?: string | null; + + stackTrace?: string | null; + + message?: string | null; + } + + interface RpcBindingInfo { + type?: string | null; + + direction?: RpcBindingInfo.Direction | null; + + dataType?: RpcBindingInfo.DataType | null; + } + + namespace RpcBindingInfo { + enum Direction { + in = 0, + out = 1, + inout = 2, + } + + enum DataType { + undefined = 0, + string = 1, + binary = 2, + stream = 3, + } + } + + interface RpcTypedData { + string?: string | null; + + json?: string | null; + + bytes?: Uint8Array | null; + + stream?: Uint8Array | null; + + http?: RpcHttpData | null; + + int?: number | Long | null; + + double?: number | null; + + collectionBytes?: RpcCollectionBytes | null; + + collectionString?: RpcCollectionString | null; + + collectionDouble?: RpcCollectionDouble | null; + + collectionSint64?: RpcCollectionSInt64 | null; + } + + interface RpcCollectionSInt64 { + sint64?: (number | Long)[] | null; + } + + interface RpcCollectionString { + string?: string[] | null; + } + + interface RpcCollectionBytes { + bytes?: Uint8Array[] | null; + } + + interface RpcCollectionDouble { + double?: number[] | null; + } + + interface RpcInvocationRequest { + invocationId?: string | null; + + functionId?: string | null; + + inputData?: RpcParameterBinding[] | null; + + triggerMetadata?: { [k: string]: RpcTypedData } | null; + + traceContext?: RpcTraceContext | null; + + retryContext?: RpcRetryContext | null; + } + + interface RpcTraceContext { + traceParent?: string | null; + + traceState?: string | null; + + attributes?: { [k: string]: string } | null; + } + + interface RpcRetryContext { + retryCount?: number | null; + + maxRetryCount?: number | null; + + exception?: RpcException | null; + } + + interface RpcInvocationResponse { + invocationId?: string | null; + + outputData?: RpcParameterBinding[] | null; + + returnValue?: RpcTypedData | null; + + result?: RpcStatusResult | null; + } + + interface RpcParameterBinding { + name?: string | null; + + data?: RpcTypedData | null; + } + + interface RpcHttpData { + method?: string | null; + + url?: string | null; + + headers?: { [k: string]: string } | null; + + body?: RpcTypedData | null; + + params?: { [k: string]: string } | null; + + statusCode?: string | null; + + query?: { [k: string]: string } | null; + + enableContentNegotiation?: boolean | null; + + rawBody?: RpcTypedData | null; + + cookies?: RpcHttpCookie[] | null; + + nullableHeaders?: { [k: string]: RpcNullableString } | null; + + nullableParams?: { [k: string]: RpcNullableString } | null; + + nullableQuery?: { [k: string]: RpcNullableString } | null; + } + + interface RpcHttpCookie { + name?: string | null; + + value?: string | null; + + domain?: RpcNullableString | null; + + path?: RpcNullableString | null; + + expires?: RpcNullableTimestamp | null; + + secure?: RpcNullableBool | null; + + httpOnly?: RpcNullableBool | null; + + sameSite?: RpcHttpCookie.SameSite | null; + + maxAge?: RpcNullableDouble | null; + } + + interface RpcNullableString { + value?: string | null; + } + + interface RpcNullableDouble { + value?: number | null; + } + + interface RpcNullableBool { + value?: boolean | null; + } + + interface RpcNullableTimestamp { + value?: RpcTimestamp | null; + } + + interface RpcTimestamp { + seconds?: number | Long | null; + + nanos?: number | null; + } + + namespace RpcHttpCookie { + enum SameSite { + None = 0, + Lax = 1, + Strict = 2, + ExplicitNone = 3, + } + } + // #endregion rpc types } diff --git a/types/.npmignore b/types/.npmignore deleted file mode 100644 index 85a1c5c..0000000 --- a/types/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -# **NOTE**: This file is only used if you pack locally. -# The official build/release pipeline has separate logic to control the package contents in "./azure-pipelines/build.yml". - -*.test.ts -tsconfig.json -dist -*.tgz \ No newline at end of file diff --git a/types/LICENSE b/types/LICENSE deleted file mode 100644 index a33fe1c..0000000 --- a/types/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) .NET Foundation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE diff --git a/types/index.d.ts b/types/index.d.ts index 8db52bf..ff20287 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -2,6 +2,11 @@ // Licensed under the MIT License. declare module '@azure/functions' { + /** + * Sets the programming model contained in this package to be the one used by the Azure Functions worker + */ + function setup(): void; + /** * Interface for your Azure Function code. This function must be exported (via module.exports or exports) * and will execute when triggered. It is recommended that you declare this function as async, which diff --git a/types/package.json b/types/package.json deleted file mode 100644 index 1990cff..0000000 --- a/types/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@azure/functions", - "version": "3.4.0", - "description": "Azure Functions types for Typescript", - "repository": { - "type": "git", - "url": "https://github.com/Azure/azure-functions-nodejs-worker/tree/v3.x/types" - }, - "keywords": [ - "azure-functions", - "typescript" - ], - "types": "./index.d.ts", - "author": "Microsoft", - "license": "MIT" -} diff --git a/webpack.config.js b/webpack.config.js index 4ca90b1..3998fbb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,16 +1,17 @@ module.exports = { - entry: "./dist/src/Worker.js", + entry: "./dist/src/index.js", output: { path: `${__dirname}/dist/src`, - filename: "worker-bundle.js", - library: "worker", + filename: "index-bundle.js", libraryTarget: "commonjs2" }, target: 'node', node: { __dirname: false }, - externals: [], + externals: { + '@azure/functions-core': 'commonjs2 @azure/functions-core' + }, module: { parser: { javascript: { diff --git a/worker.config.json b/worker.config.json deleted file mode 100644 index c2fcd2f..0000000 --- a/worker.config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "description":{ - "language":"node", - "extensions":[".js", ".mjs", ".cjs"], - "defaultExecutablePath":"node", - "defaultWorkerPath":"dist/src/nodejsWorker.js" - } -} \ No newline at end of file