diff --git a/sdk/cosmosdb/cosmos/review/cosmos.api.md b/sdk/cosmosdb/cosmos/review/cosmos.api.md index 350267c6dee4..72fba177cdca 100644 --- a/sdk/cosmosdb/cosmos/review/cosmos.api.md +++ b/sdk/cosmosdb/cosmos/review/cosmos.api.md @@ -395,6 +395,7 @@ export const Constants: { IsBatchRequest: string; IsBatchAtomic: string; BatchContinueOnError: string; + DedicatedGatewayPerRequestCacheStaleness: string; ForceRefresh: string; }; WritableLocations: string; @@ -671,6 +672,11 @@ export enum DataType { String = "String" } +// @beta +export interface DedicatedGatewayRequestOptions { + maxIntegratedCacheStalenessInMs?: number; +} + // @public (undocumented) export const DEFAULT_PARTITION_KEY_PATH: "/_partitionKey"; @@ -730,8 +736,10 @@ export type ExistingKeyOperation = { // @public (undocumented) export function extractPartitionKey(document: unknown, partitionKeyDefinition: PartitionKeyDefinition): PartitionKey[]; +// Warning: (ae-incompatible-release-tags) The symbol "FeedOptions" is marked as @public, but its signature references "DedicatedGatewayRequestOptions" which is marked as @beta +// // @public -export interface FeedOptions extends SharedOptions { +export interface FeedOptions extends SharedOptions, DedicatedGatewayRequestOptions { accessCondition?: { type: string; condition: string; @@ -1460,8 +1468,10 @@ interface RequestInfo_2 { } export { RequestInfo_2 as RequestInfo } +// Warning: (ae-incompatible-release-tags) The symbol "RequestOptions" is marked as @public, but its signature references "DedicatedGatewayRequestOptions" which is marked as @beta +// // @public -export interface RequestOptions extends SharedOptions { +export interface RequestOptions extends SharedOptions, DedicatedGatewayRequestOptions { accessCondition?: { type: string; condition: string; diff --git a/sdk/cosmosdb/cosmos/samples/v3/typescript/src/ItemManagement.ts b/sdk/cosmosdb/cosmos/samples/v3/typescript/src/ItemManagement.ts index 8ff8e394429f..debceaa96b8f 100644 --- a/sdk/cosmosdb/cosmos/samples/v3/typescript/src/ItemManagement.ts +++ b/sdk/cosmosdb/cosmos/samples/v3/typescript/src/ItemManagement.ts @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. /** diff --git a/sdk/cosmosdb/cosmos/src/common/constants.ts b/sdk/cosmosdb/cosmos/src/common/constants.ts index 0254d4ba0d47..03652e408b80 100644 --- a/sdk/cosmosdb/cosmos/src/common/constants.ts +++ b/sdk/cosmosdb/cosmos/src/common/constants.ts @@ -158,6 +158,9 @@ export const Constants = { IsBatchAtomic: "x-ms-cosmos-batch-atomic", BatchContinueOnError: "x-ms-cosmos-batch-continue-on-error", + // Dedicated Gateway Headers + DedicatedGatewayPerRequestCacheStaleness: "x-ms-dedicatedgateway-max-age", + // Cache Refresh header ForceRefresh: "x-ms-force-refresh", }, diff --git a/sdk/cosmosdb/cosmos/src/request/DedicatedGatewayRequestOptions.ts b/sdk/cosmosdb/cosmos/src/request/DedicatedGatewayRequestOptions.ts new file mode 100644 index 000000000000..2dafd9e284cf --- /dev/null +++ b/sdk/cosmosdb/cosmos/src/request/DedicatedGatewayRequestOptions.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * @beta Dedicated Gateway Request Options *Private feature* + */ +export interface DedicatedGatewayRequestOptions { + /** + * Sets the staleness value associated with the request in the Azure CosmosDB service. For requests where the {@link + * com.azure.cosmos.ConsistencyLevel} is {@link com.azure.cosmos.ConsistencyLevel#EVENTUAL}, responses from the + * integrated cache are guaranteed to be no staler than value indicated by this maxIntegratedCacheStaleness. When the + * consistency level is not set, this property is ignored. + * + *

Default value is null

+ * + *

Cache Staleness is supported in milliseconds granularity. Anything smaller than milliseconds will be ignored.

+ */ + maxIntegratedCacheStalenessInMs?: number; +} diff --git a/sdk/cosmosdb/cosmos/src/request/FeedOptions.ts b/sdk/cosmosdb/cosmos/src/request/FeedOptions.ts index 5650bd4ebb5e..970de25e1076 100644 --- a/sdk/cosmosdb/cosmos/src/request/FeedOptions.ts +++ b/sdk/cosmosdb/cosmos/src/request/FeedOptions.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { DedicatedGatewayRequestOptions } from ".."; import { SharedOptions } from "./SharedOptions"; /** * The feed options and query methods. */ -export interface FeedOptions extends SharedOptions { +export interface FeedOptions extends SharedOptions, DedicatedGatewayRequestOptions { /** Opaque token for continuing the enumeration. Default: undefined * @deprecated Use continuationToken instead. */ diff --git a/sdk/cosmosdb/cosmos/src/request/RequestOptions.ts b/sdk/cosmosdb/cosmos/src/request/RequestOptions.ts index 413208500edf..765bf151c732 100644 --- a/sdk/cosmosdb/cosmos/src/request/RequestOptions.ts +++ b/sdk/cosmosdb/cosmos/src/request/RequestOptions.ts @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { SharedOptions } from "./SharedOptions"; +import { DedicatedGatewayRequestOptions, SharedOptions } from ".."; /** * Options that can be specified for a requested issued to the Azure Cosmos DB servers.= */ -export interface RequestOptions extends SharedOptions { +export interface RequestOptions extends SharedOptions, DedicatedGatewayRequestOptions { /** Conditions Associated with the request. */ accessCondition?: { /** Conditional HTTP method header type (IfMatch or IfNoneMatch). */ diff --git a/sdk/cosmosdb/cosmos/src/request/index.ts b/sdk/cosmosdb/cosmos/src/request/index.ts index dae314f01343..9033c21eed29 100644 --- a/sdk/cosmosdb/cosmos/src/request/index.ts +++ b/sdk/cosmosdb/cosmos/src/request/index.ts @@ -18,3 +18,4 @@ export { SharedOptions } from "./SharedOptions"; export { StatusCode, SubStatusCode } from "./StatusCodes"; export { FeedResponse } from "./FeedResponse"; export { RequestContext } from "./RequestContext"; +export { DedicatedGatewayRequestOptions } from "./DedicatedGatewayRequestOptions"; diff --git a/sdk/cosmosdb/cosmos/src/request/request.ts b/sdk/cosmosdb/cosmos/src/request/request.ts index 8b9b51b4c754..bb3d684478a6 100644 --- a/sdk/cosmosdb/cosmos/src/request/request.ts +++ b/sdk/cosmosdb/cosmos/src/request/request.ts @@ -127,6 +127,11 @@ export async function getHeaders({ headers[Constants.HttpHeaders.ConsistencyLevel] = options.consistencyLevel; } + if (options.maxIntegratedCacheStalenessInMs && resourceType === ResourceType.item) { + headers[Constants.HttpHeaders.DedicatedGatewayPerRequestCacheStaleness] = + options.maxIntegratedCacheStalenessInMs; + } + if (options.resourceTokenExpirySeconds) { headers[Constants.HttpHeaders.ResourceTokenExpiry] = options.resourceTokenExpirySeconds; } @@ -187,6 +192,7 @@ export async function getHeaders({ if (options.disableRUPerMinuteUsage) { headers[Constants.HttpHeaders.DisableRUPerMinuteUsage] = true; } + if ( clientOptions.key || clientOptions.resourceTokens || diff --git a/sdk/cosmosdb/cosmos/test/internal/session.spec.ts b/sdk/cosmosdb/cosmos/test/internal/session.spec.ts index 8dc20654e92b..a95a9d3269d9 100644 --- a/sdk/cosmosdb/cosmos/test/internal/session.spec.ts +++ b/sdk/cosmosdb/cosmos/test/internal/session.spec.ts @@ -70,6 +70,96 @@ describe("New session token", function () { }); }); +describe("Integrated Cache Staleness", async function (this: Suite) { + beforeEach(async function () { + await removeAllDatabases(); + }); + const dbId = addEntropy("maxIntegratedCacheTestDB"); + const containerId = addEntropy("maxIntegratedCacheTestContainer"); + const dedicatedGatewayMaxAge = 600000; + const client = new CosmosClient({ + endpoint, + key: masterKey, + consistencyLevel: ConsistencyLevel.Eventual, + plugins: [ + { + on: "request", + plugin: async (context, next) => { + it("Should check if the max integrated cache staleness header is set and the value is correct.", async function () { + if (context.headers["x-ms-consistency-level"]) { + if (context.resourceType === ResourceType.item) { + if (context.headers["x-ms-dedicatedgateway-max-age"]) { + assert.strictEqual( + context.headers["x-ms-dedicatedgateway-max-age"].valueOf(), + dedicatedGatewayMaxAge + ); + } else { + assert( + context.headers["x-ms-dedicatedgateway-max-age"], + "x-ms-dedicatedgateway-max-age is not set." + ); + assert.ifError(context.headers["x-ms-dedicatedgateway-max-age"]); + } + } else { + assert( + context.headers["x-ms-dedicatedgateway-max-age"], + "Attempt to use x-ms-dedicatedgateway-max-age on a non-item request." + ); + assert.ifError(context.headers["x-ms-dedicatedgateway-max-age"]); + } + } else { + assert( + context.headers["x-ms-consistency-level"], + "x-ms-consistency-level is not set." + ); + assert.ifError(context.headers["x-ms-consistency-level"]); + } + }); + const response = await next(context); + return response; + }, + }, + ], + }); + const itemRequestFeedOptions = { + maxIntegratedCacheStalenessInMs: dedicatedGatewayMaxAge, + }; + const { database } = await client.databases.createIfNotExists({ + id: dbId, + }); + const { container } = await database.containers.createIfNotExists({ + id: containerId, + }); + + // Should pass with maxIntegratedCacheStalenessInMs and consistency level set. + await container.items.create({ id: "1" }); + await container.item("1").read(itemRequestFeedOptions); + + // Should pass with maxIntegratedCacheStalenessInMs and consistency level set. + // read document. + await container.items.readAll(itemRequestFeedOptions).fetchAll(); + + // Should pass with maxIntegratedCacheStalenessInMs and consistency level set. + // query documents + const querySpec = { + query: "SELECT * FROM root r WHERE r.id=@id", + parameters: [ + { + name: "@id", + value: "1", + }, + ], + }; + await container.items.query(querySpec, itemRequestFeedOptions).fetchAll(); + + // Should fail: maxIntegratedCacheStalenessInMs should only be set at the item request level and query feed options + assert.doesNotThrow(async () => { + await container.read({ + maxIntegratedCacheStalenessInMs: dedicatedGatewayMaxAge, + }); + }, "maxIntegratedCacheStalenessInMs should only be set at the item request level and query feed options"); +}); + // For some reason this test does not pass against the emulator. Skipping it for now describe.skip("Session Token", function (this: Suite) { beforeEach(async function () { diff --git a/sdk/cosmosdb/cosmos/tsconfig.strict.json b/sdk/cosmosdb/cosmos/tsconfig.strict.json index c38cebfdbc29..770783cc0cf5 100644 --- a/sdk/cosmosdb/cosmos/tsconfig.strict.json +++ b/sdk/cosmosdb/cosmos/tsconfig.strict.json @@ -117,6 +117,7 @@ "src/client/Database/index.ts", "src/documents/index.ts", "src/request/index.ts", + "src/request/DedicatedGatewayRequestOptions.ts", "src/index.ts", "src/retry/index.ts", "src/queryExecutionContext/Aggregators/index.ts",