Skip to content
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

[Cosmos] Adds JSONPatch to item #16264

Merged
merged 20 commits into from Aug 31, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions sdk/cosmosdb/cosmos/CHANGELOG.md
@@ -1,5 +1,16 @@
# Release History

## 3.14.0 (2021-08-31)

### Features Added

- *PREVIEW* Adds `container.item(itemId).patch()`. `patch()` is an alternative to `replace()` for item updates. https://github.com/Azure/azure-sdk-for-js/pull/16264/files#diff-7caca690c469e2025576523c0377ac71815f001024fde7c48b20cd24adaa6977R561
- *PREVIEW* Adds support for Bulk operation PATCH.

### Bugs Fixed

- Fixes bug where Batch was passing the wrong header for batch requests with partition keys

## 3.13.1 (2021-08-23)

### Bugs Fixed
Expand Down
2 changes: 1 addition & 1 deletion sdk/cosmosdb/cosmos/package.json
@@ -1,6 +1,6 @@
{
"name": "@azure/cosmos",
"version": "3.13.1",
"version": "3.14.0",
"description": "Microsoft Azure Cosmos DB Service Node.js SDK for SQL API",
"sdk-type": "client",
"keywords": [
Expand Down
72 changes: 70 additions & 2 deletions sdk/cosmosdb/cosmos/review/cosmos.api.md
Expand Up @@ -32,6 +32,7 @@ export const BulkOperationType: {
readonly Read: "Read";
readonly Delete: "Delete";
readonly Replace: "Replace";
readonly Patch: "Patch";
};

// @public
Expand All @@ -40,6 +41,12 @@ export interface BulkOptions {
continueOnError?: boolean;
}

// @public (undocumented)
export type BulkPatchOperation = OperationBase & {
operationType: typeof BulkOperationType.Patch;
id: string;
};

// @public
export class ChangeFeedIterator<T> {
fetchNext(): Promise<ChangeFeedResponse<Array<T & Resource>>>;
Expand Down Expand Up @@ -131,6 +138,15 @@ export class ClientContext {
[containerUrl: string]: any;
};
// (undocumented)
patch<T>({ body, path, resourceType, resourceId, options, partitionKey }: {
body: any;
path: string;
resourceType: ResourceType;
resourceId: string;
options?: RequestOptions;
partitionKey?: PartitionKey;
}): Promise<Response<T & Resource>>;
// (undocumented)
queryFeed<T>({ path, resourceType, resourceId, resultFn, query, options, partitionKeyRangeId, partitionKey }: {
path: string;
resourceType: ResourceType;
Expand Down Expand Up @@ -698,6 +714,13 @@ export interface ErrorResponse extends Error {
substatus?: number;
}

// @public (undocumented)
export type ExistingKeyOperation = {
op: keyof typeof PatchOperationType;
value: any;
path: string;
};

// @public (undocumented)
export function extractPartitionKey(document: unknown, partitionKeyDefinition: PartitionKeyDefinition): PartitionKey[];

Expand Down Expand Up @@ -783,6 +806,8 @@ export enum HTTPMethod {
// (undocumented)
get = "GET",
// (undocumented)
patch = "PATCH",
// (undocumented)
post = "POST",
// (undocumented)
put = "PUT"
Expand Down Expand Up @@ -838,6 +863,7 @@ export class Item {
delete<T extends ItemDefinition = any>(options?: RequestOptions): Promise<ItemResponse<T>>;
// (undocumented)
readonly id: string;
patch<T extends ItemDefinition = any>(body: PatchRequestBody, options?: RequestOptions): Promise<ItemResponse<T>>;
read<T extends ItemDefinition = any>(options?: RequestOptions): Promise<ItemResponse<T>>;
replace(body: ItemDefinition, options?: RequestOptions): Promise<ItemResponse<ItemDefinition>>;
replace<T extends ItemDefinition>(body: T, options?: RequestOptions): Promise<ItemResponse<T>>;
Expand Down Expand Up @@ -970,7 +996,7 @@ export class Offers {
}

// @public (undocumented)
export type Operation = CreateOperation | UpsertOperation | ReadOperation | DeleteOperation | ReplaceOperation;
export type Operation = CreateOperation | UpsertOperation | ReadOperation | DeleteOperation | ReplaceOperation | BulkPatchOperation;

// @public (undocumented)
export interface OperationBase {
Expand All @@ -983,7 +1009,7 @@ export interface OperationBase {
}

// @public (undocumented)
export type OperationInput = CreateOperationInput | UpsertOperationInput | ReadOperationInput | DeleteOperationInput | ReplaceOperationInput;
export type OperationInput = CreateOperationInput | UpsertOperationInput | ReadOperationInput | DeleteOperationInput | ReplaceOperationInput | PatchOperationInput;

// @public (undocumented)
export interface OperationResponse {
Expand All @@ -1008,6 +1034,8 @@ export enum OperationType {
// (undocumented)
Execute = "execute",
// (undocumented)
Patch = "patch",
// (undocumented)
Query = "query",
// (undocumented)
Read = "read",
Expand Down Expand Up @@ -1071,6 +1099,40 @@ export interface PartitionKeyRangePropertiesNames {
MinInclusive: "minInclusive";
}

// @public (undocumented)
export type PatchOperation = ExistingKeyOperation | RemoveOperation;

// @public (undocumented)
export interface PatchOperationInput {
// (undocumented)
id: string;
// (undocumented)
ifMatch?: string;
// (undocumented)
ifNoneMatch?: string;
// (undocumented)
operationType: typeof BulkOperationType.Patch;
// (undocumented)
partitionKey?: string | number | null | Record<string, unknown> | undefined;
// (undocumented)
resourceBody: PatchRequestBody;
}

// @public (undocumented)
export const PatchOperationType: {
readonly add: "add";
readonly replace: "replace";
readonly remove: "remove";
readonly set: "set";
readonly incr: "incr";
};

// @public (undocumented)
export type PatchRequestBody = {
operations: PatchOperation[];
condition?: string;
} | PatchOperation[];

// @public
export class Permission {
constructor(user: User, id: string, clientContext: ClientContext);
Expand Down Expand Up @@ -1306,6 +1368,12 @@ export interface ReadOperationInput {
partitionKey?: string | number | boolean | null | Record<string, unknown> | undefined;
}

// @public (undocumented)
export type RemoveOperation = {
op: "remove";
path: string;
};

// @public (undocumented)
export type ReplaceOperation = OperationWithItem & {
operationType: typeof BulkOperationType.Replace;
Expand Down
53 changes: 51 additions & 2 deletions sdk/cosmosdb/cosmos/src/ClientContext.ts
Expand Up @@ -296,6 +296,55 @@ export class ClientContext {
}
}

public async patch<T>({
body,
path,
resourceType,
resourceId,
options = {},
partitionKey
}: {
body: any;
path: string;
resourceType: ResourceType;
resourceId: string;
options?: RequestOptions;
partitionKey?: PartitionKey;
}): Promise<Response<T & Resource>> {
try {
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.patch,
client: this,
operationType: OperationType.Patch,
path,
resourceType,
body,
resourceId,
options,
plugins: this.cosmosClientOptions.plugins,
partitionKey
};

request.headers = await this.buildHeaders(request);
this.applySessionToken(request);

// patch will use WriteEndpoint
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(
request.resourceType,
request.operationType
);
const response = await executePlugins(request, executeRequest, PluginOn.operation);
this.captureSessionToken(undefined, path, OperationType.Patch, response.headers);
return response;
} catch (err) {
this.captureSessionToken(err, path, OperationType.Upsert, (err as ErrorResponse).headers);
throw err;
}
}

public async create<T, U = T>({
body,
path,
Expand Down Expand Up @@ -607,12 +656,12 @@ export class ClientContext {
resourceId,
plugins: this.cosmosClientOptions.plugins,
options,
pipeline: this.pipeline
pipeline: this.pipeline,
partitionKey
};

request.headers = await this.buildHeaders(request);
request.headers[Constants.HttpHeaders.IsBatchRequest] = true;
request.headers[Constants.HttpHeaders.PartitionKey] = partitionKey;
request.headers[Constants.HttpHeaders.IsBatchAtomic] = true;

this.applySessionToken(request);
Expand Down
40 changes: 40 additions & 0 deletions sdk/cosmosdb/cosmos/src/client/Item/Item.ts
Expand Up @@ -12,6 +12,7 @@ import {
import { PartitionKey } from "../../documents";
import { extractPartitionKey, undefinedPartitionKey } from "../../extractPartitionKey";
import { RequestOptions, Response } from "../../request";
import { PatchRequestBody } from "../../utils/patch";
import { Container } from "../Container";
import { Resource } from "../Resource";
import { ItemDefinition } from "./ItemDefinition";
Expand Down Expand Up @@ -205,4 +206,43 @@ export class Item {
this
);
}

/**
* Perform a JSONPatch on the item.
*
* Any provided type, T, is not necessarily enforced by the SDK.
* You may get more or less properties and it's up to your logic to enforce it.
*
* @param options - Additional options for the request
*/
public async patch<T extends ItemDefinition = any>(
body: PatchRequestBody,
options: RequestOptions = {}
): Promise<ItemResponse<T>> {
if (this.partitionKey === undefined) {
const {
resource: partitionKeyDefinition
} = await this.container.readPartitionKeyDefinition();
this.partitionKey = extractPartitionKey(body, partitionKeyDefinition);
}

const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);

const response = await this.clientContext.patch<T>({
body,
path,
resourceType: ResourceType.item,
resourceId: id,
options,
partitionKey: this.partitionKey
});
return new ItemResponse(
response.result,
response.headers,
response.code,
response.substatus,
this
);
}
}
4 changes: 3 additions & 1 deletion sdk/cosmosdb/cosmos/src/common/constants.ts
Expand Up @@ -255,6 +255,7 @@ export enum ResourceType {
*/
export enum HTTPMethod {
get = "GET",
patch = "PATCH",
post = "POST",
put = "PUT",
delete = "DELETE"
Expand All @@ -271,7 +272,8 @@ export enum OperationType {
Read = "read",
Query = "query",
Execute = "execute",
Batch = "batch"
Batch = "batch",
Patch = "patch"
}

/**
Expand Down
11 changes: 10 additions & 1 deletion sdk/cosmosdb/cosmos/src/index.ts
Expand Up @@ -21,8 +21,17 @@ export {
UpsertOperationInput,
ReplaceOperationInput,
ReadOperationInput,
DeleteOperationInput
DeleteOperationInput,
PatchOperationInput,
BulkPatchOperation
} from "./utils/batch";
export {
PatchOperation,
PatchOperationType,
ExistingKeyOperation,
RemoveOperation,
PatchRequestBody
} from "./utils/patch";
export {
ConnectionMode,
ConsistencyLevel,
Expand Down