1 |
|
2 |
|
3 |
|
4 | import {
|
5 | CosmosDBFunctionOptions,
|
6 | EventGridFunctionOptions,
|
7 | EventHubFunctionOptions,
|
8 | ExponentialBackoffRetryOptions,
|
9 | FixedDelayRetryOptions,
|
10 | FunctionOptions,
|
11 | FunctionTrigger,
|
12 | GenericFunctionOptions,
|
13 | HttpFunctionOptions,
|
14 | HttpHandler,
|
15 | HttpMethod,
|
16 | HttpMethodFunctionOptions,
|
17 | ServiceBusQueueFunctionOptions,
|
18 | ServiceBusTopicFunctionOptions,
|
19 | StorageBlobFunctionOptions,
|
20 | StorageQueueFunctionOptions,
|
21 | TimerFunctionOptions,
|
22 | WarmupFunctionOptions,
|
23 | } from '@azure/functions';
|
24 | import * as coreTypes from '@azure/functions-core';
|
25 | import { CoreInvocationContext, FunctionCallback } from '@azure/functions-core';
|
26 | import { returnBindingKey, version } from './constants';
|
27 | import { toRpcDuration } from './converters/toRpcDuration';
|
28 | import { InvocationModel } from './InvocationModel';
|
29 | import * as output from './output';
|
30 | import * as trigger from './trigger';
|
31 | import { isTrigger } from './utils/isTrigger';
|
32 | import { tryGetCoreApiLazy } from './utils/tryGetCoreApiLazy';
|
33 |
|
34 | export * as hook from './hooks/registerHook';
|
35 |
|
36 | class ProgrammingModel implements coreTypes.ProgrammingModel {
|
37 | name = '@azure/functions';
|
38 | version = version;
|
39 | getInvocationModel(coreCtx: CoreInvocationContext): InvocationModel {
|
40 | return new InvocationModel(coreCtx);
|
41 | }
|
42 | }
|
43 |
|
44 | let hasSetup = false;
|
45 | function setup() {
|
46 | const coreApi = tryGetCoreApiLazy();
|
47 | if (!coreApi) {
|
48 | console.warn(
|
49 | 'WARNING: Failed to detect the Azure Functions runtime. Switching "@azure/functions" package to test mode - not all features are supported.'
|
50 | );
|
51 | } else {
|
52 | coreApi.setProgrammingModel(new ProgrammingModel());
|
53 | }
|
54 | hasSetup = true;
|
55 | }
|
56 |
|
57 | function convertToHttpOptions(
|
58 | optionsOrHandler: HttpFunctionOptions | HttpHandler,
|
59 | method: HttpMethod
|
60 | ): HttpFunctionOptions {
|
61 | const options: HttpFunctionOptions =
|
62 | typeof optionsOrHandler === 'function' ? { handler: optionsOrHandler } : optionsOrHandler;
|
63 | options.methods = [method];
|
64 | return options;
|
65 | }
|
66 |
|
67 | function convertToGenericOptions<T extends Omit<FunctionOptions, 'trigger'> & Partial<FunctionOptions>>(
|
68 | options: T,
|
69 | triggerMethod: (o: Omit<T, 'handler' | 'return' | 'trigger' | 'extraInputs' | 'extraOutputs'>) => FunctionTrigger
|
70 | ): FunctionOptions {
|
71 | const { handler, return: ret, trigger, extraInputs, extraOutputs, ...triggerOptions } = options;
|
72 | return {
|
73 | trigger: trigger ?? triggerMethod(triggerOptions),
|
74 | return: ret,
|
75 | extraInputs,
|
76 | extraOutputs,
|
77 | handler,
|
78 | };
|
79 | }
|
80 |
|
81 | export function get(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
|
82 | http(name, convertToHttpOptions(optionsOrHandler, 'GET'));
|
83 | }
|
84 |
|
85 | export function put(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
|
86 | http(name, convertToHttpOptions(optionsOrHandler, 'PUT'));
|
87 | }
|
88 |
|
89 | export function post(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
|
90 | http(name, convertToHttpOptions(optionsOrHandler, 'POST'));
|
91 | }
|
92 |
|
93 | export function patch(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
|
94 | http(name, convertToHttpOptions(optionsOrHandler, 'PATCH'));
|
95 | }
|
96 |
|
97 | export function deleteRequest(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
|
98 | http(name, convertToHttpOptions(optionsOrHandler, 'DELETE'));
|
99 | }
|
100 |
|
101 | export function http(name: string, options: HttpFunctionOptions): void {
|
102 | options.return ||= output.http({});
|
103 | generic(name, convertToGenericOptions(options, trigger.http));
|
104 | }
|
105 |
|
106 | export function timer(name: string, options: TimerFunctionOptions): void {
|
107 | generic(name, convertToGenericOptions(options, trigger.timer));
|
108 | }
|
109 |
|
110 | export function storageBlob(name: string, options: StorageBlobFunctionOptions): void {
|
111 | generic(name, convertToGenericOptions(options, trigger.storageBlob));
|
112 | }
|
113 |
|
114 | export function storageQueue(name: string, options: StorageQueueFunctionOptions): void {
|
115 | generic(name, convertToGenericOptions(options, trigger.storageQueue));
|
116 | }
|
117 |
|
118 | export function serviceBusQueue(name: string, options: ServiceBusQueueFunctionOptions): void {
|
119 | generic(name, convertToGenericOptions(options, trigger.serviceBusQueue));
|
120 | }
|
121 |
|
122 | export function serviceBusTopic(name: string, options: ServiceBusTopicFunctionOptions): void {
|
123 | generic(name, convertToGenericOptions(options, trigger.serviceBusTopic));
|
124 | }
|
125 |
|
126 | export function eventHub(name: string, options: EventHubFunctionOptions): void {
|
127 | generic(name, convertToGenericOptions(options, trigger.eventHub));
|
128 | }
|
129 |
|
130 | export function eventGrid(name: string, options: EventGridFunctionOptions): void {
|
131 | generic(name, convertToGenericOptions(options, trigger.eventGrid));
|
132 | }
|
133 |
|
134 | export function cosmosDB(name: string, options: CosmosDBFunctionOptions): void {
|
135 |
|
136 | generic(name, convertToGenericOptions(options, <any>trigger.cosmosDB));
|
137 | }
|
138 |
|
139 | export function warmup(name: string, options: WarmupFunctionOptions): void {
|
140 | generic(name, convertToGenericOptions(options, trigger.warmup));
|
141 | }
|
142 |
|
143 | export function generic(name: string, options: GenericFunctionOptions): void {
|
144 | if (!hasSetup) {
|
145 | setup();
|
146 | }
|
147 |
|
148 | const bindings: Record<string, coreTypes.RpcBindingInfo> = {};
|
149 |
|
150 | const trigger = options.trigger;
|
151 | bindings[trigger.name] = {
|
152 | ...trigger,
|
153 | direction: 'in',
|
154 | type: isTrigger(trigger.type) ? trigger.type : trigger.type + 'Trigger',
|
155 | };
|
156 |
|
157 | if (options.extraInputs) {
|
158 | for (const input of options.extraInputs) {
|
159 | bindings[input.name] = {
|
160 | ...input,
|
161 | direction: 'in',
|
162 | };
|
163 | }
|
164 | }
|
165 |
|
166 | if (options.return) {
|
167 | bindings[returnBindingKey] = {
|
168 | ...options.return,
|
169 | direction: 'out',
|
170 | };
|
171 | }
|
172 |
|
173 | if (options.extraOutputs) {
|
174 | for (const output of options.extraOutputs) {
|
175 | bindings[output.name] = {
|
176 | ...output,
|
177 | direction: 'out',
|
178 | };
|
179 | }
|
180 | }
|
181 |
|
182 | let retryOptions: coreTypes.RpcRetryOptions | undefined;
|
183 | if (options.retry) {
|
184 | retryOptions = {
|
185 | ...options.retry,
|
186 | retryStrategy: options.retry.strategy,
|
187 | delayInterval: toRpcDuration((<FixedDelayRetryOptions>options.retry).delayInterval, 'retry.delayInterval'),
|
188 | maximumInterval: toRpcDuration(
|
189 | (<ExponentialBackoffRetryOptions>options.retry).maximumInterval,
|
190 | 'retry.maximumInterval'
|
191 | ),
|
192 | minimumInterval: toRpcDuration(
|
193 | (<ExponentialBackoffRetryOptions>options.retry).minimumInterval,
|
194 | 'retry.minimumInterval'
|
195 | ),
|
196 | };
|
197 | }
|
198 |
|
199 | const coreApi = tryGetCoreApiLazy();
|
200 | if (!coreApi) {
|
201 | console.warn(
|
202 | `WARNING: Skipping call to register function "${name}" because the "@azure/functions" package is in test mode.`
|
203 | );
|
204 | } else {
|
205 | coreApi.registerFunction({ name, bindings, retryOptions }, <FunctionCallback>options.handler);
|
206 | }
|
207 | }
|