Refactor to Unified RequestContext (#272)

This commit is contained in:
Steve Faulkner 2019-03-27 22:51:45 -04:00 коммит произвёл GitHub
Родитель c235b2a312
Коммит 9e8ea2490d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
40 изменённых файлов: 542 добавлений и 846 удалений

3
.vscode/settings.json поставляемый
Просмотреть файл

@ -1,4 +1,5 @@
{
"mocha.files.glob":"test/legacy/**/*.js",
"editor.formatOnSave": true
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
}

127
package-lock.json сгенерированный
Просмотреть файл

@ -132,15 +132,50 @@
"integrity": "sha512-L/srhENhBtbZLUD9FfJ2ZQdnYv3A3MT3UI2EMbC06fHUzIxLjjbkomD6o42UrbsRMwlS9p1BtxExeaCdX73q2Q==",
"dev": true
},
"@sinonjs/formatio": {
"version": "2.0.0",
"resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz",
"integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==",
"@sinonjs/commons": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz",
"integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==",
"dev": true,
"requires": {
"samsam": "1.3.0"
"type-detect": "4.0.8"
}
},
"@sinonjs/formatio": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz",
"integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1",
"@sinonjs/samsam": "^3.1.0"
}
},
"@sinonjs/samsam": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.0.tgz",
"integrity": "sha512-beHeJM/RRAaLLsMJhsCvHK31rIqZuobfPLa/80yGH5hnD8PV1hyh9xJBJNFfNmO7yWqm+zomijHsXpI6iTQJfQ==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1.0.2",
"array-from": "^2.1.1",
"lodash": "^4.17.11"
},
"dependencies": {
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
}
}
},
"@sinonjs/text-encoding": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
"dev": true
},
"@types/argparse": {
"version": "1.0.33",
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.33.tgz",
@ -253,9 +288,9 @@
}
},
"@types/sinon": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.3.tgz",
"integrity": "sha512-Tt7w/ylBS/OEAlSCwzB0Db1KbxnkycP/1UkQpbvKFYoUuRn4uYsC3xh5TRPrOjTy0i8TIkSz1JdNL4GPVdf3KQ==",
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.10.tgz",
"integrity": "sha512-4w7SvsiUOtd4mUfund9QROPSJ5At/GQskDpqd87pJIRI6ULWSJqHI3GIZE337wQuN3aznroJGr94+o8fwvL37Q==",
"dev": true
},
"@types/tunnel": {
@ -373,6 +408,12 @@
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
"dev": true
},
"array-from": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz",
"integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=",
"dev": true
},
"array-slice": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
@ -2943,9 +2984,9 @@
}
},
"just-extend": {
"version": "1.1.27",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz",
"integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz",
"integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==",
"dev": true
},
"karma": {
@ -3200,9 +3241,9 @@
}
},
"lolex": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.1.tgz",
"integrity": "sha512-Oo2Si3RMKV3+lV5MsSWplDQFoTClz/24S0MMHYcgGWWmFXr6TMlqcqk/l1GtH+d5wLBwNRiqGnwDRMirtFalJw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz",
"integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==",
"dev": true
},
"loud-rejection": {
@ -3589,16 +3630,24 @@
"dev": true
},
"nise": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/nise/-/nise-1.4.2.tgz",
"integrity": "sha512-BxH/DxoQYYdhKgVAfqVy4pzXRZELHOIewzoesxpjYvpU+7YOalQhGNPf7wAx8pLrTNPrHRDlLOkAl8UI0ZpXjw==",
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz",
"integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==",
"dev": true,
"requires": {
"@sinonjs/formatio": "^2.0.0",
"just-extend": "^1.1.27",
"@sinonjs/formatio": "^3.1.0",
"@sinonjs/text-encoding": "^0.7.1",
"just-extend": "^4.0.2",
"lolex": "^2.3.2",
"path-to-regexp": "^1.7.0",
"text-encoding": "^0.6.4"
"path-to-regexp": "^1.7.0"
},
"dependencies": {
"lolex": {
"version": "2.7.5",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz",
"integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==",
"dev": true
}
}
},
"node-fetch": {
@ -4354,12 +4403,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"samsam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz",
"integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==",
"dev": true
},
"semaphore": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.0.5.tgz",
@ -4448,24 +4491,24 @@
"dev": true
},
"sinon": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-5.1.1.tgz",
"integrity": "sha512-h/3uHscbt5pQNxkf7Y/Lb9/OM44YNCicHakcq73ncbrIS8lXg+ZGOZbtuU+/km4YnyiCYfQQEwANaReJz7KDfw==",
"version": "7.2.7",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.7.tgz",
"integrity": "sha512-rlrre9F80pIQr3M36gOdoCEWzFAMDgHYD8+tocqOw+Zw9OZ8F84a80Ds69eZfcjnzDqqG88ulFld0oin/6rG/g==",
"dev": true,
"requires": {
"@sinonjs/formatio": "^2.0.0",
"@sinonjs/commons": "^1.3.1",
"@sinonjs/formatio": "^3.2.1",
"@sinonjs/samsam": "^3.2.0",
"diff": "^3.5.0",
"lodash.get": "^4.4.2",
"lolex": "^2.4.2",
"nise": "^1.3.3",
"supports-color": "^5.4.0",
"type-detect": "^4.0.8"
"lolex": "^3.1.0",
"nise": "^1.4.10",
"supports-color": "^5.5.0"
},
"dependencies": {
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
@ -4801,12 +4844,6 @@
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
},
"text-encoding": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz",
"integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=",
"dev": true
},
"thunkify": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz",

Просмотреть файл

@ -41,7 +41,7 @@
"@types/node-fetch": "2.1.7",
"@types/priorityqueuejs": "^1.0.1",
"@types/semaphore": "^1.1.0",
"@types/sinon": "^4.3.3",
"@types/sinon": "7.0.10",
"@types/tunnel": "^0.0.0",
"@types/underscore": "^1.8.8",
"cross-env": "5.2.0",
@ -66,7 +66,7 @@
"rollup-plugin-json": "3.1.0",
"rollup-plugin-local-resolve": "^1.0.7",
"rollup-plugin-multi-entry": "2.0.2",
"sinon": "^5.1.1",
"sinon": "7.2.7",
"source-map-support": "0.5.11",
"ts-node": "^8.0.2",
"tslint": "5.11.0",

Просмотреть файл

@ -1,18 +1,19 @@
import { PartitionKeyRange } from "./client/Container/PartitionKeyRange";
import { Resource } from "./client/Resource";
import { Constants, HTTPMethod, OperationType, ResourceType } from "./common/constants";
import { getIdFromLink, getPathFromLink, parseLink, setIsUpsertHeader } from "./common/helper";
import { getIdFromLink, getPathFromLink, parseLink } from "./common/helper";
import { StatusCodes, SubStatusCodes } from "./common/statusCodes";
import { CosmosClientOptions } from "./CosmosClientOptions";
import { ConnectionPolicy, ConsistencyLevel, DatabaseAccount, QueryCompatibilityMode } from "./documents";
import { Agent, CosmosClientOptions } from "./CosmosClientOptions";
import { ConnectionPolicy, ConsistencyLevel, DatabaseAccount } from "./documents";
import { GlobalEndpointManager } from "./globalEndpointManager";
import { FetchFunctionCallback, SqlQuerySpec } from "./queryExecutionContext";
import { CosmosHeaders } from "./queryExecutionContext/CosmosHeaders";
import { QueryIterator } from "./queryIterator";
import { FeedOptions, RequestHandler, RequestOptions, Response } from "./request";
import { FeedOptions, RequestOptions, Response } from "./request";
import { ErrorResponse } from "./request";
import { getHeaders } from "./request/request";
import { RequestContext } from "./request/RequestContext";
import { request as executeRequest } from "./request/RequestHandler";
import { SessionContainer } from "./session/sessionContainer";
import { SessionContext } from "./session/SessionContext";
@ -23,7 +24,6 @@ import { SessionContext } from "./session/SessionContext";
export class ClientContext {
private readonly sessionContainer: SessionContainer;
private connectionPolicy: ConnectionPolicy;
private requestHandler: RequestHandler;
public partitionKeyDefinitionCache: { [containerUrl: string]: any }; // TODO: ParitionKeyDefinitionCache
public constructor(
@ -32,45 +32,35 @@ export class ClientContext {
) {
this.connectionPolicy = cosmosClientOptions.connectionPolicy;
this.sessionContainer = new SessionContainer();
this.requestHandler = new RequestHandler(
globalEndpointManager,
this.connectionPolicy,
this.cosmosClientOptions.agent
);
this.partitionKeyDefinitionCache = {};
}
/** @ignore */
public async read<T>(
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions = {}
): Promise<Response<T & Resource>> {
try {
const requestHeaders = await getHeaders(
this.cosmosClientOptions.auth,
{ ...initialHeaders, ...this.cosmosClientOptions.defaultHeaders, ...(options && options.initialHeaders) },
HTTPMethod.get,
path,
id,
type,
options,
undefined,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
this.applySessionToken(path, requestHeaders);
const request: any = {
// TODO: any
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.get,
path,
operationType: OperationType.Read,
client: this,
endpointOverride: null
resourceId,
options,
resourceType
};
request.headers = await this.buildHeaders(request);
this.applySessionToken(request);
// read will use ReadEndpoint since it uses GET operation
const endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await this.requestHandler.get(endpoint, request, requestHeaders);
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await executeRequest<T & Resource>(request);
this.captureSessionToken(undefined, path, OperationType.Read, response.headers);
return response;
} catch (err) {
@ -81,8 +71,8 @@ export class ClientContext {
public async queryFeed<T>(
path: string,
type: ResourceType,
id: string,
resourceType: ResourceType,
resourceId: string,
resultFn: (result: { [key: string]: any }) => any[], // TODO: any
query: SqlQuerySpec | string,
options: FeedOptions,
@ -91,67 +81,37 @@ export class ClientContext {
// Query operations will use ReadEndpoint even though it uses
// GET(for queryFeed) and POST(for regular query operations)
const request: any = {
// TODO: any request
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.get,
path,
operationType: OperationType.Query,
client: this,
endpointOverride: null
partitionKeyRangeId,
resourceId,
resourceType,
options,
body: query
};
const endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const initialHeaders = { ...this.cosmosClientOptions.defaultHeaders, ...(options && options.initialHeaders) };
if (query === undefined) {
const reqHeaders = await getHeaders(
this.cosmosClientOptions.auth,
initialHeaders,
HTTPMethod.get,
path,
id,
type,
options,
partitionKeyRangeId,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
this.applySessionToken(path, reqHeaders);
const response = await this.requestHandler.get(endpoint, request, reqHeaders);
this.captureSessionToken(undefined, path, OperationType.Query, response.headers);
return this.processQueryFeedResponse(response, !!query, resultFn);
} else {
initialHeaders[Constants.HttpHeaders.IsQuery] = "true";
switch (this.cosmosClientOptions.queryCompatibilityMode) {
case QueryCompatibilityMode.SqlQuery:
initialHeaders[Constants.HttpHeaders.ContentType] = Constants.MediaTypes.SQL;
break;
case QueryCompatibilityMode.Query:
case QueryCompatibilityMode.Default:
default:
if (typeof query === "string") {
query = { query }; // Converts query text to query object.
}
initialHeaders[Constants.HttpHeaders.ContentType] = Constants.MediaTypes.QueryJson;
break;
}
const reqHeaders = await getHeaders(
this.cosmosClientOptions.auth,
initialHeaders,
HTTPMethod.post,
path,
id,
type,
options,
partitionKeyRangeId,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
this.applySessionToken(path, reqHeaders);
const response = await this.requestHandler.post(endpoint, request, query, reqHeaders);
this.captureSessionToken(undefined, path, OperationType.Query, response.headers);
return this.processQueryFeedResponse(response, !!query, resultFn);
if (query !== undefined) {
request.method = HTTPMethod.post;
}
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
request.headers = await this.buildHeaders(request);
if (query !== undefined) {
request.headers[Constants.HttpHeaders.IsQuery] = "true";
request.headers[Constants.HttpHeaders.ContentType] = Constants.MediaTypes.QueryJson;
if (typeof query === "string") {
request.body = { query }; // Converts query text to query object.
}
}
this.applySessionToken(request);
const response = await executeRequest(request);
this.captureSessionToken(undefined, path, OperationType.Query, response.headers);
return this.processQueryFeedResponse(response, !!query, resultFn);
}
public queryPartitionKeyRanges(collectionLink: string, query?: string | SqlQuerySpec, options?: FeedOptions) {
@ -165,35 +125,29 @@ export class ClientContext {
public async delete<T>(
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions = {}
): Promise<Response<T & Resource>> {
try {
const reqHeaders = await getHeaders(
this.cosmosClientOptions.auth,
{ ...initialHeaders, ...this.cosmosClientOptions.defaultHeaders, ...(options && options.initialHeaders) },
HTTPMethod.delete,
path,
id,
type,
options,
undefined,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.delete,
client: this,
operationType: OperationType.Delete,
path,
resourceType: type
resourceType,
options,
resourceId
};
this.applySessionToken(path, reqHeaders);
request.headers = await this.buildHeaders(request);
this.applySessionToken(request);
// deleteResource will use WriteEndpoint since it uses DELETE operation
const endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await this.requestHandler.delete(endpoint, request, reqHeaders);
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await executeRequest<T & Resource>(request);
if (parseLink(path).type !== "colls") {
this.captureSessionToken(undefined, path, OperationType.Delete, response.headers);
} else {
@ -210,54 +164,47 @@ export class ClientContext {
public async create<T>(
body: T,
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions
): Promise<Response<T & Resource>>;
// But a few cases, like permissions, there is additional junk added to the response that isn't in system resource props
public async create<T, U>(
body: T,
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions
): Promise<Response<T & U & Resource>>;
public async create<T, U>(
body: T,
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions = {}
): Promise<Response<T & U & Resource>> {
try {
const requestHeaders = await getHeaders(
this.cosmosClientOptions.auth,
{ ...initialHeaders, ...this.cosmosClientOptions.defaultHeaders, ...(options && options.initialHeaders) },
HTTPMethod.post,
path,
id,
type,
options,
undefined,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.post,
client: this,
operationType: OperationType.Create,
path,
resourceType: type
resourceType,
resourceId,
body,
options
};
request.headers = await this.buildHeaders(request);
// create will use WriteEndpoint since it uses POST operation
this.applySessionToken(path, requestHeaders);
this.applySessionToken(request);
const endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await this.requestHandler.post(endpoint, request, body, requestHeaders);
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await executeRequest<T & U & Resource>(request);
this.captureSessionToken(undefined, path, OperationType.Create, response.headers);
return response;
} catch (err) {
@ -279,14 +226,16 @@ export class ClientContext {
}
}
private applySessionToken(path: string, reqHeaders: CosmosHeaders) {
const request = this.getSessionParams(path);
private applySessionToken(requestContext: RequestContext) {
const request = this.getSessionParams(requestContext.path);
if (reqHeaders && reqHeaders[Constants.HttpHeaders.SessionToken]) {
if (requestContext.headers && requestContext.headers[Constants.HttpHeaders.SessionToken]) {
return;
}
const sessionConsistency: ConsistencyLevel = reqHeaders[Constants.HttpHeaders.ConsistencyLevel] as ConsistencyLevel;
const sessionConsistency: ConsistencyLevel = requestContext.headers[
Constants.HttpHeaders.ConsistencyLevel
] as ConsistencyLevel;
if (!sessionConsistency) {
return;
}
@ -298,44 +247,39 @@ export class ClientContext {
if (request.resourceAddress) {
const sessionToken = this.sessionContainer.get(request);
if (sessionToken) {
reqHeaders[Constants.HttpHeaders.SessionToken] = sessionToken;
requestContext.headers[Constants.HttpHeaders.SessionToken] = sessionToken;
}
}
}
public async replace<T>(
resource: any,
body: any,
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions = {}
): Promise<Response<T & Resource>> {
try {
const reqHeaders = await getHeaders(
this.cosmosClientOptions.auth,
{ ...initialHeaders, ...this.cosmosClientOptions.defaultHeaders, ...(options && options.initialHeaders) },
HTTPMethod.put,
path,
id,
type,
options,
undefined,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.put,
client: this,
operationType: OperationType.Replace,
path,
resourceType: type
resourceType,
body,
resourceId,
options
};
this.applySessionToken(path, reqHeaders);
request.headers = await this.buildHeaders(request);
this.applySessionToken(request);
// replace will use WriteEndpoint since it uses PUT operation
const endpoint = await this.globalEndpointManager.resolveServiceEndpoint(reqHeaders);
const response = await this.requestHandler.put(endpoint, request, resource, reqHeaders);
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await executeRequest<T & Resource>(request);
this.captureSessionToken(undefined, path, OperationType.Replace, response.headers);
return response;
} catch (err) {
@ -347,53 +291,46 @@ export class ClientContext {
public async upsert<T>(
body: T,
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions
): Promise<Response<T & Resource>>;
public async upsert<T, U>(
body: T,
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions
): Promise<Response<T & U & Resource>>;
public async upsert<T>(
body: T,
path: string,
type: ResourceType,
id: string,
initialHeaders: CosmosHeaders,
options?: RequestOptions
resourceType: ResourceType,
resourceId: string,
options: RequestOptions = {}
): Promise<Response<T & Resource>> {
try {
const requestHeaders = await getHeaders(
this.cosmosClientOptions.auth,
{ ...initialHeaders, ...this.cosmosClientOptions.defaultHeaders, ...(options && options.initialHeaders) },
HTTPMethod.post,
path,
id,
type,
options,
undefined,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.post,
client: this,
operationType: OperationType.Upsert,
path,
resourceType: type
resourceType,
body,
resourceId,
options
};
setIsUpsertHeader(requestHeaders);
this.applySessionToken(path, requestHeaders);
request.headers = await this.buildHeaders(request);
request.headers[Constants.HttpHeaders.IsUpsert] = true;
this.applySessionToken(request);
// upsert will use WriteEndpoint since it uses POST operation
const endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await this.requestHandler.post(endpoint, request, body, requestHeaders);
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
const response = await executeRequest<T & Resource>(request);
this.captureSessionToken(undefined, path, OperationType.Upsert, response.headers);
return response;
} catch (err) {
@ -405,10 +342,8 @@ export class ClientContext {
public async execute<T>(
sprocLink: string,
params?: any[], // TODO: any
options?: RequestOptions
options: RequestOptions = {}
): Promise<Response<T>> {
const initialHeaders = { ...this.cosmosClientOptions.defaultHeaders, ...(options && options.initialHeaders) };
// Accept a single parameter or an array of parameters.
// Didn't add type annotation for this because we should legacy this behavior
if (params !== null && params !== undefined && !Array.isArray(params)) {
@ -417,28 +352,24 @@ export class ClientContext {
const path = getPathFromLink(sprocLink);
const id = getIdFromLink(sprocLink);
const headers = await getHeaders(
this.cosmosClientOptions.auth,
initialHeaders,
HTTPMethod.post,
path,
id,
ResourceType.sproc,
options,
undefined,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
const request: RequestContext = {
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.post,
client: this,
operationType: OperationType.Execute,
path,
resourceType: ResourceType.sproc
resourceType: ResourceType.sproc,
options,
resourceId: id,
body: params
};
request.headers = await this.buildHeaders(request);
// executeStoredProcedure will use WriteEndpoint since it uses POST operation
const endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
return this.requestHandler.post(endpoint, request, params, headers);
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
return executeRequest<T>(request);
}
/**
@ -447,28 +378,23 @@ export class ClientContext {
* If not present, current client's url will be used.
*/
public async getDatabaseAccount(options: RequestOptions = {}): Promise<Response<DatabaseAccount>> {
const urlConnection = options.urlConnection || this.cosmosClientOptions.endpoint;
const requestHeaders = await getHeaders(
this.cosmosClientOptions.auth,
this.cosmosClientOptions.defaultHeaders,
HTTPMethod.get,
"",
"",
ResourceType.none,
{},
undefined,
this.cosmosClientOptions.connectionPolicy.useMultipleWriteLocations
);
const endpoint = options.urlConnection || this.cosmosClientOptions.endpoint;
const request: RequestContext = {
endpoint,
globalEndpointManager: this.globalEndpointManager,
requestAgent: this.cosmosClientOptions.agent,
connectionPolicy: this.connectionPolicy,
method: HTTPMethod.get,
client: this,
operationType: OperationType.Read,
path: "",
resourceType: ResourceType.none
resourceType: ResourceType.none,
options
};
const { result, headers } = await this.requestHandler.get(urlConnection, request, requestHeaders);
request.headers = await this.buildHeaders(request);
// await options.beforeOperation({ endpoint, request, headers: requestHeaders });
const { result, headers } = await executeRequest(request);
const databaseAccount = new DatabaseAccount(result, headers);
@ -489,7 +415,7 @@ export class ClientContext {
operationType: OperationType,
resHeaders: CosmosHeaders
) {
const request = this.getSessionParams(path); // TODO: any request
const request = this.getSessionParams(path);
request.operationType = operationType;
if (
!err ||
@ -539,4 +465,18 @@ export class ClientContext {
return false;
}
private buildHeaders(requestContext: RequestContext) {
return getHeaders({
authOptions: this.cosmosClientOptions.auth,
defaultHeaders: { ...this.cosmosClientOptions.defaultHeaders, ...requestContext.options.initialHeaders },
verb: requestContext.method,
path: requestContext.path,
resourceId: requestContext.resourceId,
resourceType: requestContext.resourceType,
options: requestContext.options,
partitionKeyRangeId: requestContext.partitionKeyRangeId,
useMultipleWriteLocations: this.connectionPolicy.useMultipleWriteLocations
});
}
}

Просмотреть файл

@ -1,9 +1,9 @@
import { AuthOptions } from "./auth";
import { ConnectionPolicy, ConsistencyLevel, QueryCompatibilityMode } from "./documents";
import { ConnectionPolicy, ConsistencyLevel } from "./documents";
import { CosmosHeaders } from "./queryExecutionContext/CosmosHeaders";
// We expose our own Agent interface to avoid taking a dependency on and leaking node types. This interface should mirror the node Agent interface
interface Agent {
export interface Agent {
maxFreeSockets: number;
maxSockets: number;
sockets: any;
@ -31,5 +31,4 @@ export interface CosmosClientOptions {
* Use an agent such as https://github.com/TooTallNate/node-proxy-agent if you need to connect to Cosmos via a proxy
*/
agent?: Agent;
queryCompatibilityMode?: QueryCompatibilityMode;
}

Просмотреть файл

@ -98,7 +98,9 @@ export class LocationCache {
);
}
public resolveServiceEndpoint(request: RequestContext): string {
public resolveServiceEndpoint(
request: Pick<RequestContext, "operationType" | "resourceType" | "retryCount" | "locationRouting">
): string {
request.locationRouting = request.locationRouting || new LocationRouting();
let locationIndex = request.locationRouting.locationIndexToRoute || 0;

Просмотреть файл

@ -36,7 +36,7 @@ export class Conflict {
const path = getPathFromLink(this.url, ResourceType.conflicts);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<ConflictDefinition>(path, ResourceType.user, id, undefined, options);
const response = await this.clientContext.read<ConflictDefinition>(path, ResourceType.user, id, options);
return new ConflictResponse(response.result, response.headers, response.statusCode, this);
}
@ -48,13 +48,7 @@ export class Conflict {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete<ConflictDefinition>(
path,
ResourceType.conflicts,
id,
undefined,
options
);
const response = await this.clientContext.delete<ConflictDefinition>(path, ResourceType.conflicts, id, options);
return new ConflictResponse(response.result, response.headers, response.statusCode, this);
}
}

Просмотреть файл

@ -180,13 +180,7 @@ export class Container {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<ContainerDefinition>(
path,
ResourceType.container,
id,
undefined,
options
);
const response = await this.clientContext.read<ContainerDefinition>(path, ResourceType.container, id, options);
this.clientContext.partitionKeyDefinitionCache[this.url] = response.result.partitionKey;
return new ContainerResponse(response.result, response.headers, response.statusCode, this);
}
@ -206,7 +200,6 @@ export class Container {
path,
ResourceType.container,
id,
undefined,
options
);
return new ContainerResponse(response.result, response.headers, response.statusCode, this);
@ -217,13 +210,7 @@ export class Container {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete<ContainerDefinition>(
path,
ResourceType.container,
id,
undefined,
options
);
const response = await this.clientContext.delete<ContainerDefinition>(path, ResourceType.container, id, options);
return new ContainerResponse(response.result, response.headers, response.statusCode, this);
}

Просмотреть файл

@ -90,28 +90,22 @@ export class Containers {
* @param body Represents the body of the container.
* @param options Use to set options like response page size, continuation tokens, etc.
*/
public async create(body: ContainerRequest, options?: RequestOptions): Promise<ContainerResponse> {
public async create(body: ContainerRequest, options: RequestOptions = {}): Promise<ContainerResponse> {
const err = {};
if (!isResourceValid(body, err)) {
throw err;
}
const path = getPathFromLink(this.database.url, ResourceType.container);
const id = getIdFromLink(this.database.url);
let initialHeaders: CosmosHeaders;
if (body.throughput) {
initialHeaders = { [Constants.HttpHeaders.OfferThroughput]: body.throughput };
options.initialHeaders = Object.assign({}, options.initialHeaders, {
[Constants.HttpHeaders.OfferThroughput]: body.throughput
});
delete body.throughput;
}
const response = await this.clientContext.create<ContainerRequest>(
body,
path,
ResourceType.container,
id,
initialHeaders,
options
);
const response = await this.clientContext.create<ContainerRequest>(body, path, ResourceType.container, id, options);
const ref = new Container(this.database, response.result.id, this.clientContext);
return new ContainerResponse(response.result, response.headers, response.statusCode, ref);
}

Просмотреть файл

@ -79,13 +79,7 @@ export class Database {
public async read(options?: RequestOptions): Promise<DatabaseResponse> {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<DatabaseDefinition>(
path,
ResourceType.database,
id,
undefined,
options
);
const response = await this.clientContext.read<DatabaseDefinition>(path, ResourceType.database, id, options);
return new DatabaseResponse(response.result, response.headers, response.statusCode, this);
}
@ -93,13 +87,7 @@ export class Database {
public async delete(options?: RequestOptions): Promise<DatabaseResponse> {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete<DatabaseDefinition>(
path,
ResourceType.database,
id,
undefined,
options
);
const response = await this.clientContext.delete<DatabaseDefinition>(path, ResourceType.database, id, options);
return new DatabaseResponse(response.result, response.headers, response.statusCode, this);
}
}

Просмотреть файл

@ -90,16 +90,16 @@ export class Databases {
* @param body The {@link DatabaseDefinition} that represents the {@link Database} to be created.
* @param options Use to set options like response page size, continuation tokens, etc.
*/
public async create(body: DatabaseRequest, options?: RequestOptions): Promise<DatabaseResponse> {
public async create(body: DatabaseRequest, options: RequestOptions = {}): Promise<DatabaseResponse> {
const err = {};
if (!isResourceValid(body, err)) {
throw err;
}
let initialHeaders: CosmosHeaders;
if (body.throughput) {
initialHeaders = { [Constants.HttpHeaders.OfferThroughput]: body.throughput };
options.initialHeaders = Object.assign({}, options.initialHeaders, {
[Constants.HttpHeaders.OfferThroughput]: body.throughput
});
delete body.throughput;
}
@ -109,7 +109,6 @@ export class Databases {
path,
ResourceType.database,
undefined,
initialHeaders,
options
);
const ref = new Database(this.client, body.id, this.clientContext);

Просмотреть файл

@ -74,7 +74,7 @@ export class Item {
}
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<T>(path, ResourceType.item, id, undefined, options);
const response = await this.clientContext.read<T>(path, ResourceType.item, id, options);
return new ItemResponse(response.result, response.headers, response.statusCode, this);
}
@ -118,7 +118,7 @@ export class Item {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.replace<T>(body, path, ResourceType.item, id, undefined, options);
const response = await this.clientContext.replace<T>(body, path, ResourceType.item, id, options);
return new ItemResponse(response.result, response.headers, response.statusCode, this);
}
@ -144,7 +144,7 @@ export class Item {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete<T>(path, ResourceType.item, id, undefined, options);
const response = await this.clientContext.delete<T>(path, ResourceType.item, id, options);
return new ItemResponse(response.result, response.headers, response.statusCode, this);
}
}

Просмотреть файл

@ -223,7 +223,7 @@ export class Items {
const path = getPathFromLink(this.container.url, ResourceType.item);
const id = getIdFromLink(this.container.url);
const response = await this.clientContext.create<T>(body, path, ResourceType.item, id, undefined, options);
const response = await this.clientContext.create<T>(body, path, ResourceType.item, id, options);
const ref = new Item(
this.container,
@ -275,8 +275,7 @@ export class Items {
const path = getPathFromLink(this.container.url, ResourceType.item);
const id = getIdFromLink(this.container.url);
const response = (await this.clientContext.upsert<T>(body, path, ResourceType.item, id, undefined, options)) as T &
Resource;
const response = (await this.clientContext.upsert<T>(body, path, ResourceType.item, id, options)) as T & Resource;
const ref = new Item(
this.container,

Просмотреть файл

@ -33,13 +33,7 @@ export class Offer {
* @param options
*/
public async read(options?: RequestOptions): Promise<OfferResponse> {
const response = await this.clientContext.read<OfferDefinition>(
this.url,
ResourceType.offer,
this.id,
undefined,
options
);
const response = await this.clientContext.read<OfferDefinition>(this.url, ResourceType.offer, this.id, options);
return new OfferResponse(response.result, response.headers, response.statusCode, this);
}
@ -58,7 +52,6 @@ export class Offer {
this.url,
ResourceType.offer,
this.id,
undefined,
options
);
return new OfferResponse(response.result, response.headers, response.statusCode, this);

Просмотреть файл

@ -37,7 +37,6 @@ export class Permission {
path,
ResourceType.permission,
id,
undefined,
options
);
return new PermissionResponse(response.result, response.headers, response.statusCode, this);
@ -62,7 +61,6 @@ export class Permission {
path,
ResourceType.permission,
id,
undefined,
options
);
return new PermissionResponse(response.result, response.headers, response.statusCode, this);
@ -80,7 +78,6 @@ export class Permission {
path,
ResourceType.permission,
id,
undefined,
options
);
return new PermissionResponse(response.result, response.headers, response.statusCode, this);

Просмотреть файл

@ -83,7 +83,6 @@ export class Permissions {
path,
ResourceType.permission,
id,
undefined,
options
);
const ref = new Permission(this.user, response.result.id, this.clientContext);
@ -110,7 +109,6 @@ export class Permissions {
path,
ResourceType.permission,
id,
undefined,
options
);
const ref = new Permission(this.user, response.result.id, this.clientContext);

Просмотреть файл

@ -36,13 +36,7 @@ export class StoredProcedure {
public async read(options?: RequestOptions): Promise<StoredProcedureResponse> {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<StoredProcedureDefinition>(
path,
ResourceType.sproc,
id,
undefined,
options
);
const response = await this.clientContext.read<StoredProcedureDefinition>(path, ResourceType.sproc, id, options);
return new StoredProcedureResponse(response.result, response.headers, response.statusCode, this);
}
@ -69,7 +63,6 @@ export class StoredProcedure {
path,
ResourceType.sproc,
id,
undefined,
options
);
return new StoredProcedureResponse(response.result, response.headers, response.statusCode, this);
@ -83,13 +76,7 @@ export class StoredProcedure {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete<StoredProcedureDefinition>(
path,
ResourceType.sproc,
id,
undefined,
options
);
const response = await this.clientContext.delete<StoredProcedureDefinition>(path, ResourceType.sproc, id, options);
return new StoredProcedureResponse(response.result, response.headers, response.statusCode, this);
}

Просмотреть файл

@ -108,7 +108,6 @@ export class StoredProcedures {
path,
ResourceType.sproc,
id,
undefined,
options
);
const ref = new StoredProcedure(this.container, response.result.id, this.clientContext);
@ -143,7 +142,6 @@ export class StoredProcedures {
path,
ResourceType.sproc,
id,
undefined,
options
);
const ref = new StoredProcedure(this.container, response.result.id, this.clientContext);

Просмотреть файл

@ -42,13 +42,7 @@ export class Trigger {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<TriggerDefinition>(
path,
ResourceType.trigger,
id,
undefined,
options
);
const response = await this.clientContext.read<TriggerDefinition>(path, ResourceType.trigger, id, options);
return new TriggerResponse(response.result, response.headers, response.statusCode, this);
}
@ -70,14 +64,7 @@ export class Trigger {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.replace<TriggerDefinition>(
body,
path,
ResourceType.trigger,
id,
undefined,
options
);
const response = await this.clientContext.replace<TriggerDefinition>(body, path, ResourceType.trigger, id, options);
return new TriggerResponse(response.result, response.headers, response.statusCode, this);
}
@ -89,13 +76,7 @@ export class Trigger {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete<TriggerDefinition>(
path,
ResourceType.trigger,
id,
undefined,
options
);
const response = await this.clientContext.delete<TriggerDefinition>(path, ResourceType.trigger, id, options);
return new TriggerResponse(response.result, response.headers, response.statusCode, this);
}
}

Просмотреть файл

@ -83,14 +83,7 @@ export class Triggers {
const path = getPathFromLink(this.container.url, ResourceType.trigger);
const id = getIdFromLink(this.container.url);
const response = await this.clientContext.create<TriggerDefinition>(
body,
path,
ResourceType.trigger,
id,
undefined,
options
);
const response = await this.clientContext.create<TriggerDefinition>(body, path, ResourceType.trigger, id, options);
const ref = new Trigger(this.container, response.result.id, this.clientContext);
return new TriggerResponse(response.result, response.headers, response.statusCode, ref);
}
@ -118,14 +111,7 @@ export class Triggers {
const path = getPathFromLink(this.container.url, ResourceType.trigger);
const id = getIdFromLink(this.container.url);
const response = await this.clientContext.upsert<TriggerDefinition>(
body,
path,
ResourceType.trigger,
id,
undefined,
options
);
const response = await this.clientContext.upsert<TriggerDefinition>(body, path, ResourceType.trigger, id, options);
const ref = new Trigger(this.container, response.result.id, this.clientContext);
return new TriggerResponse(response.result, response.headers, response.statusCode, ref);
}

Просмотреть файл

@ -56,7 +56,7 @@ export class User {
public async read(options?: RequestOptions): Promise<UserResponse> {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<UserDefinition>(path, ResourceType.user, id, undefined, options);
const response = await this.clientContext.read<UserDefinition>(path, ResourceType.user, id, options);
return new UserResponse(response.result, response.headers, response.statusCode, this);
}
@ -74,14 +74,7 @@ export class User {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.replace<UserDefinition>(
body,
path,
ResourceType.user,
id,
undefined,
options
);
const response = await this.clientContext.replace<UserDefinition>(body, path, ResourceType.user, id, options);
return new UserResponse(response.result, response.headers, response.statusCode, this);
}
@ -93,7 +86,7 @@ export class User {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete<UserDefinition>(path, ResourceType.user, id, undefined, options);
const response = await this.clientContext.delete<UserDefinition>(path, ResourceType.user, id, options);
return new UserResponse(response.result, response.headers, response.statusCode, this);
}
}

Просмотреть файл

@ -67,14 +67,7 @@ export class Users {
const path = getPathFromLink(this.database.url, ResourceType.user);
const id = getIdFromLink(this.database.url);
const response = await this.clientContext.create<UserDefinition>(
body,
path,
ResourceType.user,
id,
undefined,
options
);
const response = await this.clientContext.create<UserDefinition>(body, path, ResourceType.user, id, options);
const ref = new User(this.database, response.result.id, this.clientContext);
return new UserResponse(response.result, response.headers, response.statusCode, ref);
}
@ -93,14 +86,7 @@ export class Users {
const path = getPathFromLink(this.database.url, ResourceType.user);
const id = getIdFromLink(this.database.url);
const response = await this.clientContext.upsert<UserDefinition>(
body,
path,
ResourceType.user,
id,
undefined,
options
);
const response = await this.clientContext.upsert<UserDefinition>(body, path, ResourceType.user, id, options);
const ref = new User(this.database, response.result.id, this.clientContext);
return new UserResponse(response.result, response.headers, response.statusCode, ref);
}

Просмотреть файл

@ -42,13 +42,7 @@ export class UserDefinedFunction {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.read<UserDefinedFunctionDefinition>(
path,
ResourceType.udf,
id,
undefined,
options
);
const response = await this.clientContext.read<UserDefinedFunctionDefinition>(path, ResourceType.udf, id, options);
return new UserDefinedFunctionResponse(response.result, response.headers, response.statusCode, this);
}
@ -78,7 +72,6 @@ export class UserDefinedFunction {
path,
ResourceType.udf,
id,
undefined,
options
);
return new UserDefinedFunctionResponse(response.result, response.headers, response.statusCode, this);
@ -92,7 +85,7 @@ export class UserDefinedFunction {
const path = getPathFromLink(this.url);
const id = getIdFromLink(this.url);
const response = await this.clientContext.delete(path, ResourceType.udf, id, undefined, options);
const response = await this.clientContext.delete(path, ResourceType.udf, id, options);
return new UserDefinedFunctionResponse(response.result, response.headers, response.statusCode, this);
}
}

Просмотреть файл

@ -90,7 +90,6 @@ export class UserDefinedFunctions {
path,
ResourceType.udf,
id,
undefined,
options
);
const ref = new UserDefinedFunction(this.container, response.result.id, this.clientContext);
@ -126,7 +125,6 @@ export class UserDefinedFunctions {
path,
ResourceType.udf,
id,
undefined,
options
);
const ref = new UserDefinedFunction(this.container, response.result.id, this.clientContext);

Просмотреть файл

@ -96,18 +96,6 @@ export function getHexaDigit() {
return Math.floor(Math.random() * 16).toString(16);
}
export function setIsUpsertHeader(headers: CosmosHeaders) {
if (headers === undefined || headers === null) {
throw new Error('The "headers" parameter must not be null or undefined');
}
if (typeof headers !== "object") {
throw new Error('The "headers" parameter must be an instance of "Object". Actual type is: "string".');
}
headers[Constants.HttpHeaders.IsUpsert] = true;
}
// TODO: replace with well known library?
export function generateGuidId() {
let id = "";

Просмотреть файл

@ -1,6 +0,0 @@
// TODO: Should we remove this?
export enum QueryCompatibilityMode {
Default = 0,
Query = 1,
SqlQuery = 2
}

Просмотреть файл

@ -11,7 +11,6 @@ export * from "./PartitionKey";
export * from "./PartitionKeyDefinition";
export * from "./PartitionKind";
export * from "./PermissionMode";
export * from "./QueryCompatibilityMode";
export * from "./TriggerOperation";
export * from "./TriggerType";
export * from "./UserDefinedFunctionType";

Просмотреть файл

@ -16,7 +16,6 @@ export {
PartitionKeyDefinition,
PartitionKind,
PermissionMode,
QueryCompatibilityMode,
TriggerOperation,
TriggerType,
UserDefinedFunctionType

Просмотреть файл

@ -1,9 +1,9 @@
import { CosmosHeaders } from "../index";
import { SharedOptions } from "./SharedOptions";
/**
* The feed options and query methods.
*/
export interface FeedOptions {
export interface FeedOptions extends SharedOptions {
/** Opaque token for continuing the enumeration. */
continuation?: string;
/**
@ -27,12 +27,6 @@ export interface FeedOptions {
maxDegreeOfParallelism?: number;
/** Max number of items to be returned in the enumeration operation. */
maxItemCount?: number;
/** Specifies a partition key definition for a particular path in the Azure Cosmos DB database service. */
partitionKey?: string;
/** Token for use with Session consistency. */
sessionToken?: string;
/** (Advanced use case) Initial headers to start with when sending requests to Cosmos */
initialHeaders?: CosmosHeaders;
/** Indicates a change feed request. Must be set to "Incremental feed", or omitted otherwise. */
useIncrementalFeed?: boolean;
/** Conditions Associated with the request. */

Просмотреть файл

@ -1,6 +1,12 @@
import { ClientContext } from "../ClientContext";
import { OperationType, ResourceType } from "../common";
import { HTTPMethod, OperationType, ResourceType } from "../common";
import { Agent } from "../CosmosClientOptions";
import { ConnectionPolicy } from "../documents";
import { GlobalEndpointManager } from "../globalEndpointManager";
import { CosmosHeaders } from "../queryExecutionContext/CosmosHeaders";
import { FeedOptions } from "./FeedOptions";
import { LocationRouting } from "./LocationRouting";
import { RequestOptions } from "./RequestOptions";
export interface RequestContext {
path?: string;
@ -8,5 +14,15 @@ export interface RequestContext {
client?: ClientContext;
retryCount?: number;
resourceType?: ResourceType;
resourceId?: string;
locationRouting?: LocationRouting;
globalEndpointManager: GlobalEndpointManager;
connectionPolicy: ConnectionPolicy;
requestAgent: Agent;
body?: any;
headers?: CosmosHeaders;
endpoint?: string;
method: HTTPMethod;
partitionKeyRangeId?: string;
options: FeedOptions | RequestOptions;
}

Просмотреть файл

@ -1,214 +1,103 @@
import { AbortController } from "abort-controller";
import { Agent, OutgoingHttpHeaders } from "http";
import { RequestOptions } from "https"; // TYPES ONLY
import fetch from "node-fetch";
import { parse } from "url";
import { Constants, HTTPMethod } from "../common/constants";
import { ConnectionPolicy } from "../documents";
import { GlobalEndpointManager } from "../globalEndpointManager";
import { CosmosHeaders } from "../queryExecutionContext/CosmosHeaders";
import fetch, { RequestInit, Response } from "node-fetch";
import { trimSlashes } from "../common";
import { Constants } from "../common/constants";
import * as RetryUtility from "../retry/retryUtility";
import { ErrorResponse } from "./ErrorResponse";
import { bodyFromData } from "./request";
import { RequestContext } from "./RequestContext";
import { Response } from "./Response";
import { Response as CosmosResponse } from "./Response";
import { TimeoutError } from "./TimeoutError";
/** @hidden */
export class RequestHandler {
public constructor(
private globalEndpointManager: GlobalEndpointManager,
private connectionPolicy: ConnectionPolicy,
private requestAgent: Agent
) {}
public static async createRequestObjectStub(
connectionPolicy: ConnectionPolicy,
requestOptions: RequestOptions,
body?: any
) {
let didTimeout: boolean;
const controller = new AbortController();
const signal = controller.signal;
const timeout = setTimeout(() => {
didTimeout = true;
controller.abort();
}, connectionPolicy.requestTimeout);
let response: any;
export async function executeRequest(requestContext: RequestContext) {
let didTimeout: boolean;
const controller = new AbortController();
const signal = controller.signal;
const timeout = setTimeout(() => {
didTimeout = true;
controller.abort();
}, requestContext.connectionPolicy.requestTimeout);
try {
// TODO Remove any
response = await fetch((requestOptions as any).href + requestOptions.path, {
method: requestOptions.method,
headers: requestOptions.headers as any,
agent: requestOptions.agent,
signal,
...(body && { body })
} as any); // TODO Remove any. Upstream issue https://github.com/lquixada/cross-fetch/issues/42
} catch (error) {
if (error.name === "AbortError") {
if (didTimeout === true) {
throw new TimeoutError();
}
// TODO handle user requested cancellation here
let response: Response;
if (requestContext.body) {
requestContext.body = bodyFromData(requestContext.body);
}
try {
response = await fetch(trimSlashes(requestContext.endpoint) + requestContext.path, {
method: requestContext.method,
headers: requestContext.headers as any,
agent: requestContext.requestAgent,
signal,
body: requestContext.body
} as RequestInit);
} catch (error) {
if (error.name === "AbortError") {
if (didTimeout === true) {
throw new TimeoutError();
}
throw error;
// TODO handle user requested cancellation here
}
throw error;
}
clearTimeout(timeout);
const result = response.status === 204 || response.status === 304 ? null : await response.json();
const headers = {} as any;
response.headers.forEach((value: string, key: string) => {
headers[key] = value;
});
if (response.status >= 400) {
const errorResponse: ErrorResponse = {
code: response.status,
// TODO Upstream code expects this as a string.
// So after parsing to JSON we convert it back to string if there is an error
body: JSON.stringify(result),
headers
};
if (Constants.HttpHeaders.ActivityId in headers) {
errorResponse.activityId = headers[Constants.HttpHeaders.ActivityId];
}
clearTimeout(timeout);
const result = response.status === 204 || response.status === 304 ? null : await response.json();
const headers = {} as any;
response.headers.forEach((value: string, key: string) => {
headers[key] = value;
});
if (response.status >= 400) {
const errorResponse: ErrorResponse = {
code: response.status,
// TODO Upstream code expects this as a string.
// So after parsing to JSON we convert it back to string if there is an error
body: JSON.stringify(result),
headers
};
if (Constants.HttpHeaders.ActivityId in headers) {
errorResponse.activityId = headers[Constants.HttpHeaders.ActivityId];
}
if (Constants.HttpHeaders.SubStatus in headers) {
errorResponse.substatus = parseInt(headers[Constants.HttpHeaders.SubStatus], 10);
}
if (Constants.HttpHeaders.RetryAfterInMilliseconds in headers) {
errorResponse.retryAfterInMilliseconds = parseInt(headers[Constants.HttpHeaders.RetryAfterInMilliseconds], 10);
}
return Promise.reject(errorResponse);
}
return Promise.resolve({
headers,
result,
statusCode: response.status
});
}
/**
* Creates the request object, call the passed callback when the response is retrieved.
* @param {object} globalEndpointManager - an instance of GlobalEndpointManager class.
* @param {object} connectionPolicy - an instance of ConnectionPolicy that has the connection configs.
* @param {object} requestAgent - the https agent used for send request
* @param {string} method - the http request method ( 'get', 'post', 'put', .. etc ).
* @param {String} hostname - The base url for the endpoint.
* @param {string} path - the path of the requesed resource.
* @param {Object} data - the request body. It can be either string, buffer, or undefined.
* @param {Object} queryParams - query parameters for the request.
* @param {Object} headers - specific headers for the request.
* @param {function} callback - the callback that will be called when the response is retrieved and processed.
*/
public static async request(
globalEndpointManager: GlobalEndpointManager,
connectionPolicy: ConnectionPolicy,
requestAgent: Agent,
method: HTTPMethod,
hostname: string,
request: RequestContext,
data: string | Buffer,
headers: CosmosHeaders
): Promise<Response<any>> {
// TODO: any
const path = (request as { path: string }).path === undefined ? request : (request as { path: string }).path;
let body: any; // TODO: any
if (data) {
body = bodyFromData(data);
if (!body) {
return {
result: {
message: "parameter data must be a javascript object, string, or Buffer"
},
headers: undefined
};
}
if (Constants.HttpHeaders.SubStatus in headers) {
errorResponse.substatus = parseInt(headers[Constants.HttpHeaders.SubStatus], 10);
}
const requestOptions: RequestOptions = parse(hostname);
requestOptions.method = method;
requestOptions.path += path;
requestOptions.headers = headers as OutgoingHttpHeaders;
requestOptions.agent = requestAgent;
requestOptions.secureProtocol = "TLSv1_client_method"; // TODO: Should be a constant
if (connectionPolicy.disableSSLVerification === true) {
requestOptions.rejectUnauthorized = false;
if (Constants.HttpHeaders.RetryAfterInMilliseconds in headers) {
errorResponse.retryAfterInMilliseconds = parseInt(headers[Constants.HttpHeaders.RetryAfterInMilliseconds], 10);
}
return RetryUtility.execute({
globalEndpointManager,
body,
createRequestObjectFunc: this.createRequestObjectStub,
connectionPolicy,
requestOptions,
request
});
}
/** @ignore */
public get(urlString: string, request: RequestContext, headers: CosmosHeaders) {
// TODO: any
return RequestHandler.request(
this.globalEndpointManager,
this.connectionPolicy,
this.requestAgent,
HTTPMethod.get,
urlString,
request,
undefined,
headers
);
}
/** @ignore */
public post(urlString: string, request: RequestContext, body: any, headers: CosmosHeaders) {
// TODO: any
return RequestHandler.request(
this.globalEndpointManager,
this.connectionPolicy,
this.requestAgent,
HTTPMethod.post,
urlString,
request,
body,
headers
);
}
/** @ignore */
public put(urlString: string, request: RequestContext, body: any, headers: CosmosHeaders) {
// TODO: any
return RequestHandler.request(
this.globalEndpointManager,
this.connectionPolicy,
this.requestAgent,
HTTPMethod.put,
urlString,
request,
body,
headers
);
}
/** @ignore */
public delete(urlString: string, request: RequestContext, headers: CosmosHeaders) {
return RequestHandler.request(
this.globalEndpointManager,
this.connectionPolicy,
this.requestAgent,
HTTPMethod.delete,
urlString,
request,
undefined,
headers
);
throw errorResponse;
}
return {
headers,
result,
statusCode: response.status
};
}
export async function request<T>(requestContext: RequestContext): Promise<CosmosResponse<T>> {
const { globalEndpointManager, connectionPolicy, body } = requestContext;
let parsedBody: any; // TODO: any
if (body) {
parsedBody = bodyFromData(body);
if (!body) {
throw new Error("parameter data must be a javascript object, string, or Buffer");
}
}
return RetryUtility.execute({
globalEndpointManager,
body: parsedBody,
connectionPolicy,
requestContext
});
}

Просмотреть файл

@ -1,10 +1,9 @@
import { PartitionKey } from "../documents";
import { CosmosHeaders } from "../index";
import { SharedOptions } from "./SharedOptions";
/**
* Options that can be specified for a requested issued to the Azure Cosmos DB servers.=
*/
export interface RequestOptions {
export interface RequestOptions extends SharedOptions {
/** Conditions Associated with the request. */
accessCondition?: {
/** Conditional HTTP method header type (IfMatch or IfNoneMatch). */
@ -23,8 +22,6 @@ export interface RequestOptions {
enableScriptLogging?: boolean;
/** Specifies indexing directives (index, do not index .. etc). */
indexingDirective?: string;
/** Represents Request Units(RU)/Minute throughput is enabled/disabled for a container. */
offerEnableRUPerMinuteThroughput?: boolean;
/** The offer throughput provisioned for a container in measurement of Requests-per-Unit. */
offerThroughput?: number;
/**
@ -33,8 +30,6 @@ export interface RequestOptions {
* This option is only valid when creating a document container.
*/
offerType?: string;
/** Specifies a partition key definition for a particular path in the Azure Cosmos DB database service. */
partitionKey?: PartitionKey | PartitionKey[];
/** Enables/disables getting document container quota related stats for document container read requests. */
populateQuotaInfo?: boolean;
/** Indicates what is the post trigger to be invoked after the operation. */
@ -43,10 +38,6 @@ export interface RequestOptions {
preTriggerInclude?: string | string[];
/** Expiry time (in seconds) for resource token associated with permission (applicable only for requests on permissions). */
resourceTokenExpirySeconds?: number;
/** Token for use with Session consistency. */
sessionToken?: string;
/** (Advanced use case) Initial headers to start with when sending requests to Cosmos */
initialHeaders?: CosmosHeaders;
/** (Advanced use case) The url to connect to. */
urlConnection?: string;
/** (Advanced use case) Skip getting info on the parititon key from the container. */

Просмотреть файл

@ -0,0 +1,23 @@
import { PartitionKey } from "../documents";
import { CosmosHeaders } from "../index";
import { RequestContext } from "./RequestContext";
interface BeforeOperationArgs {
endpoint: string;
request: RequestContext;
headers: CosmosHeaders;
}
/**
* Options that can be specified for a requested issued to the Azure Cosmos DB servers.=
*/
export interface SharedOptions {
/** Specifies a partition key definition for a particular path in the Azure Cosmos DB database service. */
partitionKey?: PartitionKey | PartitionKey[];
/** Enables/disables getting document container quota related stats for document container read requests. */
sessionToken?: string;
/** (Advanced use case) Initial headers to start with when sending requests to Cosmos */
initialHeaders?: CosmosHeaders;
/** (Advanced use case) TODO: Document */
beforeOperation?: (args: BeforeOperationArgs) => Promise<BeforeOperationArgs>;
}

Просмотреть файл

@ -1,6 +1,5 @@
export { ErrorResponse } from "./ErrorResponse";
export { FeedOptions } from "./FeedOptions";
export { RequestHandler } from "./RequestHandler";
export { RequestOptions } from "./RequestOptions";
export { Response } from "./Response";
export { ResourceResponse } from "./ResourceResponse";

Просмотреть файл

@ -24,112 +24,122 @@ export function bodyFromData(data: Buffer | string | object) {
return data;
}
export async function getHeaders(
authOptions: AuthOptions,
defaultHeaders: CosmosHeaders,
verb: HTTPMethod,
path: string,
resourceId: string,
resourceType: ResourceType,
options: RequestOptions | FeedOptions,
partitionKeyRangeId?: string,
useMultipleWriteLocations?: boolean
): Promise<CosmosHeaders> {
interface GetHeadersOptions {
authOptions: AuthOptions;
defaultHeaders: CosmosHeaders;
verb: HTTPMethod;
path: string;
resourceId: string;
resourceType: ResourceType;
options: RequestOptions & FeedOptions;
partitionKeyRangeId?: string;
useMultipleWriteLocations?: boolean;
}
export async function getHeaders({
authOptions,
defaultHeaders,
verb,
path,
resourceId,
resourceType,
options,
partitionKeyRangeId,
useMultipleWriteLocations
}: GetHeadersOptions): Promise<CosmosHeaders> {
const headers: CosmosHeaders = { ...defaultHeaders };
const opts: RequestOptions & FeedOptions = (options || {}) as any; // TODO: this is dirty
if (useMultipleWriteLocations) {
headers[Constants.HttpHeaders.ALLOW_MULTIPLE_WRITES] = true;
}
if (opts.continuation) {
headers[Constants.HttpHeaders.Continuation] = opts.continuation;
if (options.continuation) {
headers[Constants.HttpHeaders.Continuation] = options.continuation;
}
if (opts.preTriggerInclude) {
if (options.preTriggerInclude) {
headers[Constants.HttpHeaders.PreTriggerInclude] =
opts.preTriggerInclude.constructor === Array
? (opts.preTriggerInclude as string[]).join(",")
: (opts.preTriggerInclude as string);
options.preTriggerInclude.constructor === Array
? (options.preTriggerInclude as string[]).join(",")
: (options.preTriggerInclude as string);
}
if (opts.postTriggerInclude) {
if (options.postTriggerInclude) {
headers[Constants.HttpHeaders.PostTriggerInclude] =
opts.postTriggerInclude.constructor === Array
? (opts.postTriggerInclude as string[]).join(",")
: (opts.postTriggerInclude as string);
options.postTriggerInclude.constructor === Array
? (options.postTriggerInclude as string[]).join(",")
: (options.postTriggerInclude as string);
}
if (opts.offerType) {
headers[Constants.HttpHeaders.OfferType] = opts.offerType;
if (options.offerType) {
headers[Constants.HttpHeaders.OfferType] = options.offerType;
}
if (opts.offerThroughput) {
headers[Constants.HttpHeaders.OfferThroughput] = opts.offerThroughput;
if (options.offerThroughput) {
headers[Constants.HttpHeaders.OfferThroughput] = options.offerThroughput;
}
if (opts.maxItemCount) {
headers[Constants.HttpHeaders.PageSize] = opts.maxItemCount;
if (options.maxItemCount) {
headers[Constants.HttpHeaders.PageSize] = options.maxItemCount;
}
if (opts.accessCondition) {
if (opts.accessCondition.type === "IfMatch") {
headers[Constants.HttpHeaders.IfMatch] = opts.accessCondition.condition;
if (options.accessCondition) {
if (options.accessCondition.type === "IfMatch") {
headers[Constants.HttpHeaders.IfMatch] = options.accessCondition.condition;
} else {
headers[Constants.HttpHeaders.IfNoneMatch] = opts.accessCondition.condition;
headers[Constants.HttpHeaders.IfNoneMatch] = options.accessCondition.condition;
}
}
if (opts.useIncrementalFeed) {
if (options.useIncrementalFeed) {
headers[Constants.HttpHeaders.A_IM] = "Incremental Feed";
}
if (opts.indexingDirective) {
headers[Constants.HttpHeaders.IndexingDirective] = opts.indexingDirective;
if (options.indexingDirective) {
headers[Constants.HttpHeaders.IndexingDirective] = options.indexingDirective;
}
if (opts.consistencyLevel) {
headers[Constants.HttpHeaders.ConsistencyLevel] = opts.consistencyLevel;
if (options.consistencyLevel) {
headers[Constants.HttpHeaders.ConsistencyLevel] = options.consistencyLevel;
}
if (opts.resourceTokenExpirySeconds) {
headers[Constants.HttpHeaders.ResourceTokenExpiry] = opts.resourceTokenExpirySeconds;
if (options.resourceTokenExpirySeconds) {
headers[Constants.HttpHeaders.ResourceTokenExpiry] = options.resourceTokenExpirySeconds;
}
if (opts.sessionToken) {
headers[Constants.HttpHeaders.SessionToken] = opts.sessionToken;
if (options.sessionToken) {
headers[Constants.HttpHeaders.SessionToken] = options.sessionToken;
}
if (opts.enableScanInQuery) {
headers[Constants.HttpHeaders.EnableScanInQuery] = opts.enableScanInQuery;
if (options.enableScanInQuery) {
headers[Constants.HttpHeaders.EnableScanInQuery] = options.enableScanInQuery;
}
if (opts.enableCrossPartitionQuery) {
headers[Constants.HttpHeaders.EnableCrossPartitionQuery] = opts.enableCrossPartitionQuery;
if (options.enableCrossPartitionQuery) {
headers[Constants.HttpHeaders.EnableCrossPartitionQuery] = options.enableCrossPartitionQuery;
}
if (opts.populateQuotaInfo) {
headers[Constants.HttpHeaders.PopulateQuotaInfo] = opts.populateQuotaInfo;
if (options.populateQuotaInfo) {
headers[Constants.HttpHeaders.PopulateQuotaInfo] = options.populateQuotaInfo;
}
if (opts.populateQueryMetrics) {
headers[Constants.HttpHeaders.PopulateQueryMetrics] = opts.populateQueryMetrics;
if (options.populateQueryMetrics) {
headers[Constants.HttpHeaders.PopulateQueryMetrics] = options.populateQueryMetrics;
}
if (opts.maxDegreeOfParallelism !== undefined) {
if (options.maxDegreeOfParallelism !== undefined) {
headers[Constants.HttpHeaders.ParallelizeCrossPartitionQuery] = true;
}
if (opts.populateQuotaInfo) {
if (options.populateQuotaInfo) {
headers[Constants.HttpHeaders.PopulateQuotaInfo] = true;
}
if (opts.partitionKey !== undefined) {
let partitionKey: string[] | string = opts.partitionKey;
if (partitionKey === null || !Array.isArray(partitionKey)) {
partitionKey = [partitionKey as string];
if (options.partitionKey !== undefined) {
if (options.partitionKey === null || !Array.isArray(options.partitionKey)) {
options.partitionKey = [options.partitionKey as string];
}
headers[Constants.HttpHeaders.PartitionKey] = jsonStringifyAndEscapeNonASCII(partitionKey);
headers[Constants.HttpHeaders.PartitionKey] = jsonStringifyAndEscapeNonASCII(options.partitionKey);
}
if (authOptions.masterKey || authOptions.key || authOptions.tokenProvider) {
@ -150,15 +160,11 @@ export async function getHeaders(
headers[Constants.HttpHeaders.PartitionKeyRangeID] = partitionKeyRangeId;
}
if (opts.enableScriptLogging) {
headers[Constants.HttpHeaders.EnableScriptLogging] = opts.enableScriptLogging;
if (options.enableScriptLogging) {
headers[Constants.HttpHeaders.EnableScriptLogging] = options.enableScriptLogging;
}
if (opts.offerEnableRUPerMinuteThroughput) {
headers[Constants.HttpHeaders.OfferIsRUPerMinuteThroughputEnabled] = true;
}
if (opts.disableRUPerMinuteUsage) {
if (options.disableRUPerMinuteUsage) {
headers[Constants.HttpHeaders.DisableRUPerMinuteUsage] = true;
}
if (

Просмотреть файл

@ -1,5 +1,4 @@
import { RequestOptions } from "https";
import * as url from "url";
import { Constants } from "../common/constants";
import { sleep } from "../common/helper";
import { StatusCodes, SubStatusCodes } from "../common/statusCodes";
@ -8,6 +7,7 @@ import { GlobalEndpointManager } from "../globalEndpointManager";
import { Response } from "../request";
import { LocationRouting } from "../request/LocationRouting";
import { RequestContext } from "../request/RequestContext";
import { executeRequest } from "../request/RequestHandler";
import { DefaultRetryPolicy } from "./defaultRetryPolicy";
import { EndpointDiscoveryRetryPolicy } from "./endpointDiscoveryRetryPolicy";
import { ResourceThrottleRetryPolicy } from "./resourceThrottleRetryPolicy";
@ -25,12 +25,10 @@ export type CreateRequestObjectStubFunction = (
interface ExecuteArgs {
globalEndpointManager: GlobalEndpointManager;
body: Buffer;
createRequestObjectFunc: CreateRequestObjectStubFunction;
connectionPolicy: ConnectionPolicy;
requestOptions: RequestOptions;
request: RequestContext;
retryContext?: RetryContext;
retryPolicies?: RetryPolicies;
requestContext: RequestContext;
}
interface RetryPolicies {
@ -42,13 +40,11 @@ interface RetryPolicies {
export async function execute({
body,
createRequestObjectFunc,
connectionPolicy,
requestOptions,
globalEndpointManager,
request,
retryContext,
retryPolicies
retryPolicies,
requestContext
}: ExecuteArgs): Promise<Response<any>> {
// TODO: any response
@ -57,7 +53,10 @@ export async function execute({
}
if (!retryPolicies) {
retryPolicies = {
endpointDiscoveryRetryPolicy: new EndpointDiscoveryRetryPolicy(globalEndpointManager, request.operationType),
endpointDiscoveryRetryPolicy: new EndpointDiscoveryRetryPolicy(
globalEndpointManager,
requestContext.operationType
),
resourceThrottleRetryPolicy: new ResourceThrottleRetryPolicy(
connectionPolicy.retryOptions.maxRetryAttemptCount,
connectionPolicy.retryOptions.fixedRetryIntervalInMilliseconds,
@ -65,30 +64,30 @@ export async function execute({
),
sessionReadRetryPolicy: new SessionRetryPolicy(
globalEndpointManager,
request.resourceType,
request.operationType,
requestContext.resourceType,
requestContext.operationType,
connectionPolicy
),
defaultRetryPolicy: new DefaultRetryPolicy(request.operationType)
defaultRetryPolicy: new DefaultRetryPolicy(requestContext.operationType)
};
}
const httpsRequest = createRequestObjectFunc(connectionPolicy, requestOptions, body);
if (!request.locationRouting) {
request.locationRouting = new LocationRouting();
const httpsRequest = executeRequest(requestContext);
if (!requestContext.locationRouting) {
requestContext.locationRouting = new LocationRouting();
}
request.locationRouting.clearRouteToLocation();
requestContext.locationRouting.clearRouteToLocation();
if (retryContext) {
request.locationRouting.routeToLocation(
requestContext.locationRouting.routeToLocation(
retryContext.retryCount || 0,
!retryContext.retryRequestOnPreferredLocations
);
if (retryContext.clearSessionTokenNotAvailable) {
request.client.clearSessionToken(request.path);
requestContext.client.clearSessionToken(requestContext.path);
}
}
const locationEndpoint = await globalEndpointManager.resolveServiceEndpoint(request);
requestOptions = modifyRequestOptions(requestOptions, url.parse(locationEndpoint));
request.locationRouting.routeToLocation(locationEndpoint);
const locationEndpoint = await globalEndpointManager.resolveServiceEndpoint(requestContext);
requestContext.endpoint = locationEndpoint;
requestContext.locationRouting.routeToLocation(locationEndpoint);
try {
const response = await (httpsRequest as Promise<Response<any>>);
response.headers[Constants.ThrottleRetryCount] = retryPolicies.resourceThrottleRetryPolicy.currentRetryAttemptCount;
@ -116,36 +115,20 @@ export async function execute({
err.headers = { ...err.headers, ...headers };
throw err;
} else {
request.retryCount++;
requestContext.retryCount++;
const newUrl = (results as any)[1]; // TODO: any hack
if (newUrl !== undefined) {
modifyRequestOptions(requestOptions, url.parse(newUrl));
requestContext.endpoint = newUrl;
}
await sleep(retryPolicy.retryAfterInMilliseconds);
return execute({
body,
createRequestObjectFunc,
connectionPolicy,
requestOptions,
globalEndpointManager,
request,
requestContext,
retryContext,
retryPolicies
});
}
}
}
function modifyRequestOptions(
oldRequestOptions: RequestOptions | any, // TODO: any hack is bad
newUrl: url.UrlWithStringQuery | any
) {
// TODO: any hack is bad
const properties = Object.keys(newUrl);
for (const index in properties) {
if (properties[index] !== "path") {
oldRequestOptions[properties[index]] = newUrl[properties[index]];
}
}
return oldRequestOptions;
}

Просмотреть файл

@ -13,17 +13,13 @@ describe("NodeJS CRUD Tests", function() {
});
describe("validate database account functionality", function() {
const databaseAccountTest = async function() {
it("nativeApi Should get database account successfully name based", async function() {
const { resource: databaseAccount, headers } = await client.getDatabaseAccount();
assert.equal(databaseAccount.DatabasesLink, "/dbs/");
assert.equal(databaseAccount.MediaLink, "/media/");
assert.equal(databaseAccount.MaxMediaStorageUsageInMB, headers["x-ms-max-media-storage-usage-mb"]); // TODO: should use constants here
assert.equal(databaseAccount.CurrentMediaStorageUsageInMB, headers["x-ms-media-storage-usage-mb"]);
assert(databaseAccount.ConsistencyPolicy !== undefined);
};
it("nativeApi Should get database account successfully name based", async function() {
await databaseAccountTest();
});
});
});

Просмотреть файл

@ -4,11 +4,12 @@ import { ClientContext } from "../../dist-esm/ClientContext";
import { OperationType, ResourceType, trimSlashes } from "../../dist-esm/common";
import { ConsistencyLevel, PartitionKind } from "../../dist-esm/documents";
import { Constants, CosmosClient, CosmosHeaders } from "../../dist-esm/index";
import { RequestHandler } from "../../dist-esm/request";
import { SessionContainer } from "../../dist-esm/session/sessionContainer";
import { VectorSessionToken } from "../../dist-esm/session/VectorSessionToken";
import { endpoint, masterKey } from "../common/_testConfig";
import { getTestDatabase, removeAllDatabases } from "../common/TestHelpers";
import * as RequestHandler from "../../dist-esm/request/RequestHandler";
import { RequestContext } from "../../dist-esm/request/RequestContext";
// TODO: there is alot of "any" types for tokens here
// TODO: there is alot of leaky document client stuff here that will make removing document client hard
@ -35,13 +36,9 @@ describe("Session Token", function() {
const containerOptions = { offerThroughput: 25100 };
const clientContext: ClientContext = (client as any).clientContext;
const requestHandler: RequestHandler = (clientContext as any).requestHandler;
const sessionContainer: SessionContainer = (clientContext as any).sessionContainer;
const getSpy = sinon.spy(requestHandler, "get");
const postSpy = sinon.spy(requestHandler, "post");
const putSpy = sinon.spy(requestHandler, "put");
const deleteSpy = sinon.spy(requestHandler, "delete");
const spy = sinon.spy(RequestHandler, "request");
beforeEach(async function() {
await removeAllDatabases();
@ -52,7 +49,7 @@ describe("Session Token", function() {
const { resource: createdContainerDef } = await database.containers.create(containerDefinition, containerOptions);
const container = database.container(createdContainerDef.id);
assert.equal(postSpy.lastCall.args[3][Constants.HttpHeaders.SessionToken], undefined);
assert.equal(spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken], undefined);
// TODO: testing implementation detail by looking at containerResourceIdToSesssionTokens
let collRid2SessionToken: Map<string, Map<string, VectorSessionToken>> = (sessionContainer as any)
.collectionResourceIdToSessionTokens;
@ -60,7 +57,7 @@ describe("Session Token", function() {
const { resource: document1 } = await container.items.create({ id: "1" });
assert.equal(
postSpy.lastCall.args[3][Constants.HttpHeaders.SessionToken],
spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken],
undefined,
"Initial create token should be qual"
);
@ -82,7 +79,11 @@ describe("Session Token", function() {
resourceId: "2"
});
const { resource: document2 } = await container.items.create({ id: "2" });
assert.equal(postSpy.lastCall.args[3][Constants.HttpHeaders.SessionToken], token, "create token should be equal");
assert.equal(
spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken],
token,
"create token should be equal"
);
collRid2SessionToken = getCollection2TokenMap(sessionContainer);
assert.equal(collRid2SessionToken.size, 1, "Should only have one container in the sessioncontainer");
@ -107,7 +108,11 @@ describe("Session Token", function() {
resourceId: "1"
});
await container.item(document1.id, "1").read();
assert.equal(getSpy.lastCall.args[2][Constants.HttpHeaders.SessionToken], readToken, "read token should be equal");
assert.equal(
spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken],
readToken,
"read token should be equal"
);
collRid2SessionToken = getCollection2TokenMap(sessionContainer);
assert.equal(collRid2SessionToken.size, 1, "Should only have one container in the sessioncontainer");
@ -136,7 +141,7 @@ describe("Session Token", function() {
{ partitionKey: "1" }
);
assert.equal(
postSpy.lastCall.args[3][Constants.HttpHeaders.SessionToken],
spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken],
upsertToken,
"upsert token should be equal"
);
@ -167,7 +172,7 @@ describe("Session Token", function() {
});
await container.item(document2.id, "2").delete();
assert.equal(
deleteSpy.lastCall.args[2][Constants.HttpHeaders.SessionToken],
spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken],
deleteToken,
"delete token should be equal"
);
@ -198,7 +203,7 @@ describe("Session Token", function() {
});
await container.item(document13.id).replace({ id: "1", operation: "replace" }, { partitionKey: "1" });
assert.equal(
putSpy.lastCall.args[3][Constants.HttpHeaders.SessionToken],
spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken],
replaceToken,
"replace token should be equal"
);
@ -230,7 +235,7 @@ describe("Session Token", function() {
resourceType: ResourceType.item
});
await queryIterator.fetchAll();
assert.equal(postSpy.lastCall.args[3][Constants.HttpHeaders.SessionToken], queryToken);
assert.equal(spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken], queryToken);
collRid2SessionToken = getCollection2TokenMap(sessionContainer);
assert.equal(collRid2SessionToken.size, 1, "Should only have one container in the sessioncontainer");
@ -256,17 +261,14 @@ describe("Session Token", function() {
});
await container.delete();
assert.equal(
deleteSpy.lastCall.args[2][Constants.HttpHeaders.SessionToken],
spy.lastCall.args[0].headers[Constants.HttpHeaders.SessionToken],
deleteContainerToken,
"delete container token should match"
);
collRid2SessionToken = getCollection2TokenMap(sessionContainer);
assert.equal(collRid2SessionToken.size, 0, "collRid map should be empty on container delete");
getSpy.restore();
postSpy.restore();
deleteSpy.restore();
putSpy.restore();
spy.restore();
});
it("validate 'lsn not caught up' error for higher lsn and clearing session token", async function() {
@ -287,19 +289,19 @@ describe("Session Token", function() {
await database.containers.create(containerDefinition, containerOptions);
const container = database.container(containerDefinition.id);
const { headers } = await container.items.create({ id: "1" });
const callbackSpy = sinon.spy(function(path: string, reqHeaders: CosmosHeaders) {
await container.items.create({ id: "1" });
const callbackSpy = sinon.spy(function(requestContext: RequestContext) {
const oldTokens = getCollection2TokenMap(sessionContainer);
reqHeaders[Constants.HttpHeaders.SessionToken] = increaseLSN(oldTokens);
requestContext.headers[Constants.HttpHeaders.SessionToken] = increaseLSN(oldTokens);
});
const applySessionTokenStub = sinon.stub(clientContext as any, "applySessionToken").callsFake(callbackSpy);
const applySessionTokenStub = sinon.stub(clientContext as any, "applySessionToken").callsFake(callbackSpy as any);
try {
await container.item("1").read({ partitionKey: "1" });
assert.fail("readDocument must throw");
} catch (err) {
assert.equal(err.substatus, 1002, "Substatus should indicate the LSN didn't catchup.");
assert.equal(callbackSpy.callCount, 1);
assert.equal(trimSlashes(callbackSpy.lastCall.args[0]), containerLink + "/docs/1");
assert.equal(trimSlashes(callbackSpy.lastCall.args[0].path), containerLink + "/docs/1");
} finally {
applySessionTokenStub.restore();
}

Просмотреть файл

@ -1,6 +1,5 @@
import assert from "assert";
import { CosmosHeaders } from "../../dist-esm";
import { Constants, isResourceValid, setIsUpsertHeader } from "../../dist-esm/common";
import { isResourceValid } from "../../dist-esm/common";
describe("Helper methods", function() {
describe("isResourceValid Unit Tests", function() {
@ -13,37 +12,4 @@ describe("Helper methods", function() {
done();
});
});
describe("setIsUpsertHeader", function() {
it("Should add is-upsert header.", function(done) {
const headers: any = {};
assert.equal(undefined, headers[Constants.HttpHeaders.IsUpsert]);
setIsUpsertHeader(headers);
assert.equal(true, headers[Constants.HttpHeaders.IsUpsert]);
done();
});
it("Should update is-upsert header.", function(done) {
const headers: CosmosHeaders = {};
headers[Constants.HttpHeaders.IsUpsert] = false;
assert.equal(false, headers[Constants.HttpHeaders.IsUpsert]);
setIsUpsertHeader(headers);
assert.equal(true, headers[Constants.HttpHeaders.IsUpsert]);
done();
});
it("Should throw on undefined headers", function(done) {
assert.throws(function() {
setIsUpsertHeader(undefined);
}, /The "headers" parameter must not be null or undefined/);
done();
});
it("Should throw on null headers", function(done) {
assert.throws(function() {
setIsUpsertHeader(null);
}, /The "headers" parameter must not be null or undefined/);
done();
});
});
});

Просмотреть файл

@ -4,6 +4,7 @@ import { LocationCache } from "../../dist-esm/LocationCache";
import * as assert from "assert";
import { OperationType, ResourceType } from "../../dist-esm/common";
import { RequestContext } from "../../dist-esm/request/RequestContext";
const scenarios: Scenario[] = [];
const regions = ["westus", "East US", "eastus2", "south Centralus", "sEasIa"];