Skip to content

Refactor namespaces to modules #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/addBindingName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

export { HttpRequest } from './http/HttpRequest';
export { HttpResponse } from './http/HttpResponse';
export { InvocationContext } from './InvocationContext';

const bindingCounts: Record<string, number> = {};
/**
* 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
* 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
* More info here: https://github.com/Azure/azure-functions-nodejs-worker/issues/638
*/
export function addBindingName<T extends { type: string; name?: string }>(
binding: T,
suffix: string
): T & { name: string } {
if (!binding.name) {
let bindingType = binding.type;
if (!bindingType.toLowerCase().endsWith(suffix.toLowerCase())) {
bindingType += suffix;
}
let count = bindingCounts[bindingType] || 0;
count += 1;
bindingCounts[bindingType] = count;
binding.name = bindingType + count.toString();
}
return <T & { name: string }>binding;
}
244 changes: 244 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import {
CosmosDBFunctionOptions,
EventGridFunctionOptions,
EventHubFunctionOptions,
FunctionOptions,
HttpFunctionOptions,
HttpHandler,
HttpMethod,
HttpMethodFunctionOptions,
ServiceBusQueueFunctionOptions,
ServiceBusTopicFunctionOptions,
StorageBlobFunctionOptions,
StorageQueueFunctionOptions,
TimerFunctionOptions,
} from '@azure/functions';
import * as coreTypes from '@azure/functions-core';
import { CoreInvocationContext, FunctionCallback } from '@azure/functions-core';
import { returnBindingKey, version } from './constants';
import { InvocationModel } from './InvocationModel';
import * as output from './output';
import * as trigger from './trigger';
import { isTrigger } from './utils/isTrigger';

let coreApi: typeof coreTypes | undefined | null;
function tryGetCoreApiLazy(): typeof coreTypes | null {
if (coreApi === undefined) {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
coreApi = <typeof coreTypes>require('@azure/functions-core');
} catch {
coreApi = null;
}
}
return coreApi;
}

class ProgrammingModel implements coreTypes.ProgrammingModel {
name = '@azure/functions';
version = version;
getInvocationModel(coreCtx: CoreInvocationContext): InvocationModel {
return new InvocationModel(coreCtx);
}
}

let hasSetup = false;
function setup() {
const coreApi = tryGetCoreApiLazy();
if (!coreApi) {
console.warn(
'WARNING: Failed to detect the Azure Functions runtime. Switching "@azure/functions" package to test mode - not all features are supported.'
);
} else {
coreApi.setProgrammingModel(new ProgrammingModel());
}
hasSetup = true;
}

function convertToHttpOptions(
optionsOrHandler: HttpFunctionOptions | HttpHandler,
method: HttpMethod
): HttpFunctionOptions {
const options: HttpFunctionOptions =
typeof optionsOrHandler === 'function' ? { handler: optionsOrHandler } : optionsOrHandler;
options.methods = [method];
return options;
}

export function get(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
http(name, convertToHttpOptions(optionsOrHandler, 'GET'));
}

export function put(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
http(name, convertToHttpOptions(optionsOrHandler, 'PUT'));
}

export function post(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
http(name, convertToHttpOptions(optionsOrHandler, 'POST'));
}

export function patch(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
http(name, convertToHttpOptions(optionsOrHandler, 'PATCH'));
}

export function deleteRequest(name: string, optionsOrHandler: HttpMethodFunctionOptions | HttpHandler): void {
http(name, convertToHttpOptions(optionsOrHandler, 'DELETE'));
}

export function http(name: string, options: HttpFunctionOptions): void {
options.return ||= output.http({});
generic(name, {
trigger: trigger.http({
authLevel: options.authLevel,
methods: options.methods,
route: options.route,
}),
...options,
});
}

export function timer(name: string, options: TimerFunctionOptions): void {
generic(name, {
trigger: trigger.timer({
schedule: options.schedule,
runOnStartup: options.runOnStartup,
useMonitor: options.useMonitor,
}),
...options,
});
}

export function storageBlob(name: string, options: StorageBlobFunctionOptions): void {
generic(name, {
trigger: trigger.storageBlob({
connection: options.connection,
path: options.path,
}),
...options,
});
}

export function storageQueue(name: string, options: StorageQueueFunctionOptions): void {
generic(name, {
trigger: trigger.storageQueue({
connection: options.connection,
queueName: options.queueName,
}),
...options,
});
}

export function serviceBusQueue(name: string, options: ServiceBusQueueFunctionOptions): void {
generic(name, {
trigger: trigger.serviceBusQueue({
connection: options.connection,
queueName: options.queueName,
isSessionsEnabled: options.isSessionsEnabled,
}),
...options,
});
}

export function serviceBusTopic(name: string, options: ServiceBusTopicFunctionOptions): void {
generic(name, {
trigger: trigger.serviceBusTopic({
connection: options.connection,
topicName: options.topicName,
subscriptionName: options.subscriptionName,
isSessionsEnabled: options.isSessionsEnabled,
}),
...options,
});
}

export function eventHub(name: string, options: EventHubFunctionOptions): void {
generic(name, {
trigger: trigger.eventHub({
connection: options.connection,
eventHubName: options.eventHubName,
cardinality: options.cardinality,
consumerGroup: options.consumerGroup,
}),
...options,
});
}

export function eventGrid(name: string, options: EventGridFunctionOptions): void {
generic(name, {
trigger: trigger.eventGrid({}),
...options,
});
}

export function cosmosDB(name: string, options: CosmosDBFunctionOptions): void {
generic(name, {
trigger: trigger.cosmosDB({
collectionName: options.collectionName,
connectionStringSetting: options.connectionStringSetting,
createLeaseCollectionIfNotExists: options.createLeaseCollectionIfNotExists,
databaseName: options.databaseName,
id: options.id,
leaseCollectionName: options.leaseCollectionName,
leaseCollectionPrefix: options.leaseCollectionPrefix,
leaseCollectionThroughput: options.leaseCollectionThroughput,
leaseConnectionStringSetting: options.leaseConnectionStringSetting,
leaseDatabaseName: options.leaseDatabaseName,
partitionKey: options.partitionKey,
sqlQuery: options.sqlQuery,
}),
...options,
});
}

export function generic(name: string, options: FunctionOptions): void {
if (!hasSetup) {
setup();
}

const bindings: Record<string, coreTypes.RpcBindingInfo> = {};

const trigger = options.trigger;
bindings[trigger.name] = {
...trigger,
direction: 'in',
type: isTrigger(trigger.type) ? trigger.type : trigger.type + 'Trigger',
};

if (options.extraInputs) {
for (const input of options.extraInputs) {
bindings[input.name] = {
...input,
direction: 'in',
};
}
}

if (options.return) {
options.return.name = returnBindingKey;
bindings[options.return.name] = {
...options.return,
direction: 'out',
};
}

if (options.extraOutputs) {
for (const output of options.extraOutputs) {
bindings[output.name] = {
...output,
direction: 'out',
};
}
}

const coreApi = tryGetCoreApiLazy();
if (!coreApi) {
console.warn(
`WARNING: Skipping call to register function "${name}" because the "@azure/functions" package is in test mode.`
);
} else {
coreApi.registerFunction({ name, bindings }, <FunctionCallback>options.handler);
}
}
Loading