Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
logs
*.log
npm-debug.log*

sample/bin/*
sample/obj/*
# Runtime data
pids
*.pid
Expand Down
10 changes: 9 additions & 1 deletion src/trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,18 @@ export function eventGrid(options: EventGridTriggerOptions): EventGridTrigger {
}

export function cosmosDB(options: CosmosDBTriggerOptions): CosmosDBTrigger {
return addTriggerBindingName({
const binding = addTriggerBindingName({
...options,
type: 'cosmosDBTrigger',
});

// The Functions host (Cosmos DB extension) only recognizes 'AllVersionsAndDeletes' on the wire,
// so translate the developer-facing 'FullFidelity' value before registration.
if ('changeFeedMode' in binding && binding.changeFeedMode === 'FullFidelity') {
(binding as { changeFeedMode?: string }).changeFeedMode = 'AllVersionsAndDeletes';
}

return binding;
}

export function warmup(options: WarmupTriggerOptions): WarmupTrigger {
Expand Down
50 changes: 50 additions & 0 deletions test/app.cosmosDB.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import 'mocha';
import { RpcBindingInfo } from '@azure/functions-core';
import { expect } from 'chai';
import * as sinon from 'sinon';
import * as app from '../src/app';
import * as tryGetCoreApiLazyModule from '../src/utils/tryGetCoreApiLazy';
import { InvocationContext } from '../types';

describe('app.cosmosDB', () => {
const handler = (_documents: unknown[], _context: InvocationContext) => {};

afterEach(() => {
sinon.restore();
});

it('registers changeFeedMode on function metadata', () => {
const registerFunction = sinon.stub();
const setProgrammingModel = sinon.stub();

sinon.stub(tryGetCoreApiLazyModule, 'tryGetCoreApiLazy').returns({
registerFunction,
setProgrammingModel,
} as unknown as ReturnType<typeof tryGetCoreApiLazyModule.tryGetCoreApiLazy>);

app.cosmosDB('cosmosFn', {
handler,
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
changeFeedMode: 'FullFidelity',
});

sinon.assert.calledOnce(registerFunction);
const metadata = registerFunction.firstCall.args[0];

expect(metadata.name).to.equal('cosmosFn');
const cosmosBinding = Object.values(metadata.bindings as Record<string, RpcBindingInfo>).find(
(b) => b.type === 'cosmosDBTrigger'
);
expect(cosmosBinding).to.include({
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
changeFeedMode: 'AllVersionsAndDeletes',
});
});
});
42 changes: 42 additions & 0 deletions test/converters/toCoreFunctionMetadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,48 @@ describe('toCoreFunctionMetadata', () => {
retryOptions: undefined,
});
});

it('cosmosDB trigger maps FullFidelity changeFeedMode to AllVersionsAndDeletes', () => {
const result = toCoreFunctionMetadata('funcName', {
handler,
trigger: trigger.cosmosDB({
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
changeFeedMode: 'FullFidelity',
}),
return: output.http({}),
});

const cosmosBinding = Object.values(result.bindings).find((b) => b.type === 'cosmosDBTrigger');
expect(cosmosBinding).to.include({
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
changeFeedMode: 'AllVersionsAndDeletes',
});
});

it('cosmosDB trigger preserves changeFeedMode LatestVersion', () => {
const result = toCoreFunctionMetadata('funcName', {
handler,
trigger: trigger.cosmosDB({
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
changeFeedMode: 'LatestVersion',
}),
return: output.http({}),
});

const cosmosBinding = Object.values(result.bindings).find((b) => b.type === 'cosmosDBTrigger');
expect(cosmosBinding).to.include({
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
changeFeedMode: 'LatestVersion',
});
});
});

describe('toCoreFunctionMetadata sdk binding tests', () => {
Expand Down
58 changes: 57 additions & 1 deletion test/types/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,60 @@
// Licensed under the MIT License.

// This file will be compiled by multiple versions of TypeScript as decribed in ./test/TypesTests.ts to verify there are no errors
// Temporarily removing this code until we have updated templates for the new programming model
interface TodoItem {
id: string;
status: string;
}

type CosmosDBv4LatestVersionFunctionOptions<T = unknown> =
import('../../types').CosmosDBv4LatestVersionFunctionOptions<T>;
type CosmosDBv4FullFidelityFunctionOptions<T = unknown> =
import('../../types').CosmosDBv4FullFidelityFunctionOptions<T>;
type CosmosDBChangeFeedItem<T = unknown> = import('../../types').CosmosDBChangeFeedItem<T>;

const latestVersionOptions: CosmosDBv4LatestVersionFunctionOptions<TodoItem> = {
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
handler: (documents: TodoItem[]) => {
const item: TodoItem | undefined = documents[0];
void item?.id;
},
};

const fullFidelityOptions: CosmosDBv4FullFidelityFunctionOptions<TodoItem> = {
connection: 'CosmosConnection',
databaseName: 'dbName',
containerName: 'containerName',
changeFeedMode: 'FullFidelity',
handler: (documents: CosmosDBChangeFeedItem<TodoItem>[]) => {
const item: CosmosDBChangeFeedItem<TodoItem> | undefined = documents[0];
const currentId: string | undefined = item?.current?.id;
const previousId: string | undefined = item?.previous?.id;
const operationType: string | undefined = item?.metadata?.operationType;
void currentId;
void previousId;
void operationType;
},
};

type ExpectedAvadHandler = import('../../types').CosmosDBv4Handler<CosmosDBChangeFeedItem<TodoItem>>;
type ValidAvadHandler = (
documents: CosmosDBChangeFeedItem<TodoItem>[],
context: import('../../types').InvocationContext
) => import('../../types').FunctionResult;
type InvalidAvadHandler = (
documents: TodoItem[],
context: import('../../types').InvocationContext
) => import('../../types').FunctionResult;

type IsValidHandlerAssignableToAvad = ValidAvadHandler extends ExpectedAvadHandler ? true : false;
type IsInvalidHandlerAssignableToAvad = InvalidAvadHandler extends ExpectedAvadHandler ? true : false;

const validHandlerAssignableToAvad: IsValidHandlerAssignableToAvad = true;
const invalidHandlerAssignableToAvad: IsInvalidHandlerAssignableToAvad = false;

void latestVersionOptions;
void fullFidelityOptions;
void validHandlerAssignableToAvad;
void invalidHandlerAssignableToAvad;
4 changes: 3 additions & 1 deletion types/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ export function eventHub<T = unknown>(name: string, options: EventHubFunctionOpt
export function eventGrid<T = EventGridEvent>(name: string, options: EventGridFunctionOptions<T>): void;

/**
* Registers a Cosmos DB function in your app that will be triggered whenever inserts and updates occur (not deletions)
* Registers a Cosmos DB function in your app that will be triggered by container changes.
* By default, this trigger processes latest document versions. To include delete events and
* intermediate mutations, set `changeFeedMode` to `FullFidelity`.
* @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
*/
Expand Down
7 changes: 7 additions & 0 deletions types/cosmosDB.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
CosmosDBv3TriggerOptions,
} from './cosmosDB.v3';
import {
CosmosDBv4ChangeFeedItem,
CosmosDBv4ChangeFeedMetadata,
CosmosDBv4ChangeFeedMode,
CosmosDBv4FunctionOptions,
CosmosDBv4Handler,
CosmosDBv4Input,
Expand All @@ -32,5 +35,9 @@ export type CosmosDBInput = CosmosDBv3Input | CosmosDBv4Input;
export type CosmosDBTriggerOptions = CosmosDBv3TriggerOptions | CosmosDBv4TriggerOptions;
export type CosmosDBTrigger = CosmosDBv3Trigger | CosmosDBv4Trigger;

export type CosmosDBChangeFeedMode = CosmosDBv4ChangeFeedMode;
export type CosmosDBChangeFeedMetadata = CosmosDBv4ChangeFeedMetadata;
export type CosmosDBChangeFeedItem<T = unknown> = CosmosDBv4ChangeFeedItem<T>;

export type CosmosDBOutputOptions = CosmosDBv3OutputOptions | CosmosDBv4OutputOptions;
export type CosmosDBOutput = CosmosDBv3Output | CosmosDBv4Output;
64 changes: 63 additions & 1 deletion types/cosmosDB.v4.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,71 @@ import { InvocationContext } from './InvocationContext';

export type CosmosDBv4Handler<T = unknown> = (documents: T[], context: InvocationContext) => FunctionResult;

export interface CosmosDBv4FunctionOptions<T = unknown> extends CosmosDBv4TriggerOptions, Partial<FunctionOptions> {
/**
* Metadata included with each change feed item when using `FullFidelity`.
* Property names follow the wire format emitted by Cosmos DB.
*/
export interface CosmosDBv4ChangeFeedMetadata {
crts: number;
lsn: number;
operationType: string;
previousImageLSN?: number;
timeToLiveExpired?: boolean;
id?: string;
partitionKey?: Record<string, unknown>;
}

/**
* Full-fidelity change feed item payload emitted when `changeFeedMode` is `FullFidelity`.
* Property names follow the wire format emitted by Cosmos DB.
*/
export interface CosmosDBv4ChangeFeedItem<T = unknown> {
current: T | null;
previous?: T;
metadata?: CosmosDBv4ChangeFeedMetadata;
}

/**
* Change feed mode for Cosmos DB trigger bindings.
*/
export type CosmosDBv4ChangeFeedMode = 'LatestVersion' | 'FullFidelity';

export interface CosmosDBv4LatestVersionFunctionOptions<T = unknown>
extends Omit<CosmosDBv4TriggerOptions, 'changeFeedMode'>,
Partial<FunctionOptions> {
handler: CosmosDBv4Handler<T>;

trigger?: CosmosDBv4Trigger;

changeFeedMode?: 'LatestVersion';

/**
* 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 CosmosDBv4FullFidelityFunctionOptions<T = unknown>
extends Omit<CosmosDBv4TriggerOptions, 'changeFeedMode'>,
Partial<FunctionOptions> {
handler: CosmosDBv4Handler<CosmosDBv4ChangeFeedItem<T>>;

trigger?: CosmosDBv4Trigger;

changeFeedMode: 'FullFidelity';

/**
* 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 type CosmosDBv4FunctionOptions<T = unknown> =
| CosmosDBv4LatestVersionFunctionOptions<T>
| CosmosDBv4FullFidelityFunctionOptions<T>;

export interface CosmosDBv4InputOptions {
/**
* An app setting (or environment variable) with the Cosmos DB connection string
Expand Down Expand Up @@ -144,16 +197,25 @@ export interface CosmosDBv4TriggerOptions {
* This option tells the Trigger to read changes from the beginning of the container's change history instead of starting at the current time.
* Reading from the beginning only works the first time the trigger starts, as in subsequent runs, the checkpoints are already stored.
* Setting this option to true when there are leases already created has no effect.
* Not supported when `changeFeedMode` is `FullFidelity`.
*/
startFromBeginning?: boolean;

/**
* Gets or sets the date and time from which to initialize the change feed read operation.
* The recommended format is ISO 8601 with the UTC designator, such as 2021-02-16T14:19:29Z.
* This is only used to set the initial trigger state. After the trigger has a lease state, changing this value has no effect.
* Not supported when `changeFeedMode` is `FullFidelity`.
*/
startFromTime?: string;

/**
* Gets or sets the change feed mode used to process document changes.
* `LatestVersion` processes the latest document version.
* `FullFidelity` includes intermediate mutations and delete events.
*/
changeFeedMode?: CosmosDBv4ChangeFeedMode;

/**
* Defines preferred locations (regions) for geo-replicated database accounts in the Azure Cosmos DB service.
* Values should be comma-separated. For example, East US,South Central US,North Europe
Expand Down
Loading