From e35ea157dc88ea70bd1705cd91abb879f3064c1a Mon Sep 17 00:00:00 2001 From: Daniel Castro Date: Mon, 23 Oct 2023 14:39:12 -0700 Subject: [PATCH 1/7] Add warmup trigger --- src/app.ts | 5 +++++ src/trigger.ts | 9 +++++++++ types/app.d.ts | 8 ++++++++ types/index.d.ts | 1 + types/warmup.d.ts | 23 +++++++++++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 types/warmup.d.ts diff --git a/src/app.ts b/src/app.ts index 8dc80a8..7c90e80 100644 --- a/src/app.ts +++ b/src/app.ts @@ -19,6 +19,7 @@ import { StorageBlobFunctionOptions, StorageQueueFunctionOptions, TimerFunctionOptions, + WarmupFunctionOptions, } from '@azure/functions'; import * as coreTypes from '@azure/functions-core'; import { CoreInvocationContext, FunctionCallback } from '@azure/functions-core'; @@ -145,6 +146,10 @@ export function cosmosDB(name: string, options: CosmosDBFunctionOptions): void { generic(name, convertToGenericOptions(options, trigger.cosmosDB)); } +export function warmup(name: string, options: WarmupFunctionOptions): void { + generic(name, convertToGenericOptions(options, trigger.warmup)); +} + export function generic(name: string, options: GenericFunctionOptions): void { if (!hasSetup) { setup(); diff --git a/src/trigger.ts b/src/trigger.ts index bdda8ee..d7749fd 100644 --- a/src/trigger.ts +++ b/src/trigger.ts @@ -22,6 +22,8 @@ import { StorageQueueTriggerOptions, TimerTrigger, TimerTriggerOptions, + WarmupTrigger, + WarmupTriggerOptions, } from '@azure/functions'; import { addBindingName } from './addBindingName'; @@ -90,6 +92,13 @@ export function cosmosDB(options: CosmosDBTriggerOptions): CosmosDBTrigger { }); } +export function warmup(options: WarmupTriggerOptions): WarmupTrigger { + return addTriggerBindingName({ + ...options, + type: 'warmupTrigger', + }); +} + export function generic(options: GenericTriggerOptions): FunctionTrigger { return addTriggerBindingName(options); } diff --git a/types/app.d.ts b/types/app.d.ts index 64a6d9c..3ad81d3 100644 --- a/types/app.d.ts +++ b/types/app.d.ts @@ -9,6 +9,7 @@ import { HttpFunctionOptions, HttpHandler, HttpMethodFunctionOptions } from './h import { ServiceBusQueueFunctionOptions, ServiceBusTopicFunctionOptions } from './serviceBus'; import { StorageBlobFunctionOptions, StorageQueueFunctionOptions } from './storage'; import { TimerFunctionOptions } from './timer'; +import { WarmupFunctionOptions } from './warmup'; /** * Registers an http function in your app that will be triggered by making a request to the function url @@ -143,6 +144,13 @@ export function eventGrid(name: string, options: EventGridFunctionOptions): void */ export function cosmosDB(name: string, options: CosmosDBFunctionOptions): void; +/** + * Registers a function in your app that will be triggered when an instance is added to scale a running function app + * @param name The name of the function. The name must be unique within your app and will mostly be used for your own tracking purposes + * @param options Configuration options describing the inputs, outputs, and handler for this function + */ +export function warmup(name: string, options: WarmupFunctionOptions): void; + /** * Registers a generic function in your app that will be triggered based on the type specified in `options.trigger.type` * Use this method if your desired trigger type does not already have its own method diff --git a/types/index.d.ts b/types/index.d.ts index 5880bbe..5cb8843 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -20,6 +20,7 @@ export * from './storage'; export * from './table'; export * from './timer'; export * as trigger from './trigger'; +export * from './warmup'; /** * Void if no `return` output is registered diff --git a/types/warmup.d.ts b/types/warmup.d.ts new file mode 100644 index 0000000..680cd53 --- /dev/null +++ b/types/warmup.d.ts @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import { FunctionOptions, FunctionResult, FunctionTrigger, RetryOptions } from './index'; +import { InvocationContext } from './InvocationContext'; + +export type WarmupHandler = (warmupContext: object, context: InvocationContext) => FunctionResult; + +export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial { + handler: WarmupHandler; + + trigger?: WarmupTrigger; + + /** + * An optional retry policy to rerun a failed execution until either successful completion occurs or the maximum number of retries is reached. + * Learn more [here](https://learn.microsoft.com/azure/azure-functions/functions-bindings-error-pages) + */ + retry?: RetryOptions; +} + +export interface WarmupTriggerOptions {} + +export type WarmupTrigger = FunctionTrigger & WarmupTriggerOptions; From b3a615c238c32f7ae35a750d5d8966bb55b5221a Mon Sep 17 00:00:00 2001 From: Daniel Castro Date: Mon, 30 Oct 2023 15:06:53 -0700 Subject: [PATCH 2/7] Additional changes to types --- src/app.ts | 3 ++- types/trigger.d.ts | 6 ++++++ types/warmup.d.ts | 10 +--------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/app.ts b/src/app.ts index 7c90e80..b373d81 100644 --- a/src/app.ts +++ b/src/app.ts @@ -147,7 +147,8 @@ export function cosmosDB(name: string, options: CosmosDBFunctionOptions): void { } export function warmup(name: string, options: WarmupFunctionOptions): void { - generic(name, convertToGenericOptions(options, trigger.warmup)); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + generic(name, convertToGenericOptions(options, trigger.warmup)); } export function generic(name: string, options: GenericFunctionOptions): void { diff --git a/types/trigger.d.ts b/types/trigger.d.ts index f58af4f..9826b45 100644 --- a/types/trigger.d.ts +++ b/types/trigger.d.ts @@ -20,6 +20,7 @@ import { StorageQueueTriggerOptions, } from './storage'; import { TimerTrigger, TimerTriggerOptions } from './timer'; +import { WarmupTrigger, WarmupTriggerOptions } from './warmup'; /** * [Link to docs and examples](https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?&pivots=programming-language-javascript) @@ -66,6 +67,11 @@ export function eventGrid(options: EventGridTriggerOptions): EventGridTrigger; */ export function cosmosDB(options: CosmosDBTriggerOptions): CosmosDBTrigger; +/** + * [Link to docs and examples](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-warmup?tabs=isolated-process&pivots=programming-language-javascript) + */ +export function warmup(options: WarmupTriggerOptions): WarmupTrigger; + /** * A generic option that can be used for any trigger type * Use this method if your desired trigger type does not already have its own method diff --git a/types/warmup.d.ts b/types/warmup.d.ts index 680cd53..674c6ac 100644 --- a/types/warmup.d.ts +++ b/types/warmup.d.ts @@ -1,21 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. -import { FunctionOptions, FunctionResult, FunctionTrigger, RetryOptions } from './index'; +import { FunctionOptions, FunctionResult, FunctionTrigger } from './index'; import { InvocationContext } from './InvocationContext'; export type WarmupHandler = (warmupContext: object, context: InvocationContext) => FunctionResult; export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial { handler: WarmupHandler; - - trigger?: WarmupTrigger; - - /** - * An optional retry policy to rerun a failed execution until either successful completion occurs or the maximum number of retries is reached. - * Learn more [here](https://learn.microsoft.com/azure/azure-functions/functions-bindings-error-pages) - */ - retry?: RetryOptions; } export interface WarmupTriggerOptions {} From 0027fa9d52c05df513894790282cd9175cd189e3 Mon Sep 17 00:00:00 2001 From: Daniel Castro Date: Mon, 30 Oct 2023 16:26:59 -0700 Subject: [PATCH 3/7] Add back trigger property --- types/warmup.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/warmup.d.ts b/types/warmup.d.ts index 674c6ac..b9f3d26 100644 --- a/types/warmup.d.ts +++ b/types/warmup.d.ts @@ -8,6 +8,8 @@ export type WarmupHandler = (warmupContext: object, context: InvocationContext) export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial { handler: WarmupHandler; + + trigger?: WarmupTrigger; } export interface WarmupTriggerOptions {} From 37b2624f50d88538dd2d58448e3a624a9c605309 Mon Sep 17 00:00:00 2001 From: Daniel Castro Date: Mon, 30 Oct 2023 21:14:33 -0700 Subject: [PATCH 4/7] Minor changes to docs, types, signatures --- src/addBindingName.ts | 8 ++++---- src/app.ts | 3 +-- types/warmup.d.ts | 8 +++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/addBindingName.ts b/src/addBindingName.ts index 6073e97..9c20c04 100644 --- a/src/addBindingName.ts +++ b/src/addBindingName.ts @@ -1,17 +1,17 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. +export { InvocationContext } from './InvocationContext'; export { HttpRequest } from './http/HttpRequest'; export { HttpResponse } from './http/HttpResponse'; -export { InvocationContext } from './InvocationContext'; const bindingCounts: Record = {}; /** - * If the host spawns multiple workers, it expects the metadata (including binding name) to be the same accross workers - * That means we need to generate binding names in a deterministic fashion, so we'll do that using a count + * If the host spawns multiple workers, it expects the metadata (including binding name) to be the same across workers. + * Therefore, we need to generate binding names in a deterministic fashion. We'll do so using a count. * There's a tiny risk users register bindings in a non-deterministic order (i.e. async race conditions), but it's okay considering the following: * 1. We will track the count individually for each binding type. This makes the names more readable and reduces the chances a race condition will matter - * 2. Users can manually specify the name themselves (aka if they're doing weird async stuff) and we will respect that + * 2. Users can manually specify the name themselves (e.g. if they're doing weird async stuff) and we will respect that * More info here: https://github.com/Azure/azure-functions-nodejs-worker/issues/638 */ export function addBindingName( diff --git a/src/app.ts b/src/app.ts index b373d81..7c90e80 100644 --- a/src/app.ts +++ b/src/app.ts @@ -147,8 +147,7 @@ export function cosmosDB(name: string, options: CosmosDBFunctionOptions): void { } export function warmup(name: string, options: WarmupFunctionOptions): void { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - generic(name, convertToGenericOptions(options, trigger.warmup)); + generic(name, convertToGenericOptions(options, trigger.warmup)); } export function generic(name: string, options: GenericFunctionOptions): void { diff --git a/types/warmup.d.ts b/types/warmup.d.ts index b9f3d26..680cd53 100644 --- a/types/warmup.d.ts +++ b/types/warmup.d.ts @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. -import { FunctionOptions, FunctionResult, FunctionTrigger } from './index'; +import { FunctionOptions, FunctionResult, FunctionTrigger, RetryOptions } from './index'; import { InvocationContext } from './InvocationContext'; export type WarmupHandler = (warmupContext: object, context: InvocationContext) => FunctionResult; @@ -10,6 +10,12 @@ export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial Date: Tue, 31 Oct 2023 09:44:37 -0700 Subject: [PATCH 5/7] Revert a few minor edits --- src/addBindingName.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addBindingName.ts b/src/addBindingName.ts index 9c20c04..9373d59 100644 --- a/src/addBindingName.ts +++ b/src/addBindingName.ts @@ -8,10 +8,10 @@ export { HttpResponse } from './http/HttpResponse'; const bindingCounts: Record = {}; /** * If the host spawns multiple workers, it expects the metadata (including binding name) to be the same across workers. - * Therefore, we need to generate binding names in a deterministic fashion. We'll do so using a count. + * That means we need to generate binding names in a deterministic fashion, so we'll do that using a count * There's a tiny risk users register bindings in a non-deterministic order (i.e. async race conditions), but it's okay considering the following: * 1. We will track the count individually for each binding type. This makes the names more readable and reduces the chances a race condition will matter - * 2. Users can manually specify the name themselves (e.g. if they're doing weird async stuff) and we will respect that + * 2. Users can manually specify the name themselves (aka if they're doing weird async stuff) and we will respect that * More info here: https://github.com/Azure/azure-functions-nodejs-worker/issues/638 */ export function addBindingName( From 5d25a453ee4f42c4abd7467c007052c579bc95b3 Mon Sep 17 00:00:00 2001 From: Daniel Castro Date: Tue, 31 Oct 2023 13:45:14 -0700 Subject: [PATCH 6/7] PR review suggestions --- types/app.d.ts | 7 ++++++- types/trigger.d.ts | 2 +- types/warmup.d.ts | 8 +------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/types/app.d.ts b/types/app.d.ts index 3ad81d3..dffcda9 100644 --- a/types/app.d.ts +++ b/types/app.d.ts @@ -145,7 +145,12 @@ export function eventGrid(name: string, options: EventGridFunctionOptions): void export function cosmosDB(name: string, options: CosmosDBFunctionOptions): void; /** - * Registers a function in your app that will be triggered when an instance is added to scale a running function app + * Registers a function in your app that will be triggered when an instance is added to scale a running function app. + * The warmup trigger is only called during scale-out operations, not during restarts or other non-scale startups. + * Make sure your logic can load all required dependencies without relying on the warmup trigger. + * Lazy loading is a good pattern to achieve this goal. + * The warmup trigger isn't available to apps running on the Consumption plan. + * For more information, please see the [Azure Functions warmup trigger documentation](https://learn.microsoft.com/azure/azure-functions/functions-bindings-warmup?tabs=isolated-process&pivots=programming-language-javascript). * @param name The name of the function. The name must be unique within your app and will mostly be used for your own tracking purposes * @param options Configuration options describing the inputs, outputs, and handler for this function */ diff --git a/types/trigger.d.ts b/types/trigger.d.ts index 9826b45..bfae17c 100644 --- a/types/trigger.d.ts +++ b/types/trigger.d.ts @@ -68,7 +68,7 @@ export function eventGrid(options: EventGridTriggerOptions): EventGridTrigger; export function cosmosDB(options: CosmosDBTriggerOptions): CosmosDBTrigger; /** - * [Link to docs and examples](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-warmup?tabs=isolated-process&pivots=programming-language-javascript) + * [Link to docs and examples](https://learn.microsoft.com/azure/azure-functions/functions-bindings-warmup?tabs=isolated-process&pivots=programming-language-javascript) */ export function warmup(options: WarmupTriggerOptions): WarmupTrigger; diff --git a/types/warmup.d.ts b/types/warmup.d.ts index 680cd53..b9f3d26 100644 --- a/types/warmup.d.ts +++ b/types/warmup.d.ts @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. -import { FunctionOptions, FunctionResult, FunctionTrigger, RetryOptions } from './index'; +import { FunctionOptions, FunctionResult, FunctionTrigger } from './index'; import { InvocationContext } from './InvocationContext'; export type WarmupHandler = (warmupContext: object, context: InvocationContext) => FunctionResult; @@ -10,12 +10,6 @@ export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial Date: Tue, 31 Oct 2023 15:14:20 -0700 Subject: [PATCH 7/7] Add interface for warmupContext --- types/warmup.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/warmup.d.ts b/types/warmup.d.ts index b9f3d26..cea229c 100644 --- a/types/warmup.d.ts +++ b/types/warmup.d.ts @@ -4,7 +4,8 @@ import { FunctionOptions, FunctionResult, FunctionTrigger } from './index'; import { InvocationContext } from './InvocationContext'; -export type WarmupHandler = (warmupContext: object, context: InvocationContext) => FunctionResult; +export interface WarmupContextOptions {} +export type WarmupHandler = (warmupContext: WarmupContextOptions, context: InvocationContext) => FunctionResult; export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial { handler: WarmupHandler; @@ -13,5 +14,4 @@ export interface WarmupFunctionOptions extends WarmupTriggerOptions, Partial