├── .editorConfig ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── all-issues.md │ └── stop-.md ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.json ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Contributing.md ├── LICENSE ├── PoliCheckExclusions.txt ├── README.md ├── SDK + Samples Workspace.code-workspace ├── SECURITY.md ├── api-extractor.json ├── azure-pipelines.yml ├── browser-test.js ├── bundle-types.js ├── changelog.md ├── consumer-test.js ├── consumer-test └── test.ts ├── dev.md ├── karma.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── samples ├── BulkUpdateWithSproc.ts ├── ChangeFeed.ts ├── ContainerManagement.ts ├── DatabaseManagement.ts ├── IndexManagement.ts ├── ItemManagement.ts ├── MultiRegionWrite │ ├── ConflictWorker.ts │ ├── MultiRegionWriteScenario.ts │ ├── Worker.ts │ ├── app.ts │ ├── config.ts │ ├── logger.ts │ ├── lwwSprocDef.ts │ ├── package-lock.json │ ├── package.json │ └── types.d.ts ├── QueryThroughput │ └── queryThroughput.js ├── ServerSideScripts │ ├── index.ts │ └── upsert.js ├── Shared │ ├── Data │ │ └── Families.json │ ├── config.ts │ └── handleError.ts ├── UserManagement.ts ├── package.json ├── tsconfig.json └── tslint.json ├── src ├── ChangeFeedIterator.ts ├── ChangeFeedOptions.ts ├── ChangeFeedResponse.ts ├── ClientContext.ts ├── CosmosClient.ts ├── CosmosClientOptions.ts ├── LocationCache.ts ├── LocationInfo.ts ├── auth.ts ├── client │ ├── Conflict │ │ ├── Conflict.ts │ │ ├── ConflictDefinition.ts │ │ ├── ConflictResolutionMode.ts │ │ ├── ConflictResolutionPolicy.ts │ │ ├── ConflictResponse.ts │ │ ├── Conflicts.ts │ │ └── index.ts │ ├── Container │ │ ├── Container.ts │ │ ├── ContainerDefinition.ts │ │ ├── ContainerRequest.ts │ │ ├── ContainerResponse.ts │ │ ├── Containers.ts │ │ ├── PartitionKeyRange.ts │ │ ├── UniqueKeyPolicy.ts │ │ └── index.ts │ ├── Database │ │ ├── Database.ts │ │ ├── DatabaseDefinition.ts │ │ ├── DatabaseRequest.ts │ │ ├── DatabaseResponse.ts │ │ ├── Databases.ts │ │ └── index.ts │ ├── Item │ │ ├── Item.ts │ │ ├── ItemDefinition.ts │ │ ├── ItemResponse.ts │ │ ├── Items.ts │ │ └── index.ts │ ├── Offer │ │ ├── Offer.ts │ │ ├── OfferDefinition.ts │ │ ├── OfferResponse.ts │ │ ├── Offers.ts │ │ └── index.ts │ ├── Permission │ │ ├── Permission.ts │ │ ├── PermissionBody.ts │ │ ├── PermissionDefinition.ts │ │ ├── PermissionResponse.ts │ │ ├── Permissions.ts │ │ └── index.ts │ ├── Resource.ts │ ├── Script │ │ └── Scripts.ts │ ├── StoredProcedure │ │ ├── StoredProcedure.ts │ │ ├── StoredProcedureDefinition.ts │ │ ├── StoredProcedureResponse.ts │ │ ├── StoredProcedures.ts │ │ └── index.ts │ ├── Trigger │ │ ├── Trigger.ts │ │ ├── TriggerDefinition.ts │ │ ├── TriggerResponse.ts │ │ ├── Triggers.ts │ │ └── index.ts │ ├── User │ │ ├── User.ts │ │ ├── UserDefinition.ts │ │ ├── UserResponse.ts │ │ ├── Users.ts │ │ └── index.ts │ ├── UserDefinedFunction │ │ ├── UserDefinedFunction.ts │ │ ├── UserDefinedFunctionDefinition.ts │ │ ├── UserDefinedFunctionResponse.ts │ │ ├── UserDefinedFunctions.ts │ │ └── index.ts │ └── index.ts ├── common │ ├── constants.ts │ ├── helper.ts │ ├── index.ts │ ├── logger.ts │ ├── partitionKeys.ts │ ├── platform.ts │ ├── statusCodes.ts │ └── uriFactory.ts ├── documents │ ├── ConnectionMode.ts │ ├── ConnectionPolicy.ts │ ├── ConsistencyLevel.ts │ ├── DataType.ts │ ├── DatabaseAccount.ts │ ├── Document.ts │ ├── IndexKind.ts │ ├── IndexingMode.ts │ ├── IndexingPolicy.ts │ ├── PartitionKey.ts │ ├── PartitionKeyDefinition.ts │ ├── PermissionMode.ts │ ├── TriggerOperation.ts │ ├── TriggerType.ts │ ├── UserDefinedFunctionType.ts │ └── index.ts ├── extractPartitionKey.ts ├── globalEndpointManager.ts ├── index.ts ├── plugins │ └── Plugin.ts ├── queryExecutionContext │ ├── Aggregators │ │ ├── AverageAggregator.ts │ │ ├── CountAggregator.ts │ │ ├── IAggregator.ts │ │ ├── MaxAggregator.ts │ │ ├── MinAggregator.ts │ │ ├── SumAggregator.ts │ │ └── index.ts │ ├── CosmosHeaders.ts │ ├── EndpointComponent │ │ ├── AggregateEndpointComponent.ts │ │ ├── OffsetLimitEndpointComponent.ts │ │ ├── OrderByEndpointComponent.ts │ │ ├── OrderedDistinctEndpointComponent.ts │ │ └── UnorderedDistinctEndpointComponent.ts │ ├── ExecutionContext.ts │ ├── FetchResult.ts │ ├── SqlQuerySpec.ts │ ├── defaultQueryExecutionContext.ts │ ├── documentProducer.ts │ ├── headerUtils.ts │ ├── index.ts │ ├── orderByDocumentProducerComparator.ts │ ├── orderByQueryExecutionContext.ts │ ├── parallelQueryExecutionContext.ts │ ├── parallelQueryExecutionContextBase.ts │ └── pipelinedQueryExecutionContext.ts ├── queryIterator.ts ├── queryMetrics │ ├── clientSideMetrics.ts │ ├── index.ts │ ├── queryMetrics.ts │ ├── queryMetricsConstants.ts │ ├── queryMetricsUtils.ts │ ├── queryPreparationTime.ts │ ├── runtimeExecutionTimes.ts │ └── timeSpan.ts ├── range │ ├── Range.ts │ ├── RangePartitionResolver.ts │ └── index.ts ├── request │ ├── ErrorResponse.ts │ ├── FeedOptions.ts │ ├── FeedResponse.ts │ ├── LocationRouting.ts │ ├── RequestContext.ts │ ├── RequestHandler.ts │ ├── RequestOptions.ts │ ├── ResourceResponse.ts │ ├── Response.ts │ ├── SharedOptions.ts │ ├── StatusCodes.ts │ ├── TimeoutError.ts │ ├── defaultAgent.browser.ts │ ├── defaultAgent.ts │ ├── index.ts │ └── request.ts ├── retry │ ├── RetryContext.ts │ ├── RetryPolicy.ts │ ├── defaultRetryPolicy.ts │ ├── endpointDiscoveryRetryPolicy.ts │ ├── index.ts │ ├── resourceThrottleRetryPolicy.ts │ ├── retryOptions.ts │ ├── retryUtility.ts │ └── sessionRetryPolicy.ts ├── routing │ ├── CollectionRoutingMapFactory.ts │ ├── QueryRange.ts │ ├── inMemoryCollectionRoutingMap.ts │ ├── index.ts │ ├── partitionKeyRangeCache.ts │ └── smartRoutingMapProvider.ts ├── session │ ├── SessionContext.ts │ ├── VectorSessionToken.ts │ └── sessionContainer.ts ├── tsconfig.json └── typings │ ├── atob.d.ts │ └── binary-search-bounds.d.ts ├── test ├── common │ ├── BaselineTest.PathParser.ts │ ├── MockClientContext.ts │ ├── MockQueryIterator.ts │ ├── TestData.ts │ ├── TestHelpers.ts │ ├── _testConfig.ts │ └── setup.ts ├── functional │ ├── authorization.spec.ts │ ├── client.spec.ts │ ├── container.spec.ts │ ├── database.spec.ts │ ├── databaseaccount.spec.ts │ ├── globalEndpointManager.spec.ts │ ├── item.spec.ts │ ├── npcontainer.spec.ts │ ├── offer.spec.ts │ ├── permission.spec.ts │ ├── plugin.spec.ts │ ├── query.spec.ts │ ├── spatial.spec.ts │ ├── sproc.spec.ts │ ├── trigger.spec.ts │ ├── ttl.spec.ts │ ├── udf.spec.ts │ └── user.spec.ts ├── integration │ ├── aggregateQuery.spec.ts │ ├── authorization.spec.ts │ ├── crossPartition.spec.ts │ ├── encoding.spec.ts │ ├── extractPartitionKey.spec.ts │ ├── incrementalFeed.spec.ts │ ├── multiregion.spec.ts │ ├── proxy.spec.ts │ ├── query.spec.ts │ ├── queryMetrics.spec.ts │ ├── retry.spec.ts │ ├── session.spec.ts │ ├── split.spec.ts │ └── sslVerification.spec.ts ├── readme.md ├── tsconfig.json ├── tslint.json ├── types │ └── proxy-agent.ts └── unit │ ├── defaultQueryExecutionContext.spec.ts │ ├── helper.spec.ts │ ├── inMemoryCollectionRoutingMap.spec.ts │ ├── locationCache.spec.ts │ ├── platform.spec.ts │ ├── range.spec.ts │ ├── rangePartitionResolver.spec.ts │ ├── sessionContainer.spec.ts │ └── smartRoutingMapProvider.spec.ts ├── tsconfig.json ├── tslint.json └── writeSDKVersion.js /.editorConfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = crlf 6 | indent_style = space 7 | indent_size = 4 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.ts] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/all-issues.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: All Issues 3 | about: '' 4 | title: Stop! This is not the repo you are looking for! 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | This repo has moved! Please file your issue in the new central SDK repo https://github.com/Azure/azure-sdk-for-js 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/stop-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Stop! 3 | about: This repo has moved to https://github.com/Azure/azure-sdk-for-js 4 | title: Stop! This is not the repo you are looking for! 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | This repo has moved! Please file your issue in the new central SDK repo https://github.com/Azure/azure-sdk-for-js 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | .vscode 3 | samples 4 | .gitattributes 5 | .gitignore 6 | *.code-workspace 7 | *.tgz 8 | tsdoc-metadata.json 9 | consumer-test/ 10 | test/ 11 | src/ 12 | lib/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.d.ts -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | notifications: 3 | email: false 4 | node_js: 5 | - 10 6 | script: 7 | - npm run ci 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha Tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "outFiles": ["${workspaceFolder}/dist-esm/**/*.js"], 13 | "preLaunchTask": "npm: compile", 14 | "args": ["-r", "esm", "-r", "test/common/setup.js", "${workspaceFolder}/test/**/*.spec.js"], 15 | "internalConsoleOptions": "openOnSessionStart", 16 | "env": { 17 | "MOCHA_TIMEOUT": "999999" 18 | }, 19 | "protocol": "inspector" 20 | }, 21 | { 22 | "type": "node", 23 | "request": "launch", 24 | "name": "Attach by Process ID", 25 | "processId": "${command:PickProcess}" 26 | }, 27 | { 28 | "type": "node", 29 | "request": "launch", 30 | "name": "build", 31 | "program": "${workspaceFolder}/node_modules/typescript/bin/tsc", 32 | "args": ["-b --verbose"] 33 | }, 34 | { 35 | "type": "node", 36 | "request": "launch", 37 | "name": "Debug file", 38 | "program": "${file}", 39 | "cwd": "${fileDirname}", 40 | "env": { 41 | "NODE_TLS_REJECT_UNAUTHORIZED": "0" 42 | } 43 | }, 44 | { 45 | "type": "node", 46 | "request": "launch", 47 | "name": "MutliRegionWrite - Debug", 48 | "args": ["${relativeFile}"], 49 | "runtimeArgs": ["-r", "ts-node/register"], 50 | "sourceMaps": true, 51 | "cwd": "${workspaceRoot}", 52 | "protocol": "inspector" 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mocha.files.glob":"test/legacy/**/*.js", 3 | "editor.formatOnSave": true, 4 | "typescript.tsdk": "node_modules/typescript/lib", 5 | "workbench.colorCustomizations": {} 6 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build", 9 | "problemMatcher": [ 10 | "$tsc", 11 | "$tslint5" 12 | ] 13 | }, 14 | { 15 | "type": "npm", 16 | "script": "compile", 17 | "problemMatcher": [ 18 | "$tsc" 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | Please read the contributing guidelines from the [Azure Team](https://azure.microsoft.com/en-us/blog/simple-contribution-to-azure-documentation-and-sdk/) 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017 Microsoft Corporation 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /PoliCheckExclusions.txt: -------------------------------------------------------------------------------- 1 | package-lock.json -------------------------------------------------------------------------------- /SDK + Samples Workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "samples\\TodoApp" 8 | } 9 | ], 10 | "settings": { 11 | "mocha.files.glob":"test/**/*.spec.ts", 12 | "mocha.sideBarOptions": { 13 | "lens": true, 14 | "decoration": true, 15 | "autoUpdateTime": 0, 16 | "showDebugTestStatus": true 17 | }, 18 | "mocha.runTestsOnSave": "false", 19 | "mocha.logVerbose": true, 20 | "mocha.options": { 21 | "compilers":{ 22 | "ts": "ts-node/register" 23 | } 24 | }, 25 | "mocha.requires": [ 26 | "ts-node/register" 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/api-extractor.schema.json", 3 | "compiler": { 4 | "configType": "tsconfig", 5 | "rootFolder": "./src" 6 | }, 7 | "project": { 8 | "entryPointSourceFile": "../dist-esm/index.d.ts" 9 | }, 10 | "validationRules": { 11 | "missingReleaseTags": "allow" 12 | }, 13 | "dtsRollup": { 14 | "enabled": true, 15 | "publishFolder": "", 16 | "mainDtsRollupPath": "./dist/index.d.ts" 17 | }, 18 | "apiReviewFile": { 19 | "enabled": false 20 | }, 21 | "apiJsonFile": { 22 | "enabled": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /browser-test.js: -------------------------------------------------------------------------------- 1 | const tests = require.context("./lib/", true, /\.spec\.js$/); 2 | 3 | tests.keys().forEach(tests); -------------------------------------------------------------------------------- /bundle-types.js: -------------------------------------------------------------------------------- 1 | // TODO. The api-extractor CLI command forces us into their docs generation and will error. 2 | // By invoking the node API we avoid this. 3 | // But we also swallow errors. 4 | // See https://github.com/Microsoft/web-build-tools/issues/920 5 | const ApiExtractor = require("@microsoft/api-extractor"); 6 | const NodeCoreLib = require("@microsoft/node-core-library"); 7 | const config = NodeCoreLib.JsonFile.loadAndValidate("api-extractor.json", ApiExtractor.Extractor.jsonSchema); 8 | 9 | // This interface provides additional runtime state that is NOT part of the config file 10 | const options = { 11 | localBuild: process.argv.indexOf("--ship") < 0 12 | }; 13 | const extractor = new ApiExtractor.Extractor(config, options); 14 | extractor.processProject(); 15 | -------------------------------------------------------------------------------- /consumer-test.js: -------------------------------------------------------------------------------- 1 | const execa = require("execa"); 2 | 3 | let versions = ["3.0", "3.1", "3.2", "3.3", "3.4"]; 4 | 5 | if (!process.env.SKIP_LATEST) { 6 | versions.push("latest"); 7 | } 8 | 9 | async function exec(cmd) { 10 | const command = execa.shell(cmd, { cwd: "./consumer-test" }); 11 | command.stderr.pipe(process.stderr); 12 | command.stdout.pipe(process.stdout); 13 | return command; 14 | } 15 | 16 | (async () => { 17 | try { 18 | console.log("Running typescript consumer test againast", versions); 19 | await exec("npm init -y"); 20 | console.log("Setting up typescript consumer project"); 21 | await exec("npm install --save ./.."); 22 | console.log("Installing @azure/cosmos as a file dependency"); 23 | for (const version of versions) { 24 | console.log(`Compling with typescript@${version} - Basic`); 25 | await exec(`npx -p typescript@${version} tsc ./test.ts --allowSyntheticDefaultImports true`); 26 | console.log(`Compling with typescript@${version} - Custom lib`); 27 | await exec(`npx -p typescript@${version} tsc ./test.ts --allowSyntheticDefaultImports true --lib es2018`); 28 | } 29 | process.exit(0); 30 | } catch (error) { 31 | console.log("Typescript consumer test failed!"); 32 | console.log(error); 33 | process.exit(1); 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /consumer-test/test.ts: -------------------------------------------------------------------------------- 1 | import * as Cosmos from "@azure/cosmos"; 2 | 3 | console.log(Object.keys(Cosmos)); 4 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu May 24 2018 16:35:54 GMT-0700 (Pacific Daylight Time) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | // frameworks to use 7 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 8 | frameworks: ['mocha'], 9 | 10 | 11 | // list of files / patterns to load in the browser 12 | files: [ 13 | './browser-test.js' 14 | ], 15 | 16 | 17 | // list of files / patterns to exclude 18 | exclude: [ 19 | './lib/dist/**' 20 | ], 21 | 22 | 23 | // preprocess matching files before serving them to the browser 24 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 25 | preprocessors: { 26 | './browser-test.js': [ 'webpack', 'sourcemap' ] 27 | }, 28 | 29 | webpack: require('./webpack.config.js'), 30 | 31 | webpackMiddleware: { 32 | stats: "errors-only" 33 | }, 34 | 35 | 36 | // test results reporter to use 37 | // possible values: 'dots', 'progress' 38 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 39 | reporters: ['progress', 'mocha'], 40 | 41 | 42 | // web server port 43 | port: 9876, 44 | 45 | 46 | // enable / disable colors in the output (reporters and logs) 47 | colors: true, 48 | 49 | 50 | // level of logging 51 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 52 | logLevel: config.LOG_DEBUG, 53 | 54 | 55 | // enable / disable watching file and executing tests whenever any file changes 56 | autoWatch: true, 57 | browserDisconnectTimeout: 120000, 58 | browserNoActivityTimeout: 120000, 59 | browserDisconnectTolerance: 5, 60 | 61 | // start these browsers 62 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 63 | browsers: ['Chrome_without_security'], 64 | 65 | customLaunchers: { 66 | Chrome_without_security: { 67 | base: 'Chrome', 68 | flags: ['--disable-web-security', '--auto-open-devtools-for-tabs'] 69 | } 70 | }, 71 | 72 | 73 | // Continuous Integration mode 74 | // if true, Karma captures browsers, runs the tests and exits 75 | singleRun: false, 76 | 77 | // Concurrency level 78 | // how many browser should be started simultaneous 79 | concurrency: Infinity 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "rollup-plugin-local-resolve"; 2 | export default [ 3 | { 4 | input: "dist-esm/index.js", 5 | output: { 6 | file: "dist/index.js", 7 | format: "umd", 8 | name: "Microsoft.Azure.Cosmos", 9 | sourcemap: true 10 | }, 11 | plugins: [resolve()] 12 | } 13 | ]; 14 | -------------------------------------------------------------------------------- /samples/ContainerManagement.ts: -------------------------------------------------------------------------------- 1 | import { finish, handleError, logStep, logSampleHeader } from "./Shared/handleError"; 2 | import { CosmosClient } from "../dist"; 3 | import { database as databaseId, container as containerId, endpoint, key } from "./Shared/config"; 4 | 5 | logSampleHeader("Container Management"); 6 | 7 | // Establish a new instance of the CosmosClient to be used throughout this demo 8 | const client = new CosmosClient({ endpoint, key }); 9 | 10 | //ensuring a database exists for us to work with 11 | async function run() { 12 | const { database } = await client.databases.createIfNotExists({ id: databaseId }); 13 | 14 | logStep(`Create container with id : ${containerId}`); 15 | await database.containers.createIfNotExists({ id: containerId }); 16 | 17 | logStep("Read all containers in database"); 18 | const iterator = database.containers.readAll(); 19 | const { resources: containersList } = await iterator.fetchAll(); 20 | console.log(" --- Priting via iterator.fetchAll()"); 21 | console.log(containersList); 22 | 23 | logStep("Read container definition"); 24 | const container = database.container(containerId); 25 | const { resource: containerDef } = await container.read(); 26 | console.log(`Container with url "${container.url}" was found its id is "${containerDef.id}`); 27 | 28 | logStep(`Delete container ${containerDef.id}`); 29 | await container.delete(); 30 | await finish(); 31 | } 32 | 33 | run().catch(handleError); 34 | -------------------------------------------------------------------------------- /samples/DatabaseManagement.ts: -------------------------------------------------------------------------------- 1 | import { handleError, logStep, logSampleHeader, finish } from "./Shared/handleError"; 2 | import { CosmosClient } from "../dist"; 3 | import { endpoint, key, database as databaseId } from "./Shared/config"; 4 | import assert from "assert"; 5 | 6 | logSampleHeader("Database Management"); 7 | 8 | // Establish a new instance of the CosmosClient to be used throughout this demo 9 | const client = new CosmosClient({ endpoint, key }); 10 | 11 | async function run() { 12 | logStep("Create database, if it doesn't already exist"); 13 | await client.databases.createIfNotExists({ id: databaseId }); 14 | console.log("Database with id " + databaseId + " created."); 15 | 16 | logStep("Read all databases"); 17 | const { resources: dbDefList } = await client.databases.readAll().fetchAll(); 18 | console.log(dbDefList); 19 | 20 | logStep("ReadDatabase with id '" + databaseId + "'"); 21 | const { resource: dbDef } = await client.database(databaseId).read(); 22 | // This uses Object deconstruction to just grab the body of the response, 23 | // but you can also grab the whole response object to use 24 | const databaseResponse = await client.database(databaseId).read(); 25 | const alsoDbDef = databaseResponse.resource; 26 | assert.equal(dbDef.id, alsoDbDef.id); // The bodies will also almost be equal, _ts will defer based on the read time 27 | // This applies for all response types, not just DatabaseResponse. 28 | 29 | console.log("Database with id of " + dbDef.id + "' was found"); 30 | 31 | logStep("delete database with id '" + databaseId + "'"); 32 | await finish(); 33 | } 34 | 35 | run().catch(handleError); 36 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/MultiRegionWriteScenario.ts: -------------------------------------------------------------------------------- 1 | import { ConsistencyLevel, CosmosClient } from "../../dist"; 2 | import config from "./config"; 3 | import { ConflictWorker } from "./ConflictWorker"; 4 | import { Worker } from "./Worker"; 5 | export class MultiRegionWriteScenario { 6 | private basicWorkers: Worker[] = []; 7 | private conflictWorker: ConflictWorker; 8 | constructor() { 9 | this.conflictWorker = new ConflictWorker( 10 | config.databaseName, 11 | config.basicCollectionName, 12 | config.manualCollectionName, 13 | config.lwwCollectionName, 14 | config.udpCollectionName 15 | ); 16 | for (const region of config.regions) { 17 | const client = new CosmosClient({ 18 | endpoint: config.endpoint, 19 | key: config.key, 20 | connectionPolicy: { 21 | preferredLocations: [region] 22 | }, 23 | consistencyLevel: ConsistencyLevel.Eventual 24 | }); 25 | this.conflictWorker.addClient(region, client); 26 | this.basicWorkers.push( 27 | new Worker(region, client.database(config.databaseName).container(config.basicCollectionName)) 28 | ); 29 | } 30 | } 31 | 32 | public async init() { 33 | await this.conflictWorker.init(); 34 | console.log("Initialized containers"); 35 | } 36 | 37 | public async runBasic() { 38 | console.log("################################################"); 39 | console.log("Basic Active-Active"); 40 | console.log("################################################"); 41 | 42 | console.log("1) Starting insert loops across multiple regions"); 43 | 44 | await Promise.all(this.basicWorkers.map(worker => worker.RunLoop(100))); 45 | 46 | console.log("2) Reading from every region..."); 47 | 48 | await Promise.all(this.basicWorkers.map(worker => worker.ReadAll(100 * this.basicWorkers.length))); 49 | 50 | console.log("3) Deleting all the documents"); 51 | 52 | await this.basicWorkers[0].DeleteAll(); 53 | 54 | console.log("################################################"); 55 | } 56 | 57 | public async runManualConflict() { 58 | console.log("################################################"); 59 | console.log("Manual Conflict Resolution"); 60 | console.log("################################################"); 61 | 62 | await this.conflictWorker.RunManualConflict(); 63 | console.log("################################################"); 64 | } 65 | 66 | public async runLWW() { 67 | console.log("################################################"); 68 | console.log("LWW Conflict Resolution"); 69 | console.log("################################################"); 70 | 71 | await this.conflictWorker.RunLWWConflict(); 72 | console.log("################################################"); 73 | } 74 | 75 | public async runUDP() { 76 | console.log("################################################"); 77 | console.log("UDP Conflict Resolution"); 78 | console.log("################################################"); 79 | 80 | await this.conflictWorker.RunUDP(); 81 | console.log("################################################"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/Worker.ts: -------------------------------------------------------------------------------- 1 | import { v4 as guid } from "uuid"; 2 | import { Container } from "../../dist"; 3 | 4 | // tslint:disable:no-console 5 | export class Worker { 6 | constructor(private readonly regionName: string, private readonly container: Container) {} 7 | 8 | public async RunLoop(itemsToInsert: number) { 9 | let iterationCount = 0; 10 | 11 | let latency: number[] = []; 12 | while (iterationCount++ < itemsToInsert) { 13 | const start = Date.now(); 14 | await this.container.items.create({ id: guid() }); 15 | const end = Date.now(); 16 | latency.push(end - start); 17 | } 18 | latency = latency.sort(); 19 | const p50Index = Math.floor(latency.length / 2); 20 | 21 | console.log(`Inserted ${latency.length} documents at ${this.regionName} with p50 ${latency[p50Index]}`); 22 | } 23 | 24 | public async ReadAll(expectedNumberOfItems: number) { 25 | while (true) { 26 | const { resources: items } = await this.container.items.readAll().fetchAll(); 27 | if (items.length < expectedNumberOfItems) { 28 | console.log( 29 | `Total item read ${items.length} from ${ 30 | this.regionName 31 | } is less than ${expectedNumberOfItems}, retrying reads` 32 | ); 33 | 34 | await this.sleep(1000); 35 | } else { 36 | console.log(`Read ${items.length} items from ${this.regionName}`); 37 | return; 38 | } 39 | } 40 | } 41 | 42 | public async DeleteAll() { 43 | const { resources: items } = await this.container.items.readAll().fetchAll(); 44 | for (const item of items) { 45 | await this.container.item(item.id, undefined).delete(); 46 | } 47 | console.log(`Deleted all documents from region ${this.regionName}`); 48 | } 49 | 50 | private sleep(timeinMS: number) { 51 | return new Promise(resolve => { 52 | setTimeout(() => { 53 | resolve(); 54 | }, timeinMS); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/app.ts: -------------------------------------------------------------------------------- 1 | import { MultiRegionWriteScenario } from "./MultiRegionWriteScenario"; 2 | 3 | // tslint:disable:no-console 4 | async function run() { 5 | const scenarios = new MultiRegionWriteScenario(); 6 | await scenarios.init(); 7 | 8 | await scenarios.runBasic(); 9 | await scenarios.runManualConflict(); 10 | await scenarios.runLWW(); 11 | await scenarios.runUDP(); 12 | } 13 | 14 | run() 15 | .catch(err => { 16 | console.error(err); 17 | process.exit(1); 18 | }) 19 | .then(() => { 20 | console.log("Complete!"); 21 | process.exit(0); 22 | }); 23 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/config.ts: -------------------------------------------------------------------------------- 1 | const endpoint = process.env["endpoint"]; 2 | const key = process.env["key"]; 3 | 4 | if (!endpoint || !key) { 5 | // tslint:disable-next-line:no-console 6 | console.error("Missing endpoint and key environment variables. Exiting..."); 7 | process.exit(1); 8 | } 9 | 10 | const regions = process.env["regions"].split(";"); 11 | 12 | const databaseName = process.env["databaseName"] || "js-mww-test"; 13 | const manualCollectionName = process.env["manualCollectionName"] || "manualCollection"; 14 | const lwwCollectionName = process.env["lwwCollectionName"] || "lwwCollection"; 15 | const udpCollectionName = process.env["udpCollectionName"] || "udpCollection"; 16 | const basicCollectionName = process.env["basicCollectionName"] || "basicCollection"; 17 | 18 | export default { 19 | endpoint, 20 | key, 21 | regions, 22 | databaseName, 23 | manualCollectionName, 24 | lwwCollectionName, 25 | udpCollectionName, 26 | basicCollectionName 27 | }; 28 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/logger.ts: -------------------------------------------------------------------------------- 1 | import * as Ora from "ora"; 2 | 3 | export default (text: string) => { 4 | return new Ora({ 5 | spinner: "clock", 6 | text 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/lwwSprocDef.ts: -------------------------------------------------------------------------------- 1 | import { StoredProcedureDefinition } from "../../dist-esm"; 2 | 3 | const lwwSprocDef: StoredProcedureDefinition = { 4 | id: "resolver", 5 | body: `function resolver(incomingRecord, existingRecord, isTombstone, conflictingRecords) { 6 | var collection = getContext().getCollection(); 7 | 8 | if (!incomingRecord) { 9 | if (existingRecord) { 10 | 11 | collection.deleteDocument(existingRecord._self, {}, function(err, responseOptions) { 12 | if (err) throw err; 13 | }); 14 | } 15 | } else if (isTombstone) { 16 | // delete always wins. 17 | } else { 18 | var documentToUse = incomingRecord; 19 | 20 | if (existingRecord) { 21 | if (documentToUse.regionId < existingRecord.regionId) { 22 | documentToUse = existingRecord; 23 | } 24 | } 25 | 26 | var i; 27 | for (i = 0; i < conflictingRecords.length; i++) { 28 | if (documentToUse.regionId < conflictingRecords[i].regionId) { 29 | documentToUse = conflictingRecords[i]; 30 | } 31 | } 32 | 33 | tryDelete(conflictingRecords, incomingRecord, existingRecord, documentToUse); 34 | } 35 | 36 | function tryDelete(documents, incoming, existing, documentToInsert) { 37 | if (documents.length > 0) { 38 | collection.deleteDocument(documents[0]._self, {}, function(err, responseOptions) { 39 | if (err) throw err; 40 | 41 | documents.shift(); 42 | tryDelete(documents, incoming, existing, documentToInsert); 43 | }); 44 | } else if (existing) { 45 | collection.replaceDocument(existing._self, documentToInsert, 46 | function(err, documentCreated) { 47 | if (err) throw err; 48 | }); 49 | } else { 50 | collection.createDocument(collection.getSelfLink(), documentToInsert, 51 | function(err, documentCreated) { 52 | if (err) throw err; 53 | }); 54 | } 55 | } 56 | }` 57 | }; 58 | 59 | export default lwwSprocDef; 60 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiregionwrite", 3 | "version": "0.0.1", 4 | "description": "Demonstrates the ability to read between multiple regions and handling conflicts", 5 | "main": "app.ts", 6 | "scripts": { 7 | "start": "ts-node app.ts" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ora": "^3.0.0", 13 | "uuid": "^3.3.2" 14 | }, 15 | "devDependencies": { 16 | "@types/ora": "^1.3.4", 17 | "@types/uuid": "^3.4.3", 18 | "ts-node": "^7.0.1", 19 | "typescript": "^3.0.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/MultiRegionWrite/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "ora"; 2 | declare module "uuid"; -------------------------------------------------------------------------------- /samples/QueryThroughput/queryThroughput.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | const { CosmosClient } = require("../.."); 4 | 5 | const endpoint = process.env.QUERY_SCENARIO_COSMOS_ENDPOINT || process.env.COSMOS_ENDPOINT; 6 | const key = process.env.QUERY_SCENARIO_COSMOS_KEY || process.env.COSMOS_KEY; 7 | const dbId = process.env.QUERY_SCENARIO_COSMOS_DB || process.env.COSMOS_DB; 8 | const containerId = process.env.QUERY_SCENARIO_COSMOS_CONTAINER || process.env.COSMOS_CONTAINER; 9 | 10 | async function run() { 11 | const client = new CosmosClient({ 12 | endpoint, 13 | key 14 | }); 15 | 16 | const query1 = "Select * from c order by c._ts"; 17 | const query2 = "Select * from c"; 18 | const query3 = "Select value count(c.id) from c"; 19 | 20 | const container = client.database(dbId).container(containerId); 21 | const options = { 22 | maxItemCount: 10000, 23 | maxDegreeOfParallelism: 1000, 24 | bufferItems: true 25 | }; 26 | 27 | const scenarios = []; 28 | scenarios.push({ container, query: query1, options }); 29 | scenarios.push({ container, query: query2, options }); 30 | scenarios.push({ container, query: query3, options }); 31 | 32 | for (const scenario of scenarios) { 33 | try { 34 | console.log("Scenario starting: " + scenario.query); 35 | const start = Date.now(); 36 | await runScenario(scenario.container, scenario.query, scenario.options); 37 | console.log('Scenario complete: "' + scenario.query + '" - took ' + (Date.now() - start) / 1000 + "s"); 38 | } catch (e) { 39 | console.log("Scenario failed: " + scenario.query + " - " + JSON.stringify(scenario.options)); 40 | } 41 | } 42 | } 43 | 44 | async function runScenario(container, query, options) { 45 | const queryIterator = container.items.query(query, options); 46 | let count = 0; 47 | while (queryIterator.hasMoreResults() && count <= 100000) { 48 | const { resources: results } = await queryIterator.fetchNext(); 49 | if (results != undefined) { 50 | count = count + results.length; 51 | } 52 | } 53 | } 54 | 55 | run().catch(console.error); 56 | -------------------------------------------------------------------------------- /samples/ServerSideScripts/index.ts: -------------------------------------------------------------------------------- 1 | import { logSampleHeader, logStep, finish, handleError } from "../Shared/handleError"; 2 | import { endpoint, key, database as databaseId, container as containerId } from "../Shared/config"; 3 | import { CosmosClient } from "../../dist"; 4 | 5 | logSampleHeader("Server Side Scripts"); 6 | 7 | // Establish a new instance of the DocumentDBClient to be used throughout this demo 8 | const client = new CosmosClient({ endpoint, key }); 9 | 10 | // Path to stored procedure definition 11 | const sprocDefinition = require("./upsert"); 12 | 13 | // Execute the stored procedure with the following parameters. 14 | const sprocParams = [ 15 | { 16 | id: "myDocument", 17 | foo: "bar" 18 | } 19 | ]; 20 | 21 | async function run() { 22 | const { database } = await client.databases.create({ id: databaseId }); 23 | const { container } = await database.containers.create({ id: containerId }); 24 | 25 | logStep("Creating the sproc: '" + sprocDefinition.id + "'"); 26 | 27 | // Query for the stored procedure. 28 | const { sproc, resource: sprocDef } = await container.scripts.storedProcedures.create(sprocDefinition); 29 | 30 | logStep("Executing the sproc: '" + sproc.id + "'"); 31 | console.log("Sproc parameters: " + JSON.stringify(sprocParams)); 32 | 33 | const { resource: results, headers } = await sproc.execute(undefined, sprocParams); 34 | 35 | console.log("//////////////////////////////////"); 36 | if (headers) { 37 | console.log("// responseHeaders"); 38 | console.log(headers); 39 | } 40 | if (results) { 41 | console.log("// results"); 42 | console.log(results); 43 | } 44 | console.log("//////////////////////////////////"); 45 | 46 | await finish(); 47 | } 48 | 49 | run().catch(handleError); 50 | -------------------------------------------------------------------------------- /samples/Shared/config.ts: -------------------------------------------------------------------------------- 1 | export const endpoint = process.env.COSMOS_ENDPOINT || "https://localhost:8081/"; 2 | export const key = 3 | process.env.COSMOS_KEY || "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; 4 | 5 | if (endpoint.includes("https://localhost")) { 6 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 7 | } 8 | 9 | export const database = "NodeSamples"; 10 | export const container = "Data"; 11 | -------------------------------------------------------------------------------- /samples/Shared/handleError.ts: -------------------------------------------------------------------------------- 1 | import { database, key, endpoint } from "./config"; 2 | import { CosmosClient } from "../../dist"; 3 | 4 | const client = new CosmosClient({ endpoint, key }); 5 | 6 | export async function handleError(error: any) { 7 | console.log("\nAn error with code '" + error.code + "' has occurred:"); 8 | console.log(error); 9 | await finish(); 10 | process.exitCode = 1; 11 | } 12 | 13 | export async function finish() { 14 | try { 15 | await client.database(database).delete(); 16 | console.log("\nEnd of demo."); 17 | } catch (err) { 18 | console.log(`Database: "${database}" might not have deleted properly. You might need to delete it manually.`); 19 | process.exitCode = 1; 20 | } 21 | } 22 | 23 | let currentStep = 0; 24 | export function logStep(message: string) { 25 | currentStep++; 26 | console.log(`\n${currentStep}: ${message}`); 27 | } 28 | 29 | export function logSampleHeader(sampleName: string) { 30 | console.log("Azure Cosmos DB Node.js Samples"); 31 | console.log("================================"); 32 | console.log(sampleName); 33 | console.log("================================"); 34 | } 35 | -------------------------------------------------------------------------------- /samples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cosmos-samples", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Cosmos DB Samples", 6 | "main": "app.js", 7 | "dependencies": {}, 8 | "scripts": { 9 | "ContainerManagement": "npx ts-node ./ContainerManagement", 10 | "UserManagement": "npx ts-node ./UserManagement", 11 | "ServerSideScripts": "npx ts-node ./ServerSideScripts", 12 | "ItemManagement": "npx ts-node ./ItemManagement", 13 | "DatabaseManagement": "npx ts-node ./DatabaseManagement", 14 | "IndexeManagement": "npx ts-node ./IndexeManagement", 15 | "ChangeFeed": "npx ts-node ./ChangeFeed", 16 | "BulkUpdateWithSproc": "npx ts-node ./BulkUpdateWithSproc" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "noImplicitAny": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "preserveConstEnums": true, 11 | "removeComments": false, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "newLine": "LF", 15 | "composite": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/ChangeFeedOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Specifies options for the change feed 3 | * 4 | * Some of these options control where and when to start reading from the change feed. The order of precedence is: 5 | * - continuation 6 | * - startTime 7 | * - startFromBeginning 8 | * 9 | * If none of those options are set, it will start reading changes from the first `ChangeFeedIterator.fetchNext()` call. 10 | */ 11 | export interface ChangeFeedOptions { 12 | /** 13 | * Max amount of items to return per page 14 | */ 15 | maxItemCount?: number; 16 | /** 17 | * The continuation token to start from. 18 | * 19 | * This is equivalent to the etag and continuation value from the `ChangeFeedResponse` 20 | */ 21 | continuation?: string; 22 | /** 23 | * The session token to use. If not specified, will use the most recent captured session token to start with. 24 | */ 25 | sessionToken?: string; 26 | /** 27 | * Signals whether to start from the beginning or not. 28 | */ 29 | startFromBeginning?: boolean; 30 | /** 31 | * Specified the start time to start reading changes from. 32 | */ 33 | startTime?: Date; 34 | } 35 | -------------------------------------------------------------------------------- /src/ChangeFeedResponse.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "./common"; 2 | import { CosmosHeaders } from "./queryExecutionContext"; 3 | 4 | /** 5 | * A single response page from the Azure Cosmos DB Change Feed 6 | */ 7 | export class ChangeFeedResponse { 8 | /** 9 | * @internal 10 | * @hidden 11 | * 12 | * @param result 13 | * @param count 14 | * @param statusCode 15 | * @param headers 16 | */ 17 | constructor( 18 | /** 19 | * Gets the items returned in the response from Azure Cosmos DB 20 | */ 21 | public readonly result: T, 22 | /** 23 | * Gets the number of items returned in the response from Azure Cosmos DB 24 | */ 25 | public readonly count: number, 26 | /** 27 | * Gets the status code of the response from Azure Cosmos DB 28 | */ 29 | public readonly statusCode: number, 30 | headers: CosmosHeaders 31 | ) { 32 | this.headers = Object.freeze(headers); 33 | } 34 | 35 | /** 36 | * Gets the request charge for this request from the Azure Cosmos DB service. 37 | */ 38 | public get requestCharge(): number { 39 | const rus = this.headers[Constants.HttpHeaders.RequestCharge]; 40 | return rus ? parseInt(rus, 10) : null; 41 | } 42 | 43 | /** 44 | * Gets the activity ID for the request from the Azure Cosmos DB service. 45 | */ 46 | public get activityId(): string { 47 | return this.headers[Constants.HttpHeaders.ActivityId]; 48 | } 49 | 50 | /** 51 | * Gets the continuation token to be used for continuing enumeration of the Azure Cosmos DB service. 52 | * 53 | * This is equivalent to the `etag` property. 54 | */ 55 | public get continuation(): string { 56 | return this.etag; 57 | } 58 | 59 | /** 60 | * Gets the session token for use in session consistency reads from the Azure Cosmos DB service. 61 | */ 62 | public get sessionToken(): string { 63 | return this.headers[Constants.HttpHeaders.SessionToken]; 64 | } 65 | 66 | /** 67 | * Gets the entity tag associated with last transaction in the Azure Cosmos DB service, 68 | * which can be used as If-Non-Match Access condition for ReadFeed REST request or 69 | * `continuation` property of `ChangeFeedOptions` parameter for 70 | * `Items.readChangeFeed()` 71 | * to get feed changes since the transaction specified by this entity tag. 72 | * 73 | * This is equivalent to the `continuation` property. 74 | */ 75 | public get etag(): string { 76 | return this.headers[Constants.HttpHeaders.ETag]; 77 | } 78 | 79 | /** 80 | * Response headers of the response from Azure Cosmos DB 81 | */ 82 | public headers: CosmosHeaders; 83 | } 84 | -------------------------------------------------------------------------------- /src/CosmosClientOptions.ts: -------------------------------------------------------------------------------- 1 | import { TokenProvider } from "./auth"; 2 | import { PermissionDefinition } from "./client"; 3 | import { ConnectionPolicy, ConsistencyLevel } from "./documents"; 4 | import { PluginConfig } from "./plugins/Plugin"; 5 | import { CosmosHeaders } from "./queryExecutionContext/CosmosHeaders"; 6 | 7 | // We expose our own Agent interface to avoid taking a dependency on and leaking node types. This interface should mirror the node Agent interface 8 | export interface Agent { 9 | maxFreeSockets: number; 10 | maxSockets: number; 11 | sockets: any; 12 | requests: any; 13 | destroy(): void; 14 | } 15 | 16 | export interface CosmosClientOptions { 17 | /** The service endpoint to use to create the client. */ 18 | endpoint: string; 19 | /** The account master or readonly key */ 20 | key?: string; 21 | /** An object that contains resources tokens. 22 | * Keys for the object are resource Ids and values are the resource tokens. 23 | */ 24 | resourceTokens?: { [resourcePath: string]: string }; 25 | /** A user supplied function for resolving header authorization tokens. 26 | * Allows users to generating their own auth tokens, potentially using a separate service 27 | */ 28 | tokenProvider?: TokenProvider; 29 | /** An array of {@link Permission} objects. */ 30 | permissionFeed?: PermissionDefinition[]; 31 | /** An instance of {@link ConnectionPolicy} class. 32 | * This parameter is optional and the default connectionPolicy will be used if omitted. 33 | */ 34 | connectionPolicy?: ConnectionPolicy; 35 | /** An optional parameter that represents the consistency level. 36 | * It can take any value from {@link ConsistencyLevel}. 37 | */ 38 | consistencyLevel?: keyof typeof ConsistencyLevel; 39 | defaultHeaders?: CosmosHeaders; 40 | /** An optional custom http(s) Agent to be used in NodeJS enironments 41 | * Use an agent such as https://github.com/TooTallNate/node-proxy-agent if you need to connect to Cosmos via a proxy 42 | */ 43 | agent?: Agent; 44 | /** @internal */ 45 | plugins?: PluginConfig[]; 46 | } 47 | -------------------------------------------------------------------------------- /src/LocationInfo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to store the location info in Location Cache 3 | * @private 4 | * @hidden 5 | */ 6 | export class LocationInfo { 7 | public preferredLocations: ReadonlyArray; 8 | public availableReadEndpointByLocation: ReadonlyMap; 9 | public availableWriteEndpointByLocation: ReadonlyMap; 10 | public orderedWriteLocations: ReadonlyArray; 11 | public orderedReadLocations: ReadonlyArray; 12 | public writeEndpoints: ReadonlyArray; 13 | public readEndpoints: ReadonlyArray; 14 | 15 | public constructor(other: LocationInfo); 16 | public constructor(preferredLocations: ReadonlyArray, defaultEndpoint: string); 17 | public constructor( 18 | preferredLocationsOrOtherLocationInfo: ReadonlyArray | LocationInfo, 19 | defaultEndpoint?: string 20 | ) { 21 | let preferredLocations: ReadonlyArray = null; 22 | let other: LocationInfo = null; 23 | if (Array.isArray(preferredLocationsOrOtherLocationInfo)) { 24 | preferredLocations = preferredLocationsOrOtherLocationInfo; 25 | } else if (preferredLocationsOrOtherLocationInfo instanceof LocationInfo) { 26 | other = preferredLocationsOrOtherLocationInfo; 27 | } else { 28 | throw new Error("Invalid type passed to LocationInfo"); 29 | } 30 | 31 | if (preferredLocations && defaultEndpoint) { 32 | this.preferredLocations = preferredLocations; 33 | this.availableWriteEndpointByLocation = new Map(); 34 | this.availableReadEndpointByLocation = new Map(); 35 | this.orderedWriteLocations = []; 36 | this.orderedReadLocations = []; 37 | this.writeEndpoints = [defaultEndpoint]; 38 | this.readEndpoints = [defaultEndpoint]; 39 | } else if (other) { 40 | this.preferredLocations = other.preferredLocations; 41 | this.availableReadEndpointByLocation = other.availableReadEndpointByLocation; 42 | this.availableWriteEndpointByLocation = other.availableWriteEndpointByLocation; 43 | this.orderedReadLocations = other.orderedReadLocations; 44 | this.orderedWriteLocations = other.orderedWriteLocations; 45 | this.writeEndpoints = other.writeEndpoints; 46 | this.readEndpoints = other.readEndpoints; 47 | } else { 48 | // This should never be called 49 | throw new Error("Invalid arguments passed to LocationInfo"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/client/Conflict/Conflict.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../../ClientContext"; 2 | import { Constants, getIdFromLink, getPathFromLink, ResourceType } from "../../common"; 3 | import { RequestOptions } from "../../request"; 4 | import { Container } from "../Container"; 5 | import { ConflictDefinition } from "./ConflictDefinition"; 6 | import { ConflictResponse } from "./ConflictResponse"; 7 | 8 | /** 9 | * Use to read or delete a given {@link Conflict} by id. 10 | * 11 | * @see {@link Conflicts} to query or read all conflicts. 12 | */ 13 | export class Conflict { 14 | /** 15 | * Returns a reference URL to the resource. Used for linking in Permissions. 16 | */ 17 | public get url() { 18 | return `/${this.container.url}/${Constants.Path.ConflictsPathSegment}/${this.id}`; 19 | } 20 | /** 21 | * @hidden 22 | * @param container The parent {@link Container}. 23 | * @param id The id of the given {@link Conflict}. 24 | */ 25 | constructor( 26 | public readonly container: Container, 27 | public readonly id: string, 28 | private readonly clientContext: ClientContext 29 | ) {} 30 | 31 | /** 32 | * Read the {@link ConflictDefinition} for the given {@link Conflict}. 33 | * @param options 34 | */ 35 | public async read(options?: RequestOptions): Promise { 36 | const path = getPathFromLink(this.url, ResourceType.conflicts); 37 | const id = getIdFromLink(this.url); 38 | 39 | const response = await this.clientContext.read({ 40 | path, 41 | resourceType: ResourceType.user, 42 | resourceId: id, 43 | options 44 | }); 45 | return new ConflictResponse(response.result, response.headers, response.code, this); 46 | } 47 | 48 | /** 49 | * Delete the given {@link ConflictDefinition}. 50 | * @param options 51 | */ 52 | public async delete(options?: RequestOptions): Promise { 53 | const path = getPathFromLink(this.url); 54 | const id = getIdFromLink(this.url); 55 | 56 | const response = await this.clientContext.delete({ 57 | path, 58 | resourceType: ResourceType.conflicts, 59 | resourceId: id, 60 | options 61 | }); 62 | return new ConflictResponse(response.result, response.headers, response.code, this); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/client/Conflict/ConflictDefinition.ts: -------------------------------------------------------------------------------- 1 | import { OperationType, ResourceType } from "../../common"; 2 | 3 | export interface ConflictDefinition { 4 | /** The id of the conflict */ 5 | id?: string; 6 | /** Source resource id */ 7 | resourceId?: string; 8 | resourceType?: ResourceType; 9 | operationType?: OperationType; 10 | content?: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/client/Conflict/ConflictResolutionMode.ts: -------------------------------------------------------------------------------- 1 | export enum ConflictResolutionMode { 2 | Custom = "Custom", 3 | LastWriterWins = "LastWriterWins" 4 | } 5 | -------------------------------------------------------------------------------- /src/client/Conflict/ConflictResolutionPolicy.ts: -------------------------------------------------------------------------------- 1 | import { ConflictResolutionMode } from "./ConflictResolutionMode"; 2 | 3 | /** 4 | * Represents the conflict resolution policy configuration for specifying how to resolve conflicts 5 | * in case writes from different regions result in conflicts on documents in the collection in the Azure Cosmos DB service. 6 | */ 7 | export interface ConflictResolutionPolicy { 8 | /** 9 | * Gets or sets the in the Azure Cosmos DB service. By default it is {@link ConflictResolutionMode.LastWriterWins}. 10 | */ 11 | mode?: keyof typeof ConflictResolutionMode; 12 | /** 13 | * Gets or sets the path which is present in each document in the Azure Cosmos DB service for last writer wins conflict-resolution. 14 | * This path must be present in each document and must be an integer value. 15 | * In case of a conflict occurring on a document, the document with the higher integer value in the specified path will be picked. 16 | * If the path is unspecified, by default the timestamp path will be used. 17 | * 18 | * This value should only be set when using {@link ConflictResolutionMode.LastWriterWins}. 19 | * 20 | * ```typescript 21 | * conflictResolutionPolicy.ConflictResolutionPath = "/name/first"; 22 | * ``` 23 | * 24 | */ 25 | conflictResolutionPath?: string; 26 | /** 27 | * Gets or sets the {@link StoredProcedure} which is used for conflict resolution in the Azure Cosmos DB service. 28 | * This stored procedure may be created after the {@link Container} is created and can be changed as required. 29 | * 30 | * 1. This value should only be set when using {@link ConflictResolutionMode.Custom}. 31 | * 2. In case the stored procedure fails or throws an exception, the conflict resolution will default to registering conflicts in the conflicts feed. 32 | * 33 | * ```typescript 34 | * conflictResolutionPolicy.ConflictResolutionProcedure = "resolveConflict" 35 | * ``` 36 | */ 37 | conflictResolutionProcedure?: string; 38 | } 39 | -------------------------------------------------------------------------------- /src/client/Conflict/ConflictResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request"; 3 | import { Resource } from "../Resource"; 4 | import { Conflict } from "./Conflict"; 5 | import { ConflictDefinition } from "./ConflictDefinition"; 6 | 7 | export class ConflictResponse extends ResourceResponse { 8 | constructor(resource: ConflictDefinition & Resource, headers: CosmosHeaders, statusCode: number, conflict: Conflict) { 9 | super(resource, headers, statusCode); 10 | this.conflict = conflict; 11 | } 12 | /** A reference to the {@link Conflict} corresponding to the returned {@link ConflictDefinition}. */ 13 | public readonly conflict: Conflict; 14 | } 15 | -------------------------------------------------------------------------------- /src/client/Conflict/Conflicts.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../../ClientContext"; 2 | import { getIdFromLink, getPathFromLink, ResourceType } from "../../common"; 3 | import { SqlQuerySpec } from "../../queryExecutionContext"; 4 | import { QueryIterator } from "../../queryIterator"; 5 | import { FeedOptions } from "../../request"; 6 | import { Container } from "../Container"; 7 | import { Resource } from "../Resource"; 8 | import { ConflictDefinition } from "./ConflictDefinition"; 9 | 10 | /** 11 | * Use to query or read all conflicts. 12 | * 13 | * @see {@link Conflict} to read or delete a given {@link Conflict} by id. 14 | */ 15 | export class Conflicts { 16 | constructor(public readonly container: Container, private readonly clientContext: ClientContext) {} 17 | 18 | /** 19 | * Queries all conflicts. 20 | * @param query Query configuration for the operation. See {@link SqlQuerySpec} for more info on how to configure a query. 21 | * @param options Use to set options like response page size, continuation tokens, etc. 22 | * @returns {@link QueryIterator} Allows you to return results in an array or iterate over them one at a time. 23 | */ 24 | public query(query: string | SqlQuerySpec, options?: FeedOptions): QueryIterator; 25 | /** 26 | * Queries all conflicts. 27 | * @param query Query configuration for the operation. See {@link SqlQuerySpec} for more info on how to configure a query. 28 | * @param options Use to set options like response page size, continuation tokens, etc. 29 | * @returns {@link QueryIterator} Allows you to return results in an array or iterate over them one at a time. 30 | */ 31 | public query(query: string | SqlQuerySpec, options?: FeedOptions): QueryIterator; 32 | public query(query: string | SqlQuerySpec, options?: FeedOptions): QueryIterator { 33 | const path = getPathFromLink(this.container.url, ResourceType.conflicts); 34 | const id = getIdFromLink(this.container.url); 35 | 36 | return new QueryIterator(this.clientContext, query, options, innerOptions => { 37 | return this.clientContext.queryFeed({ 38 | path, 39 | resourceType: ResourceType.conflicts, 40 | resourceId: id, 41 | resultFn: result => result.Conflicts, 42 | query, 43 | options: innerOptions 44 | }); 45 | }); 46 | } 47 | 48 | /** 49 | * Reads all conflicts 50 | * @param options Use to set options like response page size, continuation tokens, etc. 51 | */ 52 | public readAll(options?: FeedOptions): QueryIterator { 53 | return this.query(undefined, options); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/client/Conflict/index.ts: -------------------------------------------------------------------------------- 1 | export { Conflict } from "./Conflict"; 2 | export { Conflicts } from "./Conflicts"; 3 | export { ConflictDefinition } from "./ConflictDefinition"; 4 | export { ConflictResponse } from "./ConflictResponse"; 5 | export { ConflictResolutionPolicy } from "./ConflictResolutionPolicy"; 6 | export { ConflictResolutionMode } from "./ConflictResolutionMode"; 7 | -------------------------------------------------------------------------------- /src/client/Container/ContainerDefinition.ts: -------------------------------------------------------------------------------- 1 | import { IndexingPolicy, PartitionKeyDefinition } from "../../documents"; 2 | import { ConflictResolutionPolicy } from "../Conflict/ConflictResolutionPolicy"; 3 | import { UniqueKeyPolicy } from "./UniqueKeyPolicy"; 4 | 5 | export interface ContainerDefinition { 6 | /** The id of the container. */ 7 | id?: string; 8 | /** TODO */ 9 | partitionKey?: PartitionKeyDefinition; 10 | /** The indexing policy associated with the container. */ 11 | indexingPolicy?: IndexingPolicy; 12 | /** The default time to live in seconds for items in a container. */ 13 | defaultTtl?: number; 14 | /** The conflict resolution policy used to resolve conflicts in a container. */ 15 | conflictResolutionPolicy?: ConflictResolutionPolicy; 16 | /** Policy for additional keys that must be unique per partion key */ 17 | uniqueKeyPolicy?: UniqueKeyPolicy; 18 | } 19 | -------------------------------------------------------------------------------- /src/client/Container/ContainerRequest.ts: -------------------------------------------------------------------------------- 1 | import { ContainerDefinition } from "./ContainerDefinition"; 2 | 3 | export interface ContainerRequest extends ContainerDefinition { 4 | /** Throughput for this container. */ 5 | throughput?: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/client/Container/ContainerResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request/ResourceResponse"; 3 | import { Resource } from "../Resource"; 4 | import { ContainerDefinition } from "./ContainerDefinition"; 5 | import { Container } from "./index"; 6 | 7 | /** Response object for Container operations */ 8 | export class ContainerResponse extends ResourceResponse { 9 | constructor( 10 | resource: ContainerDefinition & Resource, 11 | headers: CosmosHeaders, 12 | statusCode: number, 13 | container: Container 14 | ) { 15 | super(resource, headers, statusCode); 16 | this.container = container; 17 | } 18 | /** A reference to the {@link Container} that the returned {@link ContainerDefinition} corresponds to. */ 19 | public readonly container: Container; 20 | } 21 | -------------------------------------------------------------------------------- /src/client/Container/PartitionKeyRange.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | */ 4 | export interface PartitionKeyRange { 5 | id: string; 6 | minInclusive: string; 7 | maxExclusive: string; 8 | ridPrefix: number; 9 | throughputFraction: number; 10 | status: string; 11 | parents: string[]; 12 | } 13 | -------------------------------------------------------------------------------- /src/client/Container/UniqueKeyPolicy.ts: -------------------------------------------------------------------------------- 1 | /** Interface for setting unique keys on container creation */ 2 | export interface UniqueKeyPolicy { 3 | uniqueKeys: UniqueKey[]; 4 | } 5 | 6 | /** Interface for a single unique key passed as part of UniqueKeyPolicy */ 7 | export interface UniqueKey { 8 | paths: string[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/client/Container/index.ts: -------------------------------------------------------------------------------- 1 | export { Container } from "./Container"; 2 | export { Containers } from "./Containers"; 3 | export { ContainerDefinition } from "./ContainerDefinition"; 4 | export { ContainerResponse } from "./ContainerResponse"; 5 | export { PartitionKeyRange } from "./PartitionKeyRange"; 6 | -------------------------------------------------------------------------------- /src/client/Database/DatabaseDefinition.ts: -------------------------------------------------------------------------------- 1 | export interface DatabaseDefinition { 2 | /** The id of the database. */ 3 | id?: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/client/Database/DatabaseRequest.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseDefinition } from "./DatabaseDefinition"; 2 | 3 | export interface DatabaseRequest extends DatabaseDefinition { 4 | /** Throughput for this database. */ 5 | throughput?: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/client/Database/DatabaseResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request/ResourceResponse"; 3 | import { Resource } from "../Resource"; 4 | import { Database } from "./Database"; 5 | import { DatabaseDefinition } from "./DatabaseDefinition"; 6 | 7 | /** Response object for Database operations */ 8 | export class DatabaseResponse extends ResourceResponse { 9 | constructor(resource: DatabaseDefinition & Resource, headers: CosmosHeaders, statusCode: number, database: Database) { 10 | super(resource, headers, statusCode); 11 | this.database = database; 12 | } 13 | /** A reference to the {@link Database} that the returned {@link DatabaseDefinition} corresponds to. */ 14 | public readonly database: Database; 15 | } 16 | -------------------------------------------------------------------------------- /src/client/Database/index.ts: -------------------------------------------------------------------------------- 1 | export { Database } from "./Database"; 2 | export { Databases } from "./Databases"; 3 | export { DatabaseDefinition } from "./DatabaseDefinition"; 4 | export { DatabaseResponse } from "./DatabaseResponse"; 5 | -------------------------------------------------------------------------------- /src/client/Item/ItemDefinition.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Items in Cosmos DB are simply JSON objects. 3 | * Most of the Item operations allow for your to provide your own type 4 | * that extends the very simple ItemDefinition. 5 | * 6 | * You cannot use any reserved keys. You can see the reserved key list 7 | * in {@link ItemBody} 8 | */ 9 | export interface ItemDefinition { 10 | /** The id of the item. User settable property. Uniquely identifies the item along with the partition key */ 11 | id?: string; 12 | /** Time to live in seconds for collections with TTL enabled */ 13 | ttl?: number; 14 | [key: string]: any; 15 | } 16 | -------------------------------------------------------------------------------- /src/client/Item/ItemResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request/ResourceResponse"; 3 | import { Resource } from "../Resource"; 4 | import { Item } from "./Item"; 5 | import { ItemDefinition } from "./ItemDefinition"; 6 | 7 | export class ItemResponse extends ResourceResponse { 8 | constructor(resource: T & Resource, headers: CosmosHeaders, statusCode: number, subsstatusCode: number, item: Item) { 9 | super(resource, headers, statusCode, subsstatusCode); 10 | this.item = item; 11 | } 12 | /** Reference to the {@link Item} the response corresponds to. */ 13 | public readonly item: Item; 14 | } 15 | -------------------------------------------------------------------------------- /src/client/Item/index.ts: -------------------------------------------------------------------------------- 1 | export { Item } from "./Item"; 2 | export { Items } from "./Items"; 3 | export { ItemResponse } from "./ItemResponse"; 4 | export { ItemDefinition } from "./ItemDefinition"; 5 | -------------------------------------------------------------------------------- /src/client/Offer/Offer.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../../ClientContext"; 2 | import { Constants, isResourceValid, ResourceType } from "../../common"; 3 | import { CosmosClient } from "../../CosmosClient"; 4 | import { RequestOptions } from "../../request"; 5 | import { OfferDefinition } from "./OfferDefinition"; 6 | import { OfferResponse } from "./OfferResponse"; 7 | 8 | /** 9 | * Use to read or replace an existing {@link Offer} by id. 10 | * 11 | * @see {@link Offers} to query or read all offers. 12 | */ 13 | export class Offer { 14 | /** 15 | * Returns a reference URL to the resource. Used for linking in Permissions. 16 | */ 17 | public get url() { 18 | return `/${Constants.Path.OffersPathSegment}/${this.id}`; 19 | } 20 | /** 21 | * @hidden 22 | * @param client The parent {@link CosmosClient} for the Database Account. 23 | * @param id The id of the given {@link Offer}. 24 | */ 25 | constructor( 26 | public readonly client: CosmosClient, 27 | public readonly id: string, 28 | private readonly clientContext: ClientContext 29 | ) {} 30 | 31 | /** 32 | * Read the {@link OfferDefinition} for the given {@link Offer}. 33 | * @param options 34 | */ 35 | public async read(options?: RequestOptions): Promise { 36 | const response = await this.clientContext.read({ 37 | path: this.url, 38 | resourceType: ResourceType.offer, 39 | resourceId: this.id, 40 | options 41 | }); 42 | return new OfferResponse(response.result, response.headers, response.code, this); 43 | } 44 | 45 | /** 46 | * Replace the given {@link Offer} with the specified {@link OfferDefinition}. 47 | * @param body The specified {@link OfferDefinition} 48 | * @param options 49 | */ 50 | public async replace(body: OfferDefinition, options?: RequestOptions): Promise { 51 | const err = {}; 52 | if (!isResourceValid(body, err)) { 53 | throw err; 54 | } 55 | const response = await this.clientContext.replace({ 56 | body, 57 | path: this.url, 58 | resourceType: ResourceType.offer, 59 | resourceId: this.id, 60 | options 61 | }); 62 | return new OfferResponse(response.result, response.headers, response.code, this); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/client/Offer/OfferDefinition.ts: -------------------------------------------------------------------------------- 1 | export interface OfferDefinition { 2 | id?: string; 3 | offerType?: string; // TODO: enum? 4 | offerVersion?: string; // TODO: enum? 5 | resource?: string; 6 | offerResourceId?: string; 7 | content?: { 8 | offerThroughput: number; 9 | offerIsRUPerMinuteThroughputEnabled: boolean; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/client/Offer/OfferResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request"; 3 | import { Resource } from "../Resource"; 4 | import { Offer } from "./Offer"; 5 | import { OfferDefinition } from "./OfferDefinition"; 6 | 7 | export class OfferResponse extends ResourceResponse { 8 | constructor(resource: OfferDefinition & Resource, headers: CosmosHeaders, statusCode: number, offer: Offer) { 9 | super(resource, headers, statusCode); 10 | this.offer = offer; 11 | } 12 | /** A reference to the {@link Offer} corresponding to the returned {@link OfferDefinition}. */ 13 | public readonly offer: Offer; 14 | } 15 | -------------------------------------------------------------------------------- /src/client/Offer/Offers.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../../ClientContext"; 2 | import { ResourceType } from "../../common"; 3 | import { CosmosClient } from "../../CosmosClient"; 4 | import { SqlQuerySpec } from "../../queryExecutionContext"; 5 | import { QueryIterator } from "../../queryIterator"; 6 | import { FeedOptions } from "../../request"; 7 | import { Resource } from "../Resource"; 8 | import { OfferDefinition } from "./OfferDefinition"; 9 | 10 | /** 11 | * Use to query or read all Offers. 12 | * 13 | * @see {@link Offer} to read or replace an existing {@link Offer} by id. 14 | */ 15 | export class Offers { 16 | /** 17 | * @hidden 18 | * @param client The parent {@link CosmosClient} for the offers. 19 | */ 20 | constructor(public readonly client: CosmosClient, private readonly clientContext: ClientContext) {} 21 | 22 | /** 23 | * Query all offers. 24 | * @param query Query configuration for the operation. See {@link SqlQuerySpec} for more info on how to configure a query. 25 | * @param options 26 | */ 27 | public query(query: SqlQuerySpec, options?: FeedOptions): QueryIterator; 28 | /** 29 | * Query all offers. 30 | * @param query Query configuration for the operation. See {@link SqlQuerySpec} for more info on how to configure a query. 31 | * @param options 32 | */ 33 | public query(query: SqlQuerySpec, options?: FeedOptions): QueryIterator; 34 | public query(query: SqlQuerySpec, options?: FeedOptions): QueryIterator { 35 | return new QueryIterator(this.clientContext, query, options, innerOptions => { 36 | return this.clientContext.queryFeed({ 37 | path: "/offers", 38 | resourceType: ResourceType.offer, 39 | resourceId: "", 40 | resultFn: result => result.Offers, 41 | query, 42 | options: innerOptions 43 | }); 44 | }); 45 | } 46 | 47 | /** 48 | * Read all offers. 49 | * @param options 50 | * @example Read all offers to array. 51 | * ```typescript 52 | * const {body: offerList} = await client.offers.readAll().fetchAll(); 53 | * ``` 54 | */ 55 | public readAll(options?: FeedOptions): QueryIterator { 56 | return this.query(undefined, options); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/client/Offer/index.ts: -------------------------------------------------------------------------------- 1 | export { Offer } from "./Offer"; 2 | export { Offers } from "./Offers"; 3 | export { OfferDefinition } from "./OfferDefinition"; 4 | export { OfferResponse } from "./OfferResponse"; 5 | -------------------------------------------------------------------------------- /src/client/Permission/Permission.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../../ClientContext"; 2 | import { createPermissionUri, getIdFromLink, getPathFromLink, isResourceValid, ResourceType } from "../../common"; 3 | import { RequestOptions } from "../../request/RequestOptions"; 4 | import { User } from "../User"; 5 | import { PermissionBody } from "./PermissionBody"; 6 | import { PermissionDefinition } from "./PermissionDefinition"; 7 | import { PermissionResponse } from "./PermissionResponse"; 8 | 9 | /** 10 | * Use to read, replace, or delete a given {@link Permission} by id. 11 | * 12 | * @see {@link Permissions} to create, upsert, query, or read all Permissions. 13 | */ 14 | export class Permission { 15 | /** 16 | * Returns a reference URL to the resource. Used for linking in Permissions. 17 | */ 18 | public get url() { 19 | return createPermissionUri(this.user.database.id, this.user.id, this.id); 20 | } 21 | /** 22 | * @hidden 23 | * @param user The parent {@link User}. 24 | * @param id The id of the given {@link Permission}. 25 | */ 26 | constructor(public readonly user: User, public readonly id: string, private readonly clientContext: ClientContext) {} 27 | 28 | /** 29 | * Read the {@link PermissionDefinition} of the given {@link Permission}. 30 | * @param options 31 | */ 32 | public async read(options?: RequestOptions): Promise { 33 | const path = getPathFromLink(this.url); 34 | const id = getIdFromLink(this.url); 35 | 36 | const response = await this.clientContext.read({ 37 | path, 38 | resourceType: ResourceType.permission, 39 | resourceId: id, 40 | options 41 | }); 42 | return new PermissionResponse(response.result, response.headers, response.code, this); 43 | } 44 | 45 | /** 46 | * Replace the given {@link Permission} with the specified {@link PermissionDefinition}. 47 | * @param body The specified {@link PermissionDefinition}. 48 | * @param options 49 | */ 50 | public async replace(body: PermissionDefinition, options?: RequestOptions): Promise { 51 | const err = {}; 52 | if (!isResourceValid(body, err)) { 53 | throw err; 54 | } 55 | 56 | const path = getPathFromLink(this.url); 57 | const id = getIdFromLink(this.url); 58 | 59 | const response = await this.clientContext.replace({ 60 | body, 61 | path, 62 | resourceType: ResourceType.permission, 63 | resourceId: id, 64 | options 65 | }); 66 | return new PermissionResponse(response.result, response.headers, response.code, this); 67 | } 68 | 69 | /** 70 | * Delete the given {@link Permission}. 71 | * @param options 72 | */ 73 | public async delete(options?: RequestOptions): Promise { 74 | const path = getPathFromLink(this.url); 75 | const id = getIdFromLink(this.url); 76 | 77 | const response = await this.clientContext.delete({ 78 | path, 79 | resourceType: ResourceType.permission, 80 | resourceId: id, 81 | options 82 | }); 83 | return new PermissionResponse(response.result, response.headers, response.code, this); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/client/Permission/PermissionBody.ts: -------------------------------------------------------------------------------- 1 | export interface PermissionBody { 2 | /** System generated resource token for the particular resource and user */ 3 | _token: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/client/Permission/PermissionDefinition.ts: -------------------------------------------------------------------------------- 1 | import { PermissionMode } from "../../documents"; 2 | 3 | export interface PermissionDefinition { 4 | /** The id of the permission */ 5 | id: string; 6 | /** The mode of the permission, must be a value of {@link PermissionMode} */ 7 | permissionMode: PermissionMode; 8 | /** The link of the resource that the permission will be applied to. */ 9 | resource: string; 10 | resourcePartitionKey?: string | any[]; // TODO: what's allowed here? 11 | } 12 | -------------------------------------------------------------------------------- /src/client/Permission/PermissionResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request"; 3 | import { Resource } from "../Resource"; 4 | import { Permission } from "./Permission"; 5 | import { PermissionBody } from "./PermissionBody"; 6 | import { PermissionDefinition } from "./PermissionDefinition"; 7 | 8 | export class PermissionResponse extends ResourceResponse { 9 | constructor( 10 | resource: PermissionDefinition & PermissionBody & Resource, 11 | headers: CosmosHeaders, 12 | statusCode: number, 13 | permission: Permission 14 | ) { 15 | super(resource, headers, statusCode); 16 | this.permission = permission; 17 | } 18 | /** A reference to the {@link Permission} corresponding to the returned {@link PermissionDefinition}. */ 19 | public readonly permission: Permission; 20 | } 21 | -------------------------------------------------------------------------------- /src/client/Permission/index.ts: -------------------------------------------------------------------------------- 1 | export { Permission } from "./Permission"; 2 | export { Permissions } from "./Permissions"; 3 | export { PermissionDefinition } from "./PermissionDefinition"; 4 | export { PermissionResponse } from "./PermissionResponse"; 5 | -------------------------------------------------------------------------------- /src/client/Resource.ts: -------------------------------------------------------------------------------- 1 | export interface Resource { 2 | /** Required. User settable property. Unique name that identifies the item, that is, no two items share the same ID within a database. The id must not exceed 255 characters. */ 3 | id: string; 4 | /** System generated property. The resource ID (_rid) is a unique identifier that is also hierarchical per the resource stack on the resource model. It is used internally for placement and navigation of the item resource. */ 5 | _rid: string; 6 | /** System generated property. Specifies the last updated timestamp of the resource. The value is a timestamp. */ 7 | _ts: number; 8 | /** System generated property. The unique addressable URI for the resource. */ 9 | _self: string; 10 | /** System generated property. Represents the resource etag required for optimistic concurrency control. */ 11 | _etag: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/client/Script/Scripts.ts: -------------------------------------------------------------------------------- 1 | import { StoredProcedure, StoredProcedures, Trigger, Triggers, UserDefinedFunction, UserDefinedFunctions } from "../.."; 2 | import { ClientContext } from "../../ClientContext"; 3 | import { Container } from "../Container/Container"; 4 | 5 | export class Scripts { 6 | /** 7 | * @param container The parent {@link Container}. 8 | * @hidden 9 | */ 10 | constructor(public readonly container: Container, private readonly clientContext: ClientContext) {} 11 | 12 | /** 13 | * Used to read, replace, or delete a specific, existing {@link StoredProcedure} by id. 14 | * 15 | * Use `.storedProcedures` for creating new stored procedures, or querying/reading all stored procedures. 16 | * @param id The id of the {@link StoredProcedure}. 17 | */ 18 | public storedProcedure(id: string): StoredProcedure { 19 | return new StoredProcedure(this.container, id, this.clientContext); 20 | } 21 | 22 | /** 23 | * Used to read, replace, or delete a specific, existing {@link Trigger} by id. 24 | * 25 | * Use `.triggers` for creating new triggers, or querying/reading all triggers. 26 | * @param id The id of the {@link Trigger}. 27 | */ 28 | public trigger(id: string): Trigger { 29 | return new Trigger(this.container, id, this.clientContext); 30 | } 31 | 32 | /** 33 | * Used to read, replace, or delete a specific, existing {@link UserDefinedFunction} by id. 34 | * 35 | * Use `.userDefinedFunctions` for creating new user defined functions, or querying/reading all user defined functions. 36 | * @param id The id of the {@link UserDefinedFunction}. 37 | */ 38 | public userDefinedFunction(id: string): UserDefinedFunction { 39 | return new UserDefinedFunction(this.container, id, this.clientContext); 40 | } 41 | 42 | private $sprocs: StoredProcedures; 43 | /** 44 | * Operations for creating new stored procedures, and reading/querying all stored procedures. 45 | * 46 | * For reading, replacing, or deleting an existing stored procedure, use `.storedProcedure(id)`. 47 | */ 48 | public get storedProcedures(): StoredProcedures { 49 | if (!this.$sprocs) { 50 | this.$sprocs = new StoredProcedures(this.container, this.clientContext); 51 | } 52 | return this.$sprocs; 53 | } 54 | 55 | private $triggers: Triggers; 56 | /** 57 | * Operations for creating new triggers, and reading/querying all triggers. 58 | * 59 | * For reading, replacing, or deleting an existing trigger, use `.trigger(id)`. 60 | */ 61 | public get triggers(): Triggers { 62 | if (!this.$triggers) { 63 | this.$triggers = new Triggers(this.container, this.clientContext); 64 | } 65 | return this.$triggers; 66 | } 67 | 68 | private $udfs: UserDefinedFunctions; 69 | /** 70 | * Operations for creating new user defined functions, and reading/querying all user defined functions. 71 | * 72 | * For reading, replacing, or deleting an existing user defined function, use `.userDefinedFunction(id)`. 73 | */ 74 | public get userDefinedFunctions(): UserDefinedFunctions { 75 | if (!this.$udfs) { 76 | this.$udfs = new UserDefinedFunctions(this.container, this.clientContext); 77 | } 78 | return this.$udfs; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/client/StoredProcedure/StoredProcedureDefinition.ts: -------------------------------------------------------------------------------- 1 | export interface StoredProcedureDefinition { 2 | /** 3 | * The id of the {@link StoredProcedure}. 4 | */ 5 | id?: string; 6 | /** 7 | * The body of the {@link StoredProcedure}. This is a JavaScript function. 8 | */ 9 | body?: string | ((...inputs: any[]) => void); 10 | } 11 | -------------------------------------------------------------------------------- /src/client/StoredProcedure/StoredProcedureResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request"; 3 | import { Resource } from "../Resource"; 4 | import { StoredProcedure } from "./StoredProcedure"; 5 | import { StoredProcedureDefinition } from "./StoredProcedureDefinition"; 6 | 7 | export class StoredProcedureResponse extends ResourceResponse { 8 | constructor( 9 | resource: StoredProcedureDefinition & Resource, 10 | headers: CosmosHeaders, 11 | statusCode: number, 12 | storedProcedure: StoredProcedure 13 | ) { 14 | super(resource, headers, statusCode); 15 | this.storedProcedure = storedProcedure; 16 | } 17 | /** 18 | * A reference to the {@link StoredProcedure} which the {@link StoredProcedureDefinition} corresponds to. 19 | */ 20 | public readonly storedProcedure: StoredProcedure; 21 | 22 | /** 23 | * Alias for storedProcedure. 24 | * 25 | * A reference to the {@link StoredProcedure} which the {@link StoredProcedureDefinition} corresponds to. 26 | */ 27 | public get sproc(): StoredProcedure { 28 | return this.storedProcedure; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/client/StoredProcedure/index.ts: -------------------------------------------------------------------------------- 1 | export { StoredProcedure } from "./StoredProcedure"; 2 | export { StoredProcedures } from "./StoredProcedures"; 3 | export { StoredProcedureDefinition } from "./StoredProcedureDefinition"; 4 | export { StoredProcedureResponse } from "./StoredProcedureResponse"; 5 | -------------------------------------------------------------------------------- /src/client/Trigger/Trigger.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../../ClientContext"; 2 | import { createTriggerUri, getIdFromLink, getPathFromLink, isResourceValid, ResourceType } from "../../common"; 3 | import { RequestOptions } from "../../request"; 4 | import { Container } from "../Container"; 5 | import { TriggerDefinition } from "./TriggerDefinition"; 6 | import { TriggerResponse } from "./TriggerResponse"; 7 | 8 | /** 9 | * Operations to read, replace, or delete a {@link Trigger}. 10 | * 11 | * Use `container.triggers` to create, upsert, query, or read all. 12 | */ 13 | export class Trigger { 14 | /** 15 | * Returns a reference URL to the resource. Used for linking in Permissions. 16 | */ 17 | public get url() { 18 | return createTriggerUri(this.container.database.id, this.container.id, this.id); 19 | } 20 | 21 | /** 22 | * @hidden 23 | * @param container The parent {@link Container}. 24 | * @param id The id of the given {@link Trigger}. 25 | */ 26 | constructor( 27 | public readonly container: Container, 28 | public readonly id: string, 29 | private readonly clientContext: ClientContext 30 | ) {} 31 | 32 | /** 33 | * Read the {@link TriggerDefinition} for the given {@link Trigger}. 34 | * @param options 35 | */ 36 | public async read(options?: RequestOptions): Promise { 37 | const path = getPathFromLink(this.url); 38 | const id = getIdFromLink(this.url); 39 | 40 | const response = await this.clientContext.read({ 41 | path, 42 | resourceType: ResourceType.trigger, 43 | resourceId: id, 44 | options 45 | }); 46 | return new TriggerResponse(response.result, response.headers, response.code, this); 47 | } 48 | 49 | /** 50 | * Replace the given {@link Trigger} with the specified {@link TriggerDefinition}. 51 | * @param body The specified {@link TriggerDefinition} to replace the existing definition with. 52 | * @param options 53 | */ 54 | public async replace(body: TriggerDefinition, options?: RequestOptions): Promise { 55 | if (body.body) { 56 | body.body = body.body.toString(); 57 | } 58 | 59 | const err = {}; 60 | if (!isResourceValid(body, err)) { 61 | throw err; 62 | } 63 | 64 | const path = getPathFromLink(this.url); 65 | const id = getIdFromLink(this.url); 66 | 67 | const response = await this.clientContext.replace({ 68 | body, 69 | path, 70 | resourceType: ResourceType.trigger, 71 | resourceId: id, 72 | options 73 | }); 74 | return new TriggerResponse(response.result, response.headers, response.code, this); 75 | } 76 | 77 | /** 78 | * Delete the given {@link Trigger}. 79 | * @param options 80 | */ 81 | public async delete(options?: RequestOptions): Promise { 82 | const path = getPathFromLink(this.url); 83 | const id = getIdFromLink(this.url); 84 | 85 | const response = await this.clientContext.delete({ 86 | path, 87 | resourceType: ResourceType.trigger, 88 | resourceId: id, 89 | options 90 | }); 91 | return new TriggerResponse(response.result, response.headers, response.code, this); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/client/Trigger/TriggerDefinition.ts: -------------------------------------------------------------------------------- 1 | import { TriggerOperation, TriggerType } from "../../documents"; 2 | 3 | export interface TriggerDefinition { 4 | /** The id of the trigger. */ 5 | id?: string; 6 | /** The body of the trigger, it can also be passed as a stringifed function */ 7 | body: (() => void) | string; 8 | /** The type of the trigger, should be one of the values of {@link TriggerType}. */ 9 | triggerType: TriggerType; 10 | /** The trigger operation, should be one of the values of {@link TriggerOperation}. */ 11 | triggerOperation: TriggerOperation; 12 | } 13 | -------------------------------------------------------------------------------- /src/client/Trigger/TriggerResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request"; 3 | import { Resource } from "../Resource"; 4 | import { Trigger } from "./index"; 5 | import { TriggerDefinition } from "./TriggerDefinition"; 6 | 7 | export class TriggerResponse extends ResourceResponse { 8 | constructor(resource: TriggerDefinition & Resource, headers: CosmosHeaders, statusCode: number, trigger: Trigger) { 9 | super(resource, headers, statusCode); 10 | this.trigger = trigger; 11 | } 12 | /** A reference to the {@link Trigger} corresponding to the returned {@link TriggerDefinition}. */ 13 | public readonly trigger: Trigger; 14 | } 15 | -------------------------------------------------------------------------------- /src/client/Trigger/index.ts: -------------------------------------------------------------------------------- 1 | export { Trigger } from "./Trigger"; 2 | export { Triggers } from "./Triggers"; 3 | export { TriggerDefinition } from "./TriggerDefinition"; 4 | export { TriggerResponse } from "./TriggerResponse"; 5 | -------------------------------------------------------------------------------- /src/client/User/UserDefinition.ts: -------------------------------------------------------------------------------- 1 | export interface UserDefinition { 2 | /** The id of the user. */ 3 | id?: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/client/User/UserResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request"; 3 | import { Resource } from "../Resource"; 4 | import { User } from "./User"; 5 | import { UserDefinition } from "./UserDefinition"; 6 | 7 | export class UserResponse extends ResourceResponse { 8 | constructor(resource: UserDefinition & Resource, headers: CosmosHeaders, statusCode: number, user: User) { 9 | super(resource, headers, statusCode); 10 | this.user = user; 11 | } 12 | /** A reference to the {@link User} corresponding to the returned {@link UserDefinition}. */ 13 | public readonly user: User; 14 | } 15 | -------------------------------------------------------------------------------- /src/client/User/index.ts: -------------------------------------------------------------------------------- 1 | export { User } from "./User"; 2 | export { Users } from "./Users"; 3 | export { UserDefinition } from "./UserDefinition"; 4 | export { UserResponse } from "./UserResponse"; 5 | -------------------------------------------------------------------------------- /src/client/UserDefinedFunction/UserDefinedFunction.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../../ClientContext"; 2 | import { 3 | createUserDefinedFunctionUri, 4 | getIdFromLink, 5 | getPathFromLink, 6 | isResourceValid, 7 | ResourceType 8 | } from "../../common"; 9 | import { RequestOptions } from "../../request"; 10 | import { Container } from "../Container"; 11 | import { UserDefinedFunctionDefinition } from "./UserDefinedFunctionDefinition"; 12 | import { UserDefinedFunctionResponse } from "./UserDefinedFunctionResponse"; 13 | 14 | /** 15 | * Used to read, replace, or delete a specified User Definied Function by id. 16 | * 17 | * @see {@link UserDefinedFunction} to create, upsert, query, read all User Defined Functions. 18 | */ 19 | export class UserDefinedFunction { 20 | /** 21 | * Returns a reference URL to the resource. Used for linking in Permissions. 22 | */ 23 | public get url() { 24 | return createUserDefinedFunctionUri(this.container.database.id, this.container.id, this.id); 25 | } 26 | /** 27 | * @hidden 28 | * @param container The parent {@link Container}. 29 | * @param id The id of the given {@link UserDefinedFunction}. 30 | */ 31 | constructor( 32 | public readonly container: Container, 33 | public readonly id: string, 34 | private readonly clientContext: ClientContext 35 | ) {} 36 | 37 | /** 38 | * Read the {@link UserDefinedFunctionDefinition} for the given {@link UserDefinedFunction}. 39 | * @param options 40 | */ 41 | public async read(options?: RequestOptions): Promise { 42 | const path = getPathFromLink(this.url); 43 | const id = getIdFromLink(this.url); 44 | 45 | const response = await this.clientContext.read({ 46 | path, 47 | resourceType: ResourceType.udf, 48 | resourceId: id, 49 | options 50 | }); 51 | return new UserDefinedFunctionResponse(response.result, response.headers, response.code, this); 52 | } 53 | 54 | /** 55 | * Replace the given {@link UserDefinedFunction} with the specified {@link UserDefinedFunctionDefinition}. 56 | * @param body The specified {@link UserDefinedFunctionDefinition}. 57 | * @param options 58 | */ 59 | public async replace( 60 | body: UserDefinedFunctionDefinition, 61 | options?: RequestOptions 62 | ): Promise { 63 | if (body.body) { 64 | body.body = body.body.toString(); 65 | } 66 | 67 | const err = {}; 68 | if (!isResourceValid(body, err)) { 69 | throw err; 70 | } 71 | 72 | const path = getPathFromLink(this.url); 73 | const id = getIdFromLink(this.url); 74 | 75 | const response = await this.clientContext.replace({ 76 | body, 77 | path, 78 | resourceType: ResourceType.udf, 79 | resourceId: id, 80 | options 81 | }); 82 | return new UserDefinedFunctionResponse(response.result, response.headers, response.code, this); 83 | } 84 | 85 | /** 86 | * Delete the given {@link UserDefined}. 87 | * @param options 88 | */ 89 | public async delete(options?: RequestOptions): Promise { 90 | const path = getPathFromLink(this.url); 91 | const id = getIdFromLink(this.url); 92 | 93 | const response = await this.clientContext.delete({ path, resourceType: ResourceType.udf, resourceId: id, options }); 94 | return new UserDefinedFunctionResponse(response.result, response.headers, response.code, this); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/client/UserDefinedFunction/UserDefinedFunctionDefinition.ts: -------------------------------------------------------------------------------- 1 | export interface UserDefinedFunctionDefinition { 2 | /** The id of the {@link UserDefinedFunction} */ 3 | id?: string; 4 | /** The body of the user defined function, it can also be passed as a stringifed function */ 5 | body?: string | (() => void); 6 | } 7 | -------------------------------------------------------------------------------- /src/client/UserDefinedFunction/UserDefinedFunctionResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../../queryExecutionContext"; 2 | import { ResourceResponse } from "../../request"; 3 | import { Resource } from "../Resource"; 4 | import { UserDefinedFunction } from "./UserDefinedFunction"; 5 | import { UserDefinedFunctionDefinition } from "./UserDefinedFunctionDefinition"; 6 | 7 | export class UserDefinedFunctionResponse extends ResourceResponse { 8 | constructor( 9 | resource: UserDefinedFunctionDefinition & Resource, 10 | headers: CosmosHeaders, 11 | statusCode: number, 12 | udf: UserDefinedFunction 13 | ) { 14 | super(resource, headers, statusCode); 15 | this.userDefinedFunction = udf; 16 | } 17 | /** A reference to the {@link UserDefinedFunction} corresponding to the returned {@link UserDefinedFunctionDefinition}. */ 18 | public readonly userDefinedFunction: UserDefinedFunction; 19 | /** 20 | * Alias for `userDefinedFunction(id). 21 | * 22 | * A reference to the {@link UserDefinedFunction} corresponding to the returned {@link UserDefinedFunctionDefinition}. 23 | */ 24 | public get udf(): UserDefinedFunction { 25 | return this.userDefinedFunction; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/client/UserDefinedFunction/index.ts: -------------------------------------------------------------------------------- 1 | export { UserDefinedFunction } from "./UserDefinedFunction"; 2 | export { UserDefinedFunctions } from "./UserDefinedFunctions"; 3 | export { UserDefinedFunctionDefinition } from "./UserDefinedFunctionDefinition"; 4 | export { UserDefinedFunctionResponse } from "./UserDefinedFunctionResponse"; 5 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Conflict"; 2 | export * from "./Container"; 3 | export * from "./Database"; 4 | export * from "./Item"; 5 | export * from "./Offer"; 6 | export * from "./Permission"; 7 | export * from "./StoredProcedure"; 8 | export * from "./Trigger"; 9 | export * from "./User"; 10 | export * from "./UserDefinedFunction"; 11 | export * from "./Resource"; 12 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants"; 2 | export * from "./helper"; 3 | export * from "./statusCodes"; 4 | export * from "./uriFactory"; 5 | export * from "./platform"; 6 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | import debugLib from "debug"; 2 | 3 | /** @hidden */ 4 | const cosmosLevelFilter = process.env.COSMOS_LOG_LEVEL || "warn|error"; 5 | 6 | /** @hidden */ 7 | const cosmosDebug = debugLib("cosmos"); 8 | 9 | /** @hidden */ 10 | type logLevel = "silly" | "debug" | "info" | "warn" | "error"; 11 | 12 | /** @hidden */ 13 | const levelLogger = (namespaceLogger: debugLib.Debugger, level: logLevel) => { 14 | return (message: string | { [key: string]: any }) => { 15 | if (cosmosLevelFilter.includes(level)) { 16 | namespaceLogger("[" + new Date().toISOString() + "][" + level + "]: %o", message); 17 | } 18 | }; 19 | }; 20 | 21 | /** @hidden */ 22 | export const logger = (namespace: string) => { 23 | const namespaceLogger = cosmosDebug.extend(namespace); 24 | return { 25 | silly: levelLogger(namespaceLogger, "silly"), 26 | debug: levelLogger(namespaceLogger, "debug"), 27 | info: levelLogger(namespaceLogger, "info"), 28 | warn: levelLogger(namespaceLogger, "warn"), 29 | error: levelLogger(namespaceLogger, "error") 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /src/common/partitionKeys.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_PARTITION_KEY_PATH = "/_partitionKey" as "/_partitionKey"; 2 | -------------------------------------------------------------------------------- /src/common/platform.ts: -------------------------------------------------------------------------------- 1 | import userAgent from "universal-user-agent"; 2 | import { Constants } from "./constants"; 3 | 4 | /** @hidden */ 5 | export function getPlatformDefaultHeaders(): { [key: string]: string } { 6 | const defaultHeaders: { [key: string]: string } = {}; 7 | defaultHeaders[Constants.HttpHeaders.UserAgent] = getUserAgent(); 8 | return defaultHeaders; 9 | } 10 | 11 | /** 12 | * @ignore 13 | */ 14 | export function getUserAgent() { 15 | return `${userAgent()} ${Constants.SDKName}/${Constants.SDKVersion}`; 16 | } 17 | -------------------------------------------------------------------------------- /src/common/statusCodes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | */ 4 | export const StatusCodes = { 5 | // Success 6 | Ok: 200, 7 | Created: 201, 8 | Accepted: 202, 9 | NoContent: 204, 10 | NotModified: 304, 11 | 12 | // Client error 13 | BadRequest: 400, 14 | Unauthorized: 401, 15 | Forbidden: 403, 16 | NotFound: 404, 17 | MethodNotAllowed: 405, 18 | RequestTimeout: 408, 19 | Conflict: 409, 20 | Gone: 410, 21 | PreconditionFailed: 412, 22 | RequestEntityTooLarge: 413, 23 | TooManyRequests: 429, 24 | RetryWith: 449, 25 | 26 | // Server Error 27 | InternalServerError: 500, 28 | ServiceUnavailable: 503, 29 | 30 | // Operation pause and cancel. These are FAKE status codes for QOS logging purpose only. 31 | OperationPaused: 1200, 32 | OperationCancelled: 1201 33 | }; 34 | 35 | /** 36 | * @ignore 37 | */ 38 | export const SubStatusCodes = { 39 | Unknown: 0, 40 | 41 | // 400: Bad Request Substatus 42 | CrossPartitionQueryNotServable: 1004, 43 | 44 | // 410: StatusCodeType_Gone: substatus 45 | PartitionKeyRangeGone: 1002, 46 | 47 | // 404: NotFound Substatus 48 | ReadSessionNotAvailable: 1002, 49 | 50 | // 403: Forbidden Substatus 51 | WriteForbidden: 3 52 | }; 53 | -------------------------------------------------------------------------------- /src/documents/ConnectionMode.ts: -------------------------------------------------------------------------------- 1 | /** Determines the connection behavior of the CosmosClient. Note, we currently only support Gateway Mode. */ 2 | export enum ConnectionMode { 3 | /** Gateway mode talks to a intermediate gateway which handles the direct communicationi with your individual partitions. */ 4 | Gateway = 0 5 | } 6 | -------------------------------------------------------------------------------- /src/documents/ConnectionPolicy.ts: -------------------------------------------------------------------------------- 1 | import { RetryOptions } from "../retry/retryOptions"; 2 | import { ConnectionMode } from "./ConnectionMode"; 3 | /** 4 | * Represents the Connection policy associated with a CosmosClient in the Azure Cosmos DB database service. 5 | */ 6 | export interface ConnectionPolicy { 7 | /** Determines which mode to connect to Cosmos with. (Currently only supports Gateway option) */ 8 | connectionMode?: ConnectionMode; 9 | /** Request timeout (time to wait for response from network peer). Represented in milliseconds. */ 10 | requestTimeout?: number; 11 | /** Flag to enable/disable automatic redirecting of requests based on read/write operations. */ 12 | enableEndpointDiscovery?: boolean; 13 | /** List of azure regions to be used as preferred locations for read requests. */ 14 | preferredLocations?: string[]; 15 | /** RetryOptions object which defines several configurable properties used during retry. */ 16 | retryOptions?: RetryOptions; 17 | /** 18 | * The flag that enables writes on any locations (regions) for geo-replicated database accounts in the Azure Cosmos DB service. 19 | * Default is `false`. 20 | */ 21 | useMultipleWriteLocations?: boolean; 22 | } 23 | 24 | /** 25 | * @ignore 26 | */ 27 | export const defaultConnectionPolicy = Object.freeze({ 28 | connectionMode: ConnectionMode.Gateway, 29 | requestTimeout: 60000, 30 | enableEndpointDiscovery: true, 31 | preferredLocations: [], 32 | retryOptions: {}, 33 | useMultipleWriteLocations: true 34 | }); 35 | -------------------------------------------------------------------------------- /src/documents/ConsistencyLevel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the consistency levels supported for Azure Cosmos DB client operations.
3 | * The requested ConsistencyLevel must match or be weaker than that provisioned for the database account. 4 | * Consistency levels. 5 | * 6 | * Consistency levels by order of strength are Strong, BoundedStaleness, Session, Consistent Prefix, and Eventual. 7 | * 8 | * See https://aka.ms/cosmos-consistency for more detailed documentation on Consistency Levels. 9 | */ 10 | export enum ConsistencyLevel { 11 | /** 12 | * Strong Consistency guarantees that read operations always return the value that was last written. 13 | */ 14 | Strong = "Strong", 15 | /** 16 | * Bounded Staleness guarantees that reads are not too out-of-date. 17 | * This can be configured based on number of operations (MaxStalenessPrefix) or time (MaxStalenessIntervalInSeconds). 18 | */ 19 | BoundedStaleness = "BoundedStaleness", 20 | /** 21 | * Session Consistency guarantees monotonic reads (you never read old data, then new, then old again), 22 | * monotonic writes (writes are ordered) and read your writes (your writes are immediately visible to your reads) 23 | * within any single session. 24 | */ 25 | Session = "Session", 26 | /** 27 | * Eventual Consistency guarantees that reads will return a subset of writes. 28 | * All writes will be eventually be available for reads. 29 | */ 30 | Eventual = "Eventual", 31 | /** 32 | * ConsistentPrefix Consistency guarantees that reads will return some prefix of all writes with no gaps. 33 | * All writes will be eventually be available for reads.` 34 | */ 35 | ConsistentPrefix = "ConsistentPrefix" 36 | } 37 | -------------------------------------------------------------------------------- /src/documents/DataType.ts: -------------------------------------------------------------------------------- 1 | /** Defines a target data type of an index path specification in the Azure Cosmos DB service. */ 2 | export enum DataType { 3 | /** Represents a numeric data type. */ 4 | Number = "Number", 5 | /** Represents a string data type. */ 6 | String = "String", 7 | /** Represents a point data type. */ 8 | Point = "Point", 9 | /** Represents a line string data type. */ 10 | LineString = "LineString", 11 | /** Represents a polygon data type. */ 12 | Polygon = "Polygon", 13 | /** Represents a multi-polygon data type. */ 14 | MultiPolygon = "MultiPolygon" 15 | } 16 | -------------------------------------------------------------------------------- /src/documents/DatabaseAccount.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "../common"; 2 | import { CosmosHeaders } from "../queryExecutionContext"; 3 | import { ConsistencyLevel } from "./ConsistencyLevel"; 4 | 5 | /** 6 | * Represents a DatabaseAccount in the Azure Cosmos DB database service. 7 | */ 8 | export class DatabaseAccount { 9 | /** The list of writable locations for a geo-replicated database account. */ 10 | public readonly writableLocations: Location[] = []; 11 | /** The list of readable locations for a geo-replicated database account. */ 12 | public readonly readableLocations: Location[] = []; 13 | /** The self-link for Databases in the databaseAccount. */ 14 | public readonly DatabasesLink: string; 15 | /** The self-link for Media in the databaseAccount. */ 16 | public readonly MediaLink: string; 17 | /** Attachment content (media) storage quota in MBs ( Retrieved from gateway ). */ 18 | public readonly MaxMediaStorageUsageInMB: number; 19 | /** 20 | * Current attachment content (media) usage in MBs (Retrieved from gateway ) 21 | * 22 | * Value is returned from cached information updated periodically and is not guaranteed 23 | * to be real time. 24 | */ 25 | public readonly CurrentMediaStorageUsageInMB: number; 26 | /** Gets the UserConsistencyPolicy settings. */ 27 | public readonly ConsistencyPolicy: ConsistencyLevel; 28 | public readonly enableMultipleWritableLocations: boolean; 29 | 30 | // TODO: body - any 31 | public constructor(body: { [key: string]: any }, headers: CosmosHeaders) { 32 | this.DatabasesLink = "/dbs/"; 33 | this.MediaLink = "/media/"; 34 | this.MaxMediaStorageUsageInMB = headers[Constants.HttpHeaders.MaxMediaStorageUsageInMB]; 35 | this.CurrentMediaStorageUsageInMB = headers[Constants.HttpHeaders.CurrentMediaStorageUsageInMB]; 36 | this.ConsistencyPolicy = body.UserConsistencyPolicy 37 | ? (body.UserConsistencyPolicy.defaultConsistencyLevel as ConsistencyLevel) 38 | : ConsistencyLevel.Session; 39 | if (body[Constants.WritableLocations] && body.id !== "localhost") { 40 | this.writableLocations = body[Constants.WritableLocations] as Location[]; 41 | } 42 | if (body[Constants.ReadableLocations] && body.id !== "localhost") { 43 | this.readableLocations = body[Constants.ReadableLocations] as Location[]; 44 | } 45 | if (body[Constants.ENABLE_MULTIPLE_WRITABLE_LOCATIONS]) { 46 | this.enableMultipleWritableLocations = 47 | body[Constants.ENABLE_MULTIPLE_WRITABLE_LOCATIONS] === true || 48 | body[Constants.ENABLE_MULTIPLE_WRITABLE_LOCATIONS] === "true"; 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Used to specify the locations that are available, read is index 1 and write is index 0. 55 | */ 56 | export interface Location { 57 | name: string; 58 | databaseAccountEndpoint: string; 59 | } 60 | -------------------------------------------------------------------------------- /src/documents/Document.ts: -------------------------------------------------------------------------------- 1 | export interface Document { 2 | [key: string]: any; 3 | } 4 | -------------------------------------------------------------------------------- /src/documents/IndexKind.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Specifies the supported Index types. 3 | */ 4 | export enum IndexKind { 5 | /** 6 | * This is supplied for a path which requires sorting. 7 | */ 8 | Range = "Range", 9 | /** 10 | * This is supplied for a path which requires geospatial indexing. 11 | */ 12 | Spatial = "Spatial" 13 | } 14 | -------------------------------------------------------------------------------- /src/documents/IndexingMode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Specifies the supported indexing modes. 3 | * @property Consistent 4 | * @property Lazy 5 | */ 6 | export enum IndexingMode { 7 | /** 8 | * Index is updated synchronously with a create or update operation. 9 | * 10 | * With consistent indexing, query behavior is the same as the default consistency level for the container. 11 | * The index is always kept up to date with the data. 12 | */ 13 | consistent = "consistent", 14 | /** 15 | * Index is updated asynchronously with respect to a create or update operation. 16 | * 17 | * With lazy indexing, queries are eventually consistent. The index is updated when the container is idle. 18 | */ 19 | lazy = "lazy", 20 | /** No Index is provided. */ 21 | none = "none" 22 | } 23 | -------------------------------------------------------------------------------- /src/documents/IndexingPolicy.ts: -------------------------------------------------------------------------------- 1 | import { DataType, IndexingMode, IndexKind } from "./index"; 2 | 3 | export interface IndexingPolicy { 4 | /** The indexing mode (consistent or lazy) {@link IndexingMode}. */ 5 | indexingMode?: keyof typeof IndexingMode; 6 | automatic?: boolean; 7 | /** An array of {@link IncludedPath} represents the paths to be included for indexing. */ 8 | includedPaths?: IndexedPath[]; 9 | /** An array of {@link IncludedPath} represents the paths to be excluded for indexing. */ 10 | excludedPaths?: IndexedPath[]; 11 | } 12 | 13 | export interface IndexedPath { 14 | path: string; 15 | indexes?: Index[]; 16 | } 17 | 18 | export interface Index { 19 | kind: keyof typeof IndexKind; 20 | dataType: keyof typeof DataType; 21 | precision?: number; 22 | } 23 | -------------------------------------------------------------------------------- /src/documents/PartitionKey.ts: -------------------------------------------------------------------------------- 1 | import { Point, Range } from "../range"; 2 | import { PartitionKeyDefinition } from "./PartitionKeyDefinition"; 3 | 4 | export type PartitionKey = PartitionKeyDefinition | Point | Range | {}; 5 | -------------------------------------------------------------------------------- /src/documents/PartitionKeyDefinition.ts: -------------------------------------------------------------------------------- 1 | export interface PartitionKeyDefinition { 2 | paths: string[]; 3 | version?: number; 4 | systemKey?: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/documents/PermissionMode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum for permission mode values. 3 | */ 4 | export enum PermissionMode { 5 | /** Permission not valid. */ 6 | None = "none", 7 | /** Permission applicable for read operations only. */ 8 | Read = "read", 9 | /** Permission applicable for all operations. */ 10 | All = "all" 11 | } 12 | -------------------------------------------------------------------------------- /src/documents/TriggerOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum for trigger operation values. 3 | * specifies the operations on which a trigger should be executed. 4 | */ 5 | export enum TriggerOperation { 6 | /** All operations. */ 7 | All = "all", 8 | /** Create operations only. */ 9 | Create = "create", 10 | /** Update operations only. */ 11 | Update = "update", 12 | /** Delete operations only. */ 13 | Delete = "delete", 14 | /** Replace operations only. */ 15 | Replace = "replace" 16 | } 17 | -------------------------------------------------------------------------------- /src/documents/TriggerType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum for trigger type values. 3 | * Specifies the type of the trigger. 4 | */ 5 | export enum TriggerType { 6 | /** Trigger should be executed before the associated operation(s). */ 7 | Pre = "pre", 8 | /** Trigger should be executed after the associated operation(s). */ 9 | Post = "post" 10 | } 11 | -------------------------------------------------------------------------------- /src/documents/UserDefinedFunctionType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum for udf type values. 3 | * Specifies the types of user defined functions. 4 | */ 5 | export enum UserDefinedFunctionType { 6 | /** The User Defined Function is written in JavaScript. This is currently the only option. */ 7 | Javascript = "Javascript" 8 | } 9 | -------------------------------------------------------------------------------- /src/documents/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ConnectionMode"; 2 | export * from "./ConnectionPolicy"; 3 | export * from "./ConsistencyLevel"; 4 | export * from "./DatabaseAccount"; 5 | export * from "./DataType"; 6 | export * from "./Document"; 7 | export * from "./IndexingMode"; 8 | export * from "./IndexingPolicy"; 9 | export * from "./IndexKind"; 10 | export * from "./PartitionKey"; 11 | export * from "./PartitionKeyDefinition"; 12 | export * from "./PermissionMode"; 13 | export * from "./TriggerOperation"; 14 | export * from "./TriggerType"; 15 | export * from "./UserDefinedFunctionType"; 16 | -------------------------------------------------------------------------------- /src/extractPartitionKey.ts: -------------------------------------------------------------------------------- 1 | import { parsePath } from "./common"; 2 | import { PartitionKey, PartitionKeyDefinition } from "./documents"; 3 | 4 | /** 5 | * @ignore 6 | * @param document 7 | * @param partitionKeyDefinition 8 | */ 9 | export function extractPartitionKey(document: any, partitionKeyDefinition: PartitionKeyDefinition): PartitionKey[] { 10 | if (partitionKeyDefinition && partitionKeyDefinition.paths && partitionKeyDefinition.paths.length > 0) { 11 | const partitionKey: PartitionKey[] = []; 12 | partitionKeyDefinition.paths.forEach((path: string) => { 13 | const pathParts = parsePath(path); 14 | let obj = document; 15 | for (const part of pathParts) { 16 | if (!(typeof obj === "object" && part in obj)) { 17 | obj = undefined; 18 | break; 19 | } 20 | obj = obj[part]; 21 | } 22 | partitionKey.push(obj); 23 | }); 24 | if (partitionKey.length === 1 && partitionKey[0] === undefined) { 25 | return undefinedPartitionKey(partitionKeyDefinition); 26 | } 27 | return partitionKey; 28 | } 29 | } 30 | /** 31 | * @ignore 32 | * @param partitionKeyDefinition 33 | */ 34 | export function undefinedPartitionKey(partitionKeyDefinition: PartitionKeyDefinition) { 35 | if (partitionKeyDefinition.systemKey === true) { 36 | return []; 37 | } else { 38 | return [{}]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { DEFAULT_PARTITION_KEY_PATH } from "./common/partitionKeys"; 2 | export { StatusCodes } from "./common"; 3 | export { extractPartitionKey } from "./extractPartitionKey"; 4 | export { setAuthorizationTokenHeaderUsingMasterKey } from "./auth"; 5 | export { 6 | ConnectionMode, 7 | ConsistencyLevel, 8 | ConnectionPolicy, 9 | DatabaseAccount, 10 | DataType, 11 | Index, 12 | IndexedPath, 13 | IndexingMode, 14 | IndexingPolicy, 15 | IndexKind, 16 | Location, 17 | PartitionKey, 18 | PartitionKeyDefinition, 19 | PermissionMode, 20 | TriggerOperation, 21 | TriggerType, 22 | UserDefinedFunctionType 23 | } from "./documents"; 24 | 25 | export { UniqueKeyPolicy, UniqueKey } from "./client/Container/UniqueKeyPolicy"; 26 | export { Constants, OperationType } from "./common"; 27 | export { RetryOptions } from "./retry"; 28 | export { Response, RequestOptions, FeedOptions, ErrorResponse, ResourceResponse } from "./request"; 29 | export { FeedResponse } from "./request/FeedResponse"; 30 | export { RequestContext } from "./request/RequestContext"; 31 | export { CosmosHeaders, SqlParameter, SqlQuerySpec } from "./queryExecutionContext"; 32 | export { QueryIterator } from "./queryIterator"; 33 | export * from "./queryMetrics"; 34 | export { CosmosClient } from "./CosmosClient"; 35 | export { CosmosClientOptions } from "./CosmosClientOptions"; 36 | export * from "./client"; 37 | export { Next, Plugin, PluginConfig, PluginOn } from "./plugins/Plugin"; 38 | -------------------------------------------------------------------------------- /src/plugins/Plugin.ts: -------------------------------------------------------------------------------- 1 | import { RequestContext } from "../request/RequestContext"; 2 | import { Response } from "../request/Response"; 3 | 4 | /** 5 | * Used to specify which type of events to execute this plug in on. 6 | * 7 | * @ignore 8 | */ 9 | export enum PluginOn { 10 | /** 11 | * Will be executed per network request 12 | */ 13 | request = "request", 14 | /** 15 | * Will be executed per API operation 16 | */ 17 | operation = "operation" 18 | } 19 | 20 | /** 21 | * Specifies which event to run for the specified plugin 22 | * 23 | * @ignore 24 | */ 25 | export interface PluginConfig { 26 | /** 27 | * The event to run the plugin on 28 | */ 29 | on: keyof typeof PluginOn; 30 | /** 31 | * The plugin to run 32 | */ 33 | plugin: Plugin; 34 | } 35 | 36 | /** 37 | * Plugins allow you to customize the behavior of the SDk with additional logging, retry, or additional functionality. 38 | * 39 | * A plugin is a function which returns a Promise>, and is passed a RequestContext and Next object. 40 | * 41 | * Next is a function which takes in requestContext returns a promise. You must await/then that promise which will contain the response from further plugins, 42 | * allowing you to log those results or handle errors. 43 | * 44 | * RequestContext is an object which controls what operation is happening, against which endpoint, and more. Modifying this and passing it along via next is how 45 | * you modify future SDK behavior. 46 | * 47 | * @ignore 48 | */ 49 | export type Plugin = (context: RequestContext, next: Next) => Promise>; 50 | 51 | /** 52 | * Next is a function which takes in requestContext returns a promise. You must await/then that promise which will contain the response from further plugins, 53 | * allowing you to log those results or handle errors. 54 | * @ignore 55 | */ 56 | export type Next = (context: RequestContext) => Promise>; 57 | 58 | /** 59 | * @internal 60 | * @hidden 61 | * @ignore 62 | * @param requestContext 63 | * @param next 64 | * @param on 65 | */ 66 | export async function executePlugins( 67 | requestContext: RequestContext, 68 | next: Plugin, 69 | on: PluginOn 70 | ): Promise> { 71 | if (!requestContext.plugins) { 72 | return next(requestContext, undefined); 73 | } 74 | let level = 0; 75 | const _: Next = (inner: RequestContext): Promise> => { 76 | if (++level >= inner.plugins.length) { 77 | return next(requestContext, undefined); 78 | } else if (inner.plugins[level].on !== on) { 79 | return _(requestContext); 80 | } else { 81 | return inner.plugins[level].plugin(inner, _); 82 | } 83 | }; 84 | if (requestContext.plugins[level].on !== on) { 85 | return _(requestContext); 86 | } else { 87 | return requestContext.plugins[level].plugin(requestContext, _); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/queryExecutionContext/Aggregators/AverageAggregator.ts: -------------------------------------------------------------------------------- 1 | import { IAggregator } from "./IAggregator"; 2 | 3 | /** @hidden */ 4 | export interface IAverageAggregator { 5 | sum: number; 6 | count: number; 7 | } 8 | 9 | /** @hidden */ 10 | export class AverageAggregator implements IAverageAggregator, IAggregator { 11 | public sum: number; 12 | public count: number; 13 | /** 14 | * Add the provided item to aggregation result. 15 | * @memberof AverageAggregator 16 | * @instance 17 | * @param other 18 | */ 19 | public aggregate(other: IAverageAggregator) { 20 | if (other == null || other.sum == null) { 21 | return; 22 | } 23 | if (this.sum == null) { 24 | this.sum = 0.0; 25 | this.count = 0; 26 | } 27 | this.sum += other.sum; 28 | this.count += other.count; 29 | } 30 | 31 | /** 32 | * Get the aggregation result. 33 | * @memberof AverageAggregator 34 | * @instance 35 | */ 36 | public getResult() { 37 | if (this.sum == null || this.count <= 0) { 38 | return undefined; 39 | } 40 | return this.sum / this.count; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/queryExecutionContext/Aggregators/CountAggregator.ts: -------------------------------------------------------------------------------- 1 | import { IAggregator } from "./IAggregator"; 2 | 3 | /** @hidden */ 4 | export class CountAggregator implements IAggregator { 5 | public value: number; 6 | /** 7 | * Represents an aggregator for COUNT operator. 8 | * @constructor CountAggregator 9 | * @ignore 10 | */ 11 | constructor() { 12 | this.value = 0; 13 | } 14 | /** 15 | * Add the provided item to aggregation result. 16 | * @memberof CountAggregator 17 | * @instance 18 | * @param other 19 | */ 20 | public aggregate(other: number) { 21 | this.value += other; 22 | } 23 | 24 | /** 25 | * Get the aggregation result. 26 | * @memberof CountAggregator 27 | * @instance 28 | */ 29 | public getResult() { 30 | return this.value; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/queryExecutionContext/Aggregators/IAggregator.ts: -------------------------------------------------------------------------------- 1 | /** @hidden */ 2 | export interface IAggregator { 3 | aggregate: (other: T) => void; 4 | getResult: () => number; 5 | } 6 | -------------------------------------------------------------------------------- /src/queryExecutionContext/Aggregators/MaxAggregator.ts: -------------------------------------------------------------------------------- 1 | import { OrderByDocumentProducerComparator } from "../orderByDocumentProducerComparator"; 2 | import { IAggregator } from "./IAggregator"; 3 | 4 | /** @hidden */ 5 | export class MaxAggregator implements IAggregator { 6 | private value: number; 7 | private comparer: OrderByDocumentProducerComparator; 8 | /** 9 | * Represents an aggregator for MAX operator. 10 | * @constructor MaxAggregator 11 | * @ignore 12 | */ 13 | constructor() { 14 | this.value = undefined; 15 | this.comparer = new OrderByDocumentProducerComparator(["Ascending"]); 16 | } 17 | /** 18 | * Add the provided item to aggregation result. 19 | * @memberof MaxAggregator 20 | * @instance 21 | * @param other 22 | */ 23 | public aggregate(other: number) { 24 | if (this.value === undefined) { 25 | this.value = other; 26 | } else if (this.comparer.compareValue(other, typeof other, this.value, typeof this.value) > 0) { 27 | this.value = other; 28 | } 29 | } 30 | 31 | /** 32 | * Get the aggregation result. 33 | * @memberof MaxAggregator 34 | * @instance 35 | */ 36 | public getResult() { 37 | return this.value; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/queryExecutionContext/Aggregators/MinAggregator.ts: -------------------------------------------------------------------------------- 1 | import { OrderByDocumentProducerComparator } from "../orderByDocumentProducerComparator"; 2 | import { IAggregator } from "./IAggregator"; 3 | 4 | /** @hidden */ 5 | export class MinAggregator implements IAggregator { 6 | private value: number; 7 | private comparer: OrderByDocumentProducerComparator; 8 | /** 9 | * Represents an aggregator for MIN operator. 10 | * @constructor MinAggregator 11 | * @ignore 12 | */ 13 | constructor() { 14 | this.value = undefined; 15 | this.comparer = new OrderByDocumentProducerComparator(["Ascending"]); 16 | } 17 | /** 18 | * Add the provided item to aggregation result. 19 | * @memberof MinAggregator 20 | * @instance 21 | * @param other 22 | */ 23 | public aggregate(other: number) { 24 | if (this.value === undefined) { 25 | // || typeof this.value === "object" 26 | this.value = other; 27 | } else { 28 | const otherType = other === null ? "NoValue" : typeof other; // || typeof other === "object" 29 | const thisType = this.value === null ? "NoValue" : typeof this.value; 30 | if (this.comparer.compareValue(other, otherType, this.value, thisType) < 0) { 31 | this.value = other; 32 | } 33 | } 34 | } 35 | 36 | /** 37 | * Get the aggregation result. 38 | * @memberof MinAggregator 39 | * @instance 40 | */ 41 | public getResult() { 42 | return this.value; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/queryExecutionContext/Aggregators/SumAggregator.ts: -------------------------------------------------------------------------------- 1 | import { IAggregator } from "./IAggregator"; 2 | 3 | /** @hidden */ 4 | export class SumAggregator implements IAggregator { 5 | public sum: number; 6 | /** 7 | * Add the provided item to aggregation result. 8 | * @memberof SumAggregator 9 | * @instance 10 | * @param other 11 | */ 12 | public aggregate(other: number) { 13 | if (other === undefined) { 14 | return; 15 | } 16 | if (this.sum === undefined) { 17 | this.sum = other; 18 | } else { 19 | this.sum += other; 20 | } 21 | } 22 | 23 | /** 24 | * Get the aggregation result. 25 | * @memberof SumAggregator 26 | * @instance 27 | */ 28 | public getResult() { 29 | return this.sum; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/queryExecutionContext/Aggregators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AverageAggregator"; 2 | export * from "./CountAggregator"; 3 | export * from "./MaxAggregator"; 4 | export * from "./MinAggregator"; 5 | export * from "./SumAggregator"; 6 | export * from "./IAggregator"; 7 | -------------------------------------------------------------------------------- /src/queryExecutionContext/CosmosHeaders.ts: -------------------------------------------------------------------------------- 1 | export interface CosmosHeaders { 2 | [key: string]: string | boolean | number; 3 | } 4 | -------------------------------------------------------------------------------- /src/queryExecutionContext/EndpointComponent/OffsetLimitEndpointComponent.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "../../request"; 2 | import { ExecutionContext } from "../ExecutionContext"; 3 | import { getInitialHeader, mergeHeaders } from "../headerUtils"; 4 | 5 | /** @hidden */ 6 | export class OffsetLimitEndpointComponent implements ExecutionContext { 7 | constructor(private executionContext: ExecutionContext, private offset: number, private limit: number) {} 8 | 9 | public async nextItem(): Promise> { 10 | const aggregateHeaders = getInitialHeader(); 11 | while (this.offset > 0) { 12 | // Grab next item but ignore the result. We only need the headers 13 | const { headers } = await this.executionContext.nextItem(); 14 | this.offset--; 15 | mergeHeaders(aggregateHeaders, headers); 16 | } 17 | if (this.limit > 0) { 18 | const { result, headers } = await this.executionContext.nextItem(); 19 | this.limit--; 20 | mergeHeaders(aggregateHeaders, headers); 21 | return { result, headers: aggregateHeaders }; 22 | } 23 | // If both limit and offset are 0, return nothing 24 | return { result: undefined, headers: getInitialHeader() }; 25 | } 26 | 27 | public async current(): Promise> { 28 | if (this.offset > 0) { 29 | const current = await this.executionContext.current(); 30 | return { result: undefined, headers: current.headers }; 31 | } 32 | return this.executionContext.current(); 33 | } 34 | 35 | public hasMoreResults() { 36 | return (this.offset > 0 || this.limit > 0) && this.executionContext.hasMoreResults(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/queryExecutionContext/EndpointComponent/OrderByEndpointComponent.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "../../request"; 2 | import { ExecutionContext } from "../ExecutionContext"; 3 | 4 | /** @hidden */ 5 | export class OrderByEndpointComponent implements ExecutionContext { 6 | /** 7 | * Represents an endpoint in handling an order by query. For each processed orderby \ 8 | * result it returns 'payload' item of the result 9 | * @constructor OrderByEndpointComponent 10 | * @param {object} executionContext - Underlying Execution Context 11 | * @ignore 12 | */ 13 | constructor(private executionContext: ExecutionContext) {} 14 | /** 15 | * Execute a provided function on the next element in the OrderByEndpointComponent. 16 | * @memberof OrderByEndpointComponent 17 | * @instance 18 | */ 19 | public async nextItem(): Promise> { 20 | const { result: item, headers } = await this.executionContext.nextItem(); 21 | return { 22 | result: item !== undefined ? item.payload : undefined, 23 | headers 24 | }; 25 | } 26 | 27 | /** 28 | * Retrieve the current element on the OrderByEndpointComponent. 29 | * @memberof OrderByEndpointComponent 30 | * @instance 31 | */ 32 | public async current(): Promise> { 33 | const { result: item, headers } = await this.executionContext.current(); 34 | return { 35 | result: item !== undefined ? item.payload : undefined, 36 | headers 37 | }; 38 | } 39 | 40 | /** 41 | * Determine if there are still remaining resources to processs. 42 | * @memberof OrderByEndpointComponent 43 | * @instance 44 | * @returns {Boolean} true if there is other elements to process in the OrderByEndpointComponent. 45 | */ 46 | public hasMoreResults() { 47 | return this.executionContext.hasMoreResults(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/queryExecutionContext/EndpointComponent/OrderedDistinctEndpointComponent.ts: -------------------------------------------------------------------------------- 1 | import { sha1 } from "crypto-hash"; 2 | import stableStringify from "fast-json-stable-stringify"; 3 | import { Response } from "../../request"; 4 | import { ExecutionContext } from "../ExecutionContext"; 5 | 6 | /** @hidden */ 7 | export class OrderedDistinctEndpointComponent implements ExecutionContext { 8 | private hashedLastResult: string; 9 | constructor(private executionContext: ExecutionContext) {} 10 | 11 | public async nextItem(): Promise> { 12 | const { headers, result } = await this.executionContext.nextItem(); 13 | if (result) { 14 | const stringifiedResult = stableStringify(result); 15 | const hashedResult = await sha1(stringifiedResult); 16 | if (hashedResult === this.hashedLastResult) { 17 | return { result: undefined, headers }; 18 | } 19 | this.hashedLastResult = hashedResult; 20 | } 21 | return { result, headers }; 22 | } 23 | 24 | public async current(): Promise> { 25 | return this.executionContext.current(); 26 | } 27 | 28 | public hasMoreResults() { 29 | return this.executionContext.hasMoreResults(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/queryExecutionContext/EndpointComponent/UnorderedDistinctEndpointComponent.ts: -------------------------------------------------------------------------------- 1 | import { sha1 } from "crypto-hash"; 2 | import stableStringify from "fast-json-stable-stringify"; 3 | import { Response } from "../../request"; 4 | import { ExecutionContext } from "../ExecutionContext"; 5 | 6 | /** @hidden */ 7 | export class UnorderedDistinctEndpointComponent implements ExecutionContext { 8 | private hashedResults: Set; 9 | constructor(private executionContext: ExecutionContext) { 10 | this.hashedResults = new Set(); 11 | } 12 | 13 | public async nextItem(): Promise> { 14 | const { headers, result } = await this.executionContext.nextItem(); 15 | if (result) { 16 | const stringifiedResult = stableStringify(result); 17 | const hashedResult = await sha1(stringifiedResult); 18 | if (this.hashedResults.has(hashedResult)) { 19 | return { result: undefined, headers }; 20 | } 21 | this.hashedResults.add(hashedResult); 22 | } 23 | return { result, headers }; 24 | } 25 | 26 | public async current(): Promise> { 27 | return this.executionContext.current(); 28 | } 29 | 30 | public hasMoreResults() { 31 | return this.executionContext.hasMoreResults(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/queryExecutionContext/ExecutionContext.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "../request"; 2 | 3 | /** @hidden */ 4 | export interface ExecutionContext { 5 | nextItem: () => Promise>; 6 | current: () => Promise>; 7 | hasMoreResults: () => boolean; 8 | fetchMore?: () => Promise>; // TODO: code smell 9 | } 10 | -------------------------------------------------------------------------------- /src/queryExecutionContext/FetchResult.ts: -------------------------------------------------------------------------------- 1 | /** @hidden */ 2 | export enum FetchResultType { 3 | "Done" = 0, 4 | "Exception" = 1, 5 | "Result" = 2 6 | } 7 | 8 | /** @hidden */ 9 | export class FetchResult { 10 | public feedResponse: any; 11 | public fetchResultType: FetchResultType; 12 | public error: any; 13 | /** 14 | * Wraps fetch results for the document producer. 15 | * This allows the document producer to buffer exceptions so that actual results don't get flushed during splits. 16 | * @constructor DocumentProducer 17 | * @param {object} feedReponse - The response the document producer got back on a successful fetch 18 | * @param {object} error - The exception meant to be buffered on an unsuccessful fetch 19 | * @ignore 20 | */ 21 | constructor(feedResponse: any, error: any) { 22 | // TODO: feedResponse/error 23 | if (feedResponse) { 24 | this.feedResponse = feedResponse; 25 | this.fetchResultType = FetchResultType.Result; 26 | } else { 27 | this.error = error; 28 | this.fetchResultType = FetchResultType.Exception; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/queryExecutionContext/SqlQuerySpec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a SQL query in the Azure Cosmos DB service. 3 | * 4 | * Queries with inputs should be parameterized to protect against SQL injection. 5 | * 6 | * @example Parameterized SQL Query 7 | * ```typescript 8 | * const query: SqlQuerySpec = { 9 | * query: "SELECT * FROM Families f where f.lastName = @lastName", 10 | * parameters: [ 11 | * {name: "@lastName", value: "Wakefield"} 12 | * ] 13 | * }; 14 | * ``` 15 | */ 16 | export interface SqlQuerySpec { 17 | /** The text of the SQL query */ 18 | query: string; 19 | /** The parameters you provide in the query */ 20 | parameters?: SqlParameter[]; 21 | } 22 | 23 | /** 24 | * Represents a parameter in a Parameterized SQL query, specified in {@link SqlQuerySpec} 25 | */ 26 | export interface SqlParameter { 27 | /** Name of the parameter. (i.e. "@lastName") */ 28 | name: string; 29 | /** Value of the parameter (this is safe to come from users, assuming they are authorized) */ 30 | value: JSONValue; 31 | } 32 | 33 | export type JSONValue = boolean | number | string | null | JSONArray | JSONObject; 34 | export interface JSONObject { 35 | [key: string]: JSONValue; 36 | } 37 | export interface JSONArray extends Array {} 38 | -------------------------------------------------------------------------------- /src/queryExecutionContext/headerUtils.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "../common"; 2 | import { QueryMetrics } from "../queryMetrics"; 3 | 4 | export interface CosmosHeaders { 5 | [key: string]: any; 6 | } 7 | 8 | /** @hidden */ 9 | // TODO: docs 10 | export function getRequestChargeIfAny(headers: CosmosHeaders | number): number { 11 | if (typeof headers === "number") { 12 | return headers; 13 | } else if (typeof headers === "string") { 14 | return parseFloat(headers); 15 | } 16 | 17 | if (headers) { 18 | const rc = headers[Constants.HttpHeaders.RequestCharge]; 19 | if (rc) { 20 | return parseFloat(rc as string); 21 | } else { 22 | return 0; 23 | } 24 | } else { 25 | return 0; 26 | } 27 | } 28 | 29 | /** 30 | * @ignore 31 | */ 32 | export function getInitialHeader(): CosmosHeaders { 33 | const headers: CosmosHeaders = {}; 34 | headers[Constants.HttpHeaders.RequestCharge] = 0; 35 | headers[Constants.HttpHeaders.QueryMetrics] = {}; 36 | return headers; 37 | } 38 | 39 | /** 40 | * @ignore 41 | * @param headers 42 | * @param toBeMergedHeaders 43 | */ 44 | // TODO: The name of this method isn't very accurate to what it does 45 | export function mergeHeaders(headers: CosmosHeaders, toBeMergedHeaders: CosmosHeaders) { 46 | if (headers[Constants.HttpHeaders.RequestCharge] === undefined) { 47 | headers[Constants.HttpHeaders.RequestCharge] = 0; 48 | } 49 | 50 | if (headers[Constants.HttpHeaders.QueryMetrics] === undefined) { 51 | headers[Constants.HttpHeaders.QueryMetrics] = QueryMetrics.zero; 52 | } 53 | 54 | if (!toBeMergedHeaders) { 55 | return; 56 | } 57 | 58 | headers[Constants.HttpHeaders.RequestCharge] += getRequestChargeIfAny(toBeMergedHeaders); 59 | if (toBeMergedHeaders[Constants.HttpHeaders.IsRUPerMinuteUsed]) { 60 | headers[Constants.HttpHeaders.IsRUPerMinuteUsed] = toBeMergedHeaders[Constants.HttpHeaders.IsRUPerMinuteUsed]; 61 | } 62 | 63 | if (Constants.HttpHeaders.QueryMetrics in toBeMergedHeaders) { 64 | const headerQueryMetrics = headers[Constants.HttpHeaders.QueryMetrics]; 65 | const toBeMergedHeaderQueryMetrics = toBeMergedHeaders[Constants.HttpHeaders.QueryMetrics]; 66 | 67 | for (const partitionId in toBeMergedHeaderQueryMetrics) { 68 | if (partitionId in headerQueryMetrics) { 69 | const combinedQueryMetrics = headerQueryMetrics[partitionId].add([toBeMergedHeaderQueryMetrics[partitionId]]); 70 | headerQueryMetrics[partitionId] = combinedQueryMetrics; 71 | } else { 72 | headerQueryMetrics[partitionId] = toBeMergedHeaderQueryMetrics[partitionId]; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/queryExecutionContext/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./headerUtils"; 2 | export * from "./SqlQuerySpec"; 3 | export * from "./defaultQueryExecutionContext"; 4 | export * from "./Aggregators"; 5 | export * from "./documentProducer"; 6 | export * from "./FetchResult"; 7 | export * from "./orderByDocumentProducerComparator"; 8 | export * from "./ExecutionContext"; 9 | export * from "./parallelQueryExecutionContextBase"; 10 | export * from "./parallelQueryExecutionContext"; 11 | export * from "./orderByQueryExecutionContext"; 12 | export * from "./pipelinedQueryExecutionContext"; 13 | -------------------------------------------------------------------------------- /src/queryExecutionContext/orderByQueryExecutionContext.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../ClientContext"; 2 | import { PartitionedQueryExecutionInfo } from "../request/ErrorResponse"; 3 | import { DocumentProducer } from "./documentProducer"; 4 | import { ExecutionContext } from "./ExecutionContext"; 5 | import { OrderByDocumentProducerComparator } from "./orderByDocumentProducerComparator"; 6 | import { ParallelQueryExecutionContextBase } from "./parallelQueryExecutionContextBase"; 7 | 8 | /** @hidden */ 9 | export class OrderByQueryExecutionContext extends ParallelQueryExecutionContextBase implements ExecutionContext { 10 | private orderByComparator: any; 11 | /** 12 | * Provides the OrderByQueryExecutionContext. 13 | * This class is capable of handling orderby queries and dervives from ParallelQueryExecutionContextBase. 14 | * 15 | * When handling a parallelized query, it instantiates one instance of 16 | * DocumentProcuder per target partition key range and aggregates the result of each. 17 | * 18 | * @constructor ParallelQueryExecutionContext 19 | * @param {ClientContext} clientContext - The service endpoint to use to create the client. 20 | * @param {string} collectionLink - The Collection Link 21 | * @param {FeedOptions} [options] - Represents the feed options. 22 | * @param {object} partitionedQueryExecutionInfo - PartitionedQueryExecutionInfo 23 | * @ignore 24 | */ 25 | constructor( 26 | clientContext: ClientContext, 27 | collectionLink: string, 28 | query: any, // TODO: any query 29 | options: any, // TODO: any options 30 | partitionedQueryExecutionInfo: PartitionedQueryExecutionInfo 31 | ) { 32 | // Calling on base class constructor 33 | super(clientContext, collectionLink, query, options, partitionedQueryExecutionInfo); 34 | this.orderByComparator = new OrderByDocumentProducerComparator(this.sortOrders); 35 | } 36 | // Instance members are inherited 37 | 38 | // Overriding documentProducerComparator for OrderByQueryExecutionContexts 39 | /** 40 | * Provides a Comparator for document producers which respects orderby sort order. 41 | * @returns {object} - Comparator Function 42 | * @ignore 43 | */ 44 | public documentProducerComparator(docProd1: DocumentProducer, docProd2: DocumentProducer) { 45 | return this.orderByComparator.compare(docProd1, docProd2); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/queryExecutionContext/parallelQueryExecutionContext.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../ClientContext"; 2 | import { PartitionedQueryExecutionInfo } from "../request/ErrorResponse"; 3 | import { DocumentProducer } from "./documentProducer"; 4 | import { ExecutionContext } from "./ExecutionContext"; 5 | import { ParallelQueryExecutionContextBase } from "./parallelQueryExecutionContextBase"; 6 | 7 | /** @hidden */ 8 | export class ParallelQueryExecutionContext extends ParallelQueryExecutionContextBase implements ExecutionContext { 9 | /** 10 | * Provides the ParallelQueryExecutionContext. 11 | * This class is capable of handling parallelized queries and dervives from ParallelQueryExecutionContextBase. 12 | * 13 | * @constructor ParallelQueryExecutionContext 14 | * @param {ClientContext} clientContext - The service endpoint to use to create the client. 15 | * @param {string} collectionLink - The Collection Link 16 | * @param {FeedOptions} [options] - Represents the feed options. 17 | * @param {object} partitionedQueryExecutionInfo - PartitionedQueryExecutionInfo 18 | * @ignore 19 | */ 20 | constructor( 21 | clientContext: ClientContext, 22 | collectionLink: string, 23 | query: any, 24 | options: any, 25 | partitionedQueryExecutionInfo: PartitionedQueryExecutionInfo 26 | ) { 27 | // Calling on base class constructor 28 | super(clientContext, collectionLink, query, options, partitionedQueryExecutionInfo); 29 | } 30 | // Instance members are inherited 31 | 32 | // Overriding documentProducerComparator for ParallelQueryExecutionContexts 33 | /** 34 | * Provides a Comparator for document producers using the min value of the corresponding target partition. 35 | * @returns {object} - Comparator Function 36 | * @ignore 37 | */ 38 | public documentProducerComparator(docProd1: DocumentProducer, docProd2: DocumentProducer) { 39 | return docProd1.generation - docProd2.generation; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/queryMetrics/clientSideMetrics.ts: -------------------------------------------------------------------------------- 1 | import { getRequestChargeIfAny } from "../queryExecutionContext"; 2 | 3 | export class ClientSideMetrics { 4 | constructor(public readonly requestCharge: number) {} 5 | 6 | /** 7 | * Adds one or more ClientSideMetrics to a copy of this instance and returns the result. 8 | */ 9 | public add(...clientSideMetricsArray: ClientSideMetrics[]) { 10 | let requestCharge = this.requestCharge; 11 | for (const clientSideMetrics of clientSideMetricsArray) { 12 | if (clientSideMetrics == null) { 13 | throw new Error("clientSideMetrics has null or undefined item(s)"); 14 | } 15 | 16 | requestCharge += getRequestChargeIfAny(clientSideMetrics.requestCharge); 17 | } 18 | 19 | return new ClientSideMetrics(requestCharge); 20 | } 21 | 22 | public static readonly zero = new ClientSideMetrics(0); 23 | 24 | public static createFromArray(...clientSideMetricsArray: ClientSideMetrics[]) { 25 | if (clientSideMetricsArray == null) { 26 | throw new Error("clientSideMetricsArray is null or undefined item(s)"); 27 | } 28 | 29 | return this.zero.add(...clientSideMetricsArray); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/queryMetrics/index.ts: -------------------------------------------------------------------------------- 1 | export { ClientSideMetrics } from "./clientSideMetrics"; 2 | export { QueryMetrics } from "./queryMetrics"; 3 | export { default as QueryMetricsConstants } from "./queryMetricsConstants"; 4 | export { QueryPreparationTimes } from "./queryPreparationTime"; 5 | export { RuntimeExecutionTimes } from "./runtimeExecutionTimes"; 6 | export { TimeSpan } from "./timeSpan"; 7 | -------------------------------------------------------------------------------- /src/queryMetrics/queryMetricsConstants.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // QueryMetrics 3 | RetrievedDocumentCount: "retrievedDocumentCount", 4 | RetrievedDocumentSize: "retrievedDocumentSize", 5 | OutputDocumentCount: "outputDocumentCount", 6 | OutputDocumentSize: "outputDocumentSize", 7 | IndexHitRatio: "indexUtilizationRatio", 8 | IndexHitDocumentCount: "indexHitDocumentCount", 9 | TotalQueryExecutionTimeInMs: "totalExecutionTimeInMs", 10 | 11 | // QueryPreparationTimes 12 | QueryCompileTimeInMs: "queryCompileTimeInMs", 13 | LogicalPlanBuildTimeInMs: "queryLogicalPlanBuildTimeInMs", 14 | PhysicalPlanBuildTimeInMs: "queryPhysicalPlanBuildTimeInMs", 15 | QueryOptimizationTimeInMs: "queryOptimizationTimeInMs", 16 | 17 | // QueryTimes 18 | IndexLookupTimeInMs: "indexLookupTimeInMs", 19 | DocumentLoadTimeInMs: "documentLoadTimeInMs", 20 | VMExecutionTimeInMs: "VMExecutionTimeInMs", 21 | DocumentWriteTimeInMs: "writeOutputTimeInMs", 22 | 23 | // RuntimeExecutionTimes 24 | QueryEngineTimes: "queryEngineTimes", 25 | SystemFunctionExecuteTimeInMs: "systemFunctionExecuteTimeInMs", 26 | UserDefinedFunctionExecutionTimeInMs: "userFunctionExecuteTimeInMs", 27 | 28 | // QueryMetrics Text 29 | RetrievedDocumentCountText: "Retrieved Document Count", 30 | RetrievedDocumentSizeText: "Retrieved Document Size", 31 | OutputDocumentCountText: "Output Document Count", 32 | OutputDocumentSizeText: "Output Document Size", 33 | IndexUtilizationText: "Index Utilization", 34 | TotalQueryExecutionTimeText: "Total Query Execution Time", 35 | 36 | // QueryPreparationTimes Text 37 | QueryPreparationTimesText: "Query Preparation Times", 38 | QueryCompileTimeText: "Query Compilation Time", 39 | LogicalPlanBuildTimeText: "Logical Plan Build Time", 40 | PhysicalPlanBuildTimeText: "Physical Plan Build Time", 41 | QueryOptimizationTimeText: "Query Optimization Time", 42 | 43 | // QueryTimes Text 44 | QueryEngineTimesText: "Query Engine Times", 45 | IndexLookupTimeText: "Index Lookup Time", 46 | DocumentLoadTimeText: "Document Load Time", 47 | WriteOutputTimeText: "Document Write Time", 48 | 49 | // RuntimeExecutionTimes Text 50 | RuntimeExecutionTimesText: "Runtime Execution Times", 51 | TotalExecutionTimeText: "Query Engine Execution Time", 52 | SystemFunctionExecuteTimeText: "System Function Execution Time", 53 | UserDefinedFunctionExecutionTimeText: "User-defined Function Execution Time", 54 | 55 | // ClientSideQueryMetrics Text 56 | ClientSideQueryMetricsText: "Client Side Metrics", 57 | RetriesText: "Retry Count", 58 | RequestChargeText: "Request Charge", 59 | FetchExecutionRangesText: "Partition Execution Timeline", 60 | SchedulingMetricsText: "Scheduling Metrics" 61 | }; 62 | -------------------------------------------------------------------------------- /src/queryMetrics/queryMetricsUtils.ts: -------------------------------------------------------------------------------- 1 | import { TimeSpan } from "./timeSpan"; 2 | 3 | /** 4 | * @ignore 5 | * @param delimitedString 6 | */ 7 | export function parseDelimitedString(delimitedString: string) { 8 | if (delimitedString == null) { 9 | throw new Error("delimitedString is null or undefined"); 10 | } 11 | 12 | const metrics: { [key: string]: any } = {}; 13 | 14 | const headerAttributes = delimitedString.split(";"); 15 | for (const attribute of headerAttributes) { 16 | const attributeKeyValue = attribute.split("="); 17 | 18 | if (attributeKeyValue.length !== 2) { 19 | throw new Error("recieved a malformed delimited string"); 20 | } 21 | 22 | const attributeKey = attributeKeyValue[0]; 23 | const attributeValue = parseFloat(attributeKeyValue[1]); 24 | 25 | metrics[attributeKey] = attributeValue; 26 | } 27 | 28 | return metrics; 29 | } 30 | 31 | /** 32 | * @ignore 33 | * @param metrics 34 | * @param key 35 | */ 36 | export function timeSpanFromMetrics(metrics: { [key: string]: any } /* TODO: any */, key: string) { 37 | if (key in metrics) { 38 | return TimeSpan.fromMilliseconds(metrics[key]); 39 | } 40 | 41 | return TimeSpan.zero; 42 | } 43 | 44 | /** 45 | * @ignore 46 | * @param input 47 | */ 48 | export function isNumeric(input: any) { 49 | return !isNaN(parseFloat(input)) && isFinite(input); 50 | } 51 | -------------------------------------------------------------------------------- /src/range/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Range"; 2 | export * from "./RangePartitionResolver"; 3 | -------------------------------------------------------------------------------- /src/request/ErrorResponse.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../index"; 2 | 3 | interface ErrorBody { 4 | code: string; 5 | message: string; 6 | /** 7 | * @ignore 8 | */ 9 | additionalErrorInfo?: PartitionedQueryExecutionInfo; 10 | } 11 | 12 | /** 13 | * @ignore 14 | */ 15 | export interface PartitionedQueryExecutionInfo { 16 | partitionedQueryExecutionInfoVersion: number; 17 | queryInfo: QueryInfo; 18 | queryRanges: QueryRange[]; 19 | } 20 | 21 | /** 22 | * @ignore 23 | */ 24 | interface QueryRange { 25 | min: string; 26 | max: string; 27 | isMinInclusive: boolean; 28 | isMaxInclusive: boolean; 29 | } 30 | 31 | /** 32 | * @ignore 33 | */ 34 | interface QueryInfo { 35 | top?: any; 36 | orderBy?: any[]; 37 | orderByExpressions?: any[]; 38 | offset?: number; 39 | limit?: number; 40 | aggregates?: any[]; 41 | rewrittenQuery?: any; 42 | distinctType: string; 43 | hasSelectValue: boolean; 44 | } 45 | 46 | export interface ErrorResponse extends Error { 47 | code?: number; 48 | substatus?: number; 49 | body?: ErrorBody; 50 | headers?: CosmosHeaders; 51 | activityId?: string; 52 | retryAfterInMilliseconds?: number; 53 | [key: string]: any; 54 | } 55 | -------------------------------------------------------------------------------- /src/request/FeedResponse.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "../common"; 2 | import { CosmosHeaders } from "../queryExecutionContext"; 3 | 4 | export class FeedResponse { 5 | constructor( 6 | public readonly resources: TResource[], 7 | private readonly headers: CosmosHeaders, 8 | public readonly hasMoreResults: boolean 9 | ) {} 10 | public get continuation(): string { 11 | return this.headers[Constants.HttpHeaders.Continuation]; 12 | } 13 | public get queryMetrics(): string { 14 | return this.headers[Constants.HttpHeaders.QueryMetrics]; 15 | } 16 | public get requestCharge(): number { 17 | return this.headers[Constants.HttpHeaders.RequestCharge]; 18 | } 19 | public get activityId(): string { 20 | return this.headers[Constants.HttpHeaders.ActivityId]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/request/LocationRouting.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | */ 4 | export class LocationRouting { 5 | private pIgnorePreferredLocation: boolean; 6 | private pLocationIndexToRoute: number; 7 | private pLocationEndpointToRoute: string; 8 | public get ignorePreferredLocation() { 9 | return this.pIgnorePreferredLocation; 10 | } 11 | 12 | public get locationIndexToRoute() { 13 | return this.pLocationIndexToRoute; 14 | } 15 | 16 | public get locationEndpointToRoute() { 17 | return this.pLocationEndpointToRoute; 18 | } 19 | 20 | public routeToLocation(locationEndpoint: string): void; 21 | public routeToLocation(locationIndex: number, ignorePreferredLocation: boolean): void; 22 | public routeToLocation(endpointOrIndex: string | number, ignorePreferredLocation?: boolean) { 23 | if (typeof ignorePreferredLocation !== "undefined" && typeof endpointOrIndex === "number") { 24 | this.pLocationIndexToRoute = endpointOrIndex; 25 | this.pIgnorePreferredLocation = ignorePreferredLocation; 26 | this.pLocationEndpointToRoute = undefined; 27 | } else if (typeof ignorePreferredLocation === "undefined" && typeof endpointOrIndex === "string") { 28 | this.pLocationEndpointToRoute = endpointOrIndex; 29 | this.pLocationIndexToRoute = undefined; 30 | this.pIgnorePreferredLocation = undefined; 31 | } else { 32 | throw new Error("Invalid arguments passed to routeToLocation"); 33 | } 34 | } 35 | 36 | public clearRouteToLocation(): void { 37 | this.pLocationEndpointToRoute = undefined; 38 | this.pLocationIndexToRoute = undefined; 39 | this.pIgnorePreferredLocation = undefined; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/request/RequestContext.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../ClientContext"; 2 | import { HTTPMethod, OperationType, ResourceType } from "../common"; 3 | import { Agent } from "../CosmosClientOptions"; 4 | import { ConnectionPolicy, PartitionKey } from "../documents"; 5 | import { GlobalEndpointManager } from "../globalEndpointManager"; 6 | import { PluginConfig } from "../plugins/Plugin"; 7 | import { CosmosHeaders } from "../queryExecutionContext/CosmosHeaders"; 8 | import { FeedOptions } from "./FeedOptions"; 9 | import { LocationRouting } from "./LocationRouting"; 10 | import { RequestOptions } from "./RequestOptions"; 11 | 12 | /** 13 | * @ignore 14 | */ 15 | export interface RequestContext { 16 | path?: string; 17 | operationType?: OperationType; 18 | client?: ClientContext; 19 | retryCount?: number; 20 | resourceType?: ResourceType; 21 | resourceId?: string; 22 | locationRouting?: LocationRouting; 23 | globalEndpointManager: GlobalEndpointManager; 24 | connectionPolicy: ConnectionPolicy; 25 | requestAgent: Agent; 26 | body?: any; 27 | headers?: CosmosHeaders; 28 | endpoint?: string; 29 | method: HTTPMethod; 30 | partitionKeyRangeId?: string; 31 | options: FeedOptions | RequestOptions; 32 | plugins: PluginConfig[]; 33 | partitionKey?: PartitionKey; 34 | } 35 | -------------------------------------------------------------------------------- /src/request/RequestOptions.ts: -------------------------------------------------------------------------------- 1 | import { SharedOptions } from "./SharedOptions"; 2 | 3 | /** 4 | * Options that can be specified for a requested issued to the Azure Cosmos DB servers.= 5 | */ 6 | export interface RequestOptions extends SharedOptions { 7 | /** Conditions Associated with the request. */ 8 | accessCondition?: { 9 | /** Conditional HTTP method header type (IfMatch or IfNoneMatch). */ 10 | type: string; 11 | /** Conditional HTTP method header value (the _etag field from the last version you read). */ 12 | condition: string; 13 | }; 14 | /** Consistency level required by the client. */ 15 | consistencyLevel?: string; 16 | /** 17 | * DisableRUPerMinuteUsage is used to enable/disable Request Units(RUs)/minute capacity 18 | * to serve the request if regular provisioned RUs/second is exhausted. 19 | */ 20 | disableRUPerMinuteUsage?: boolean; 21 | /** Enables or disables logging in JavaScript stored procedures. */ 22 | enableScriptLogging?: boolean; 23 | /** Specifies indexing directives (index, do not index .. etc). */ 24 | indexingDirective?: string; 25 | /** The offer throughput provisioned for a container in measurement of Requests-per-Unit. */ 26 | offerThroughput?: number; 27 | /** 28 | * Offer type when creating document containers. 29 | * 30 | * This option is only valid when creating a document container. 31 | */ 32 | offerType?: string; 33 | /** Enables/disables getting document container quota related stats for document container read requests. */ 34 | populateQuotaInfo?: boolean; 35 | /** Indicates what is the post trigger to be invoked after the operation. */ 36 | postTriggerInclude?: string | string[]; 37 | /** Indicates what is the pre trigger to be invoked before the operation. */ 38 | preTriggerInclude?: string | string[]; 39 | /** Expiry time (in seconds) for resource token associated with permission (applicable only for requests on permissions). */ 40 | resourceTokenExpirySeconds?: number; 41 | /** (Advanced use case) The url to connect to. */ 42 | urlConnection?: string; 43 | /** Disable automatic id generation (will cause creates to fail if id isn't on the definition) */ 44 | disableAutomaticIdGeneration?: boolean; 45 | } 46 | -------------------------------------------------------------------------------- /src/request/ResourceResponse.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "../common"; 2 | import { CosmosHeaders } from "../queryExecutionContext/CosmosHeaders"; 3 | import { StatusCode, SubStatusCode } from "./StatusCodes"; 4 | 5 | export class ResourceResponse { 6 | constructor( 7 | public readonly resource: TResource, 8 | public readonly headers: CosmosHeaders, 9 | public readonly statusCode: StatusCode, 10 | public readonly substatus?: SubStatusCode 11 | ) {} 12 | public get requestCharge(): number { 13 | return this.headers[Constants.HttpHeaders.RequestCharge] as number; 14 | } 15 | public get activityId(): string { 16 | return this.headers[Constants.HttpHeaders.ActivityId] as string; 17 | } 18 | public get etag(): string { 19 | return this.headers[Constants.HttpHeaders.ETag] as string; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/request/Response.ts: -------------------------------------------------------------------------------- 1 | import { CosmosHeaders } from "../index"; 2 | 3 | /** 4 | * @ignore 5 | */ 6 | export interface Response { 7 | headers: CosmosHeaders; 8 | result?: T; 9 | code?: number; 10 | substatus?: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/request/SharedOptions.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { CosmosHeaders } from "../index"; 3 | 4 | /** 5 | * Options that can be specified for a requested issued to the Azure Cosmos DB servers.= 6 | */ 7 | export interface SharedOptions { 8 | /** Enables/disables getting document container quota related stats for document container read requests. */ 9 | sessionToken?: string; 10 | /** (Advanced use case) Initial headers to start with when sending requests to Cosmos */ 11 | initialHeaders?: CosmosHeaders; 12 | /** 13 | * abortSignal to pass to all underlying network requests created by this method call. See https://developer.mozilla.org/en-US/docs/Web/API/AbortController 14 | * @example Cancel a read request 15 | * ```typescript 16 | * const controller = new AbortController() 17 | * const {result: item} = await items.query('SELECT * from c', { abortSignal: controller.signal}); 18 | * controller.abort() 19 | * ``` 20 | */ 21 | abortSignal?: AbortSignal; 22 | } 23 | -------------------------------------------------------------------------------- /src/request/StatusCodes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | */ 4 | export const StatusCode = { 5 | // Success 6 | Ok: 200 as 200, 7 | Created: 201 as 201, 8 | Accepted: 202 as 202, 9 | NoContent: 204 as 204, 10 | NotModified: 304 as 304, 11 | 12 | // Client error 13 | BadRequest: 400 as 400, 14 | Unauthorized: 401 as 401, 15 | Forbidden: 403 as 403, 16 | NotFound: 404 as 404, 17 | MethodNotAllowed: 405 as 405, 18 | RequestTimeout: 408 as 408, 19 | Conflict: 409 as 409, 20 | Gone: 410 as 410, 21 | PreconditionFailed: 412 as 412, 22 | RequestEntityTooLarge: 413 as 413, 23 | TooManyRequests: 429 as 429, 24 | RetryWith: 449 as 449, 25 | 26 | // Server Error 27 | InternalServerError: 500 as 500, 28 | ServiceUnavailable: 503 as 503, 29 | 30 | // Operation pause and cancel. These are FAKE status codes for QOS logging purpose only. 31 | OperationPaused: 1200 as 1200, 32 | OperationCancelled: 1201 33 | }; 34 | 35 | /** 36 | * @ignore 37 | */ 38 | export type StatusCode = (typeof StatusCode)[keyof typeof StatusCode]; 39 | 40 | /** 41 | * @ignore 42 | */ 43 | export const SubStatusCode = { 44 | Unknown: 0 as 0, 45 | 46 | // 400: Bad Request Substatus 47 | CrossPartitionQueryNotServable: 1004 as 1004, 48 | 49 | // 410: StatusCodeType_Gone: substatus 50 | PartitionKeyRangeGone: 1002 as 1002, 51 | 52 | // 404: NotFound Substatus 53 | ReadSessionNotAvailable: 1002 as 1002, 54 | 55 | // 403: Forbidden Substatus 56 | WriteForbidden: 3 57 | }; 58 | 59 | export type SubStatusCode = (typeof SubStatusCode)[keyof typeof SubStatusCode]; 60 | -------------------------------------------------------------------------------- /src/request/TimeoutError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | */ 4 | export const TimeoutErrorCode = "TimeoutError"; 5 | 6 | export class TimeoutError extends Error { 7 | public readonly code: string = TimeoutErrorCode; 8 | constructor(message?: string) { 9 | super(message); 10 | this.name = TimeoutErrorCode; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/request/defaultAgent.browser.ts: -------------------------------------------------------------------------------- 1 | import { Agent } from "http"; 2 | /** 3 | * @ignore 4 | */ 5 | export let defaultHttpAgent: Agent; 6 | /** 7 | * @ignore 8 | */ 9 | export let defaultHttpsAgent: Agent; 10 | -------------------------------------------------------------------------------- /src/request/defaultAgent.ts: -------------------------------------------------------------------------------- 1 | import { Agent } from "http"; 2 | import { isNode } from "../common"; 3 | 4 | /** 5 | * @ignore 6 | */ 7 | export let defaultHttpAgent: Agent; 8 | /** 9 | * @ignore 10 | */ 11 | export let defaultHttpsAgent: Agent; 12 | 13 | if (isNode) { 14 | // tslint:disable-next-line:no-var-requires 15 | const https = require("https"); 16 | defaultHttpsAgent = new https.Agent({ 17 | keepAlive: true 18 | }); 19 | // tslint:disable-next-line:no-var-requires 20 | const http = require("http"); 21 | defaultHttpAgent = new http.Agent({ 22 | keepAlive: true 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/request/index.ts: -------------------------------------------------------------------------------- 1 | export { ErrorResponse } from "./ErrorResponse"; 2 | export { FeedOptions } from "./FeedOptions"; 3 | export { RequestOptions } from "./RequestOptions"; 4 | export { Response } from "./Response"; 5 | export { ResourceResponse } from "./ResourceResponse"; 6 | -------------------------------------------------------------------------------- /src/retry/RetryContext.ts: -------------------------------------------------------------------------------- 1 | export interface RetryContext { 2 | retryCount?: number; 3 | retryRequestOnPreferredLocations?: boolean; 4 | clearSessionTokenNotAvailable?: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/retry/RetryPolicy.ts: -------------------------------------------------------------------------------- 1 | import { ErrorResponse } from "../request"; 2 | import { RetryContext } from "./RetryContext"; 3 | 4 | /** 5 | * @ignore 6 | */ 7 | export interface RetryPolicy { 8 | retryAfterInMilliseconds: number; 9 | shouldRetry: ( 10 | errorResponse: ErrorResponse, 11 | retryContext?: RetryContext, 12 | locationEndpoint?: string 13 | ) => Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/retry/endpointDiscoveryRetryPolicy.ts: -------------------------------------------------------------------------------- 1 | import { OperationType } from "../common"; 2 | import { isReadRequest } from "../common/helper"; 3 | import { GlobalEndpointManager } from "../globalEndpointManager"; 4 | import { ErrorResponse } from "../request"; 5 | import { RetryContext } from "./RetryContext"; 6 | import { RetryPolicy } from "./RetryPolicy"; 7 | 8 | /** 9 | * This class implements the retry policy for endpoint discovery. 10 | * @hidden 11 | */ 12 | export class EndpointDiscoveryRetryPolicy implements RetryPolicy { 13 | /** Current retry attempt count. */ 14 | public currentRetryAttemptCount: number; 15 | /** Retry interval in milliseconds. */ 16 | public retryAfterInMilliseconds: number; 17 | 18 | /** Max number of retry attempts to perform. */ 19 | private maxRetryAttemptCount: number; 20 | private static readonly maxRetryAttemptCount = 120; // TODO: Constant? 21 | private static readonly retryAfterInMilliseconds = 1000; 22 | 23 | /** 24 | * @constructor EndpointDiscoveryRetryPolicy 25 | * @param {object} globalEndpointManager The GlobalEndpointManager instance. 26 | */ 27 | constructor(private globalEndpointManager: GlobalEndpointManager, private operationType: OperationType) { 28 | this.maxRetryAttemptCount = EndpointDiscoveryRetryPolicy.maxRetryAttemptCount; 29 | this.currentRetryAttemptCount = 0; 30 | this.retryAfterInMilliseconds = EndpointDiscoveryRetryPolicy.retryAfterInMilliseconds; 31 | } 32 | 33 | /** 34 | * Determines whether the request should be retried or not. 35 | * @param {object} err - Error returned by the request. 36 | */ 37 | public async shouldRetry( 38 | err: ErrorResponse, 39 | retryContext?: RetryContext, 40 | locationEndpoint?: string 41 | ): Promise { 42 | if (!err) { 43 | return false; 44 | } 45 | 46 | if (!retryContext || !locationEndpoint) { 47 | return false; 48 | } 49 | 50 | if (!this.globalEndpointManager.enableEndpointDiscovery) { 51 | return false; 52 | } 53 | 54 | if (this.currentRetryAttemptCount >= this.maxRetryAttemptCount) { 55 | return false; 56 | } 57 | 58 | this.currentRetryAttemptCount++; 59 | 60 | if (isReadRequest(this.operationType)) { 61 | this.globalEndpointManager.markCurrentLocationUnavailableForRead(locationEndpoint); 62 | } else { 63 | this.globalEndpointManager.markCurrentLocationUnavailableForWrite(locationEndpoint); 64 | } 65 | 66 | // Check location index increment 67 | // TODO: Tracing 68 | // console.log("Write region was changed, refreshing the regions list from database account 69 | // and will retry the request."); 70 | await this.globalEndpointManager.refreshEndpointList(); 71 | 72 | retryContext.retryCount = this.currentRetryAttemptCount; 73 | retryContext.clearSessionTokenNotAvailable = false; 74 | retryContext.retryRequestOnPreferredLocations = false; 75 | 76 | return true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/retry/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./retryOptions"; 2 | export * from "./endpointDiscoveryRetryPolicy"; 3 | export * from "./resourceThrottleRetryPolicy"; 4 | export * from "./sessionRetryPolicy"; 5 | export * from "./retryUtility"; 6 | -------------------------------------------------------------------------------- /src/retry/resourceThrottleRetryPolicy.ts: -------------------------------------------------------------------------------- 1 | import { ErrorResponse } from "../request"; 2 | 3 | /** 4 | * This class implements the resource throttle retry policy for requests. 5 | * @hidden 6 | */ 7 | export class ResourceThrottleRetryPolicy { 8 | /** Current retry attempt count. */ 9 | public currentRetryAttemptCount: number = 0; 10 | /** Cummulative wait time in milliseconds for a request while the retries are happening. */ 11 | public cummulativeWaitTimeinMilliseconds: number = 0; 12 | /** Max wait time in milliseconds to wait for a request while the retries are happening. */ 13 | public retryAfterInMilliseconds: number = 0; 14 | 15 | /** Max number of retries to be performed for a request. */ 16 | private maxWaitTimeInMilliseconds: number; 17 | /** 18 | * @constructor ResourceThrottleRetryPolicy 19 | * @param {int} maxRetryAttemptCount - Max number of retries to be performed for a request. 20 | * @param {int} fixedRetryIntervalInMilliseconds - Fixed retry interval in milliseconds to wait between each \ 21 | * retry ignoring the retryAfter returned as part of the response. 22 | * @param {int} maxWaitTimeInSeconds - Max wait time in seconds to wait for a request while the \ 23 | * retries are happening. 24 | */ 25 | constructor( 26 | private maxRetryAttemptCount: number = 9, 27 | private fixedRetryIntervalInMilliseconds: number = 0, 28 | maxWaitTimeInSeconds: number = 30 29 | ) { 30 | this.maxWaitTimeInMilliseconds = maxWaitTimeInSeconds * 1000; 31 | this.currentRetryAttemptCount = 0; 32 | this.cummulativeWaitTimeinMilliseconds = 0; 33 | } 34 | /** 35 | * Determines whether the request should be retried or not. 36 | * @param {object} err - Error returned by the request. 37 | */ 38 | public async shouldRetry(err: ErrorResponse): Promise { 39 | // TODO: any custom error object 40 | if (err) { 41 | if (this.currentRetryAttemptCount < this.maxRetryAttemptCount) { 42 | this.currentRetryAttemptCount++; 43 | this.retryAfterInMilliseconds = 0; 44 | 45 | if (this.fixedRetryIntervalInMilliseconds) { 46 | this.retryAfterInMilliseconds = this.fixedRetryIntervalInMilliseconds; 47 | } else if (err.retryAfterInMilliseconds) { 48 | this.retryAfterInMilliseconds = err.retryAfterInMilliseconds; 49 | } 50 | 51 | if (this.cummulativeWaitTimeinMilliseconds < this.maxWaitTimeInMilliseconds) { 52 | this.cummulativeWaitTimeinMilliseconds += this.retryAfterInMilliseconds; 53 | return true; 54 | } 55 | } 56 | } 57 | return false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/retry/retryOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the Retry policy assocated with throttled requests in the Azure Cosmos DB database service. 3 | */ 4 | export interface RetryOptions { 5 | /** Max number of retries to be performed for a request. Default value 9. */ 6 | maxRetryAttemptCount: number; 7 | /** Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response. */ 8 | fixedRetryIntervalInMilliseconds: number; 9 | /** Max wait time in seconds to wait for a request while the retries are happening. Default value 30 seconds. */ 10 | maxWaitTimeInSeconds: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/retry/sessionRetryPolicy.ts: -------------------------------------------------------------------------------- 1 | import { isReadRequest, OperationType, ResourceType } from "../common"; 2 | import { ConnectionPolicy } from "../documents"; 3 | import { GlobalEndpointManager } from "../globalEndpointManager"; 4 | import { ErrorResponse } from "../request"; 5 | import { RetryContext } from "./RetryContext"; 6 | import { RetryPolicy } from "./RetryPolicy"; 7 | 8 | /** 9 | * This class implements the retry policy for session consistent reads. 10 | * @hidden 11 | */ 12 | export class SessionRetryPolicy implements RetryPolicy { 13 | /** Current retry attempt count. */ 14 | public currentRetryAttemptCount = 0; 15 | /** Retry interval in milliseconds. */ 16 | public retryAfterInMilliseconds = 0; 17 | 18 | /** 19 | * @constructor SessionReadRetryPolicy 20 | * @param {object} globalEndpointManager - The GlobalEndpointManager instance. 21 | * @property {object} request - The Http request information 22 | */ 23 | constructor( 24 | private globalEndpointManager: GlobalEndpointManager, 25 | private resourceType: ResourceType, 26 | private operationType: OperationType, 27 | private connectionPolicy: ConnectionPolicy 28 | ) {} 29 | 30 | /** 31 | * Determines whether the request should be retried or not. 32 | * @param {object} err - Error returned by the request. 33 | * @param {function} callback - The callback function which takes bool argument which specifies whether the request\ 34 | * will be retried or not. 35 | */ 36 | public async shouldRetry(err: ErrorResponse, retryContext?: RetryContext): Promise { 37 | if (!err) { 38 | return false; 39 | } 40 | 41 | if (!retryContext) { 42 | return false; 43 | } 44 | 45 | if (!this.connectionPolicy.enableEndpointDiscovery) { 46 | return false; 47 | } 48 | 49 | if (this.globalEndpointManager.canUseMultipleWriteLocations(this.resourceType, this.operationType)) { 50 | // If we can write to multiple locations, we should against every write endpoint until we succeed 51 | const endpoints = isReadRequest(this.operationType) 52 | ? await this.globalEndpointManager.getReadEndpoints() 53 | : await this.globalEndpointManager.getWriteEndpoints(); 54 | if (this.currentRetryAttemptCount > endpoints.length) { 55 | return false; 56 | } else { 57 | retryContext.retryCount = ++this.currentRetryAttemptCount - 1; 58 | retryContext.retryRequestOnPreferredLocations = this.currentRetryAttemptCount > 1; 59 | retryContext.clearSessionTokenNotAvailable = this.currentRetryAttemptCount === endpoints.length; 60 | return true; 61 | } 62 | } else { 63 | if (this.currentRetryAttemptCount > 1) { 64 | return false; 65 | } else { 66 | retryContext.retryCount = ++this.currentRetryAttemptCount - 1; 67 | retryContext.retryRequestOnPreferredLocations = false; // Forces all operations to primary write endpoint 68 | retryContext.clearSessionTokenNotAvailable = true; 69 | return true; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/routing/CollectionRoutingMapFactory.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "../common/constants"; 2 | import { InMemoryCollectionRoutingMap } from "./inMemoryCollectionRoutingMap"; 3 | 4 | /** 5 | * @ignore 6 | */ 7 | function compareRanges(a: any, b: any) { 8 | const aVal = a[0][Constants.PartitionKeyRange.MinInclusive]; 9 | const bVal = b[0][Constants.PartitionKeyRange.MinInclusive]; 10 | if (aVal > bVal) { 11 | return 1; 12 | } 13 | if (aVal < bVal) { 14 | return -1; 15 | } 16 | return 0; 17 | } 18 | 19 | /** @hidden */ 20 | export function createCompleteRoutingMap(partitionKeyRangeInfoTuppleList: any[]) { 21 | const rangeById: any = {}; // TODO: any 22 | const rangeByInfo: any = {}; // TODO: any 23 | 24 | let sortedRanges = []; 25 | 26 | // the for loop doesn't invoke any async callback 27 | for (const r of partitionKeyRangeInfoTuppleList) { 28 | rangeById[r[0][Constants.PartitionKeyRange.Id]] = r; 29 | rangeByInfo[r[1]] = r[0]; 30 | sortedRanges.push(r); 31 | } 32 | 33 | sortedRanges = sortedRanges.sort(compareRanges); 34 | const partitionKeyOrderedRange = sortedRanges.map(r => r[0]); 35 | const orderedPartitionInfo = sortedRanges.map(r => r[1]); 36 | 37 | if (!isCompleteSetOfRange(partitionKeyOrderedRange)) { 38 | return undefined; 39 | } 40 | return new InMemoryCollectionRoutingMap(partitionKeyOrderedRange, orderedPartitionInfo); 41 | } 42 | 43 | /** 44 | * @ignore 45 | */ 46 | function isCompleteSetOfRange(partitionKeyOrderedRange: any) { 47 | // TODO: any 48 | let isComplete = false; 49 | if (partitionKeyOrderedRange.length > 0) { 50 | const firstRange = partitionKeyOrderedRange[0]; 51 | const lastRange = partitionKeyOrderedRange[partitionKeyOrderedRange.length - 1]; 52 | isComplete = 53 | firstRange[Constants.PartitionKeyRange.MinInclusive] === 54 | Constants.EffectiveParitionKeyConstants.MinimumInclusiveEffectivePartitionKey; 55 | isComplete = 56 | isComplete && 57 | lastRange[Constants.PartitionKeyRange.MaxExclusive] === 58 | Constants.EffectiveParitionKeyConstants.MaximumExclusiveEffectivePartitionKey; 59 | 60 | for (let i = 1; i < partitionKeyOrderedRange.length; i++) { 61 | const previousRange = partitionKeyOrderedRange[i - 1]; 62 | const currentRange = partitionKeyOrderedRange[i]; 63 | isComplete = 64 | isComplete && 65 | previousRange[Constants.PartitionKeyRange.MaxExclusive] === 66 | currentRange[Constants.PartitionKeyRange.MinInclusive]; 67 | 68 | if (!isComplete) { 69 | if ( 70 | previousRange[Constants.PartitionKeyRange.MaxExclusive] > 71 | currentRange[Constants.PartitionKeyRange.MinInclusive] 72 | ) { 73 | throw Error("Ranges overlap"); 74 | } 75 | break; 76 | } 77 | } 78 | } 79 | return isComplete; 80 | } 81 | -------------------------------------------------------------------------------- /src/routing/QueryRange.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from "../common"; 2 | 3 | /** @hidden */ 4 | export class QueryRange { 5 | public min: string; 6 | public max: string; 7 | public isMinInclusive: boolean; 8 | public isMaxInclusive: boolean; 9 | 10 | /** 11 | * Represents a QueryRange. 12 | * @constructor QueryRange 13 | * @param {string} rangeMin - min 14 | * @param {string} rangeMin - max 15 | * @param {boolean} isMinInclusive - isMinInclusive 16 | * @param {boolean} isMaxInclusive - isMaxInclusive 17 | * @ignore 18 | */ 19 | constructor(rangeMin: string, rangeMax: string, isMinInclusive: boolean, isMaxInclusive: boolean) { 20 | this.min = rangeMin; 21 | this.max = rangeMax; 22 | this.isMinInclusive = isMinInclusive; 23 | this.isMaxInclusive = isMaxInclusive; 24 | } 25 | public overlaps(other: QueryRange) { 26 | // tslint:disable-next-line:no-this-assignment 27 | const range1 = this; 28 | const range2 = other; 29 | if (range1 === undefined || range2 === undefined) { 30 | return false; 31 | } 32 | if (range1.isEmpty() || range2.isEmpty()) { 33 | return false; 34 | } 35 | 36 | if (range1.min <= range2.max || range2.min <= range1.max) { 37 | if ( 38 | (range1.min === range2.max && !(range1.isMinInclusive && range2.isMaxInclusive)) || 39 | (range2.min === range1.max && !(range2.isMinInclusive && range1.isMaxInclusive)) 40 | ) { 41 | return false; 42 | } 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | public isEmpty() { 49 | return !(this.isMinInclusive && this.isMaxInclusive) && this.min === this.max; 50 | } 51 | /** 52 | * Parse a QueryRange from a partitionKeyRange 53 | * @returns QueryRange 54 | * @ignore 55 | */ 56 | public static parsePartitionKeyRange(partitionKeyRange: any) { 57 | // TODO: paritionkeyrange 58 | return new QueryRange( 59 | partitionKeyRange[Constants.PartitionKeyRange.MinInclusive], 60 | partitionKeyRange[Constants.PartitionKeyRange.MaxExclusive], 61 | true, 62 | false 63 | ); 64 | } 65 | /** 66 | * Parse a QueryRange from a dictionary 67 | * @returns QueryRange 68 | * @ignore 69 | */ 70 | public static parseFromDict(queryRangeDict: any) { 71 | // TODO: queryRangeDictionary 72 | return new QueryRange( 73 | queryRangeDict.min, 74 | queryRangeDict.max, 75 | queryRangeDict.isMinInclusive, 76 | queryRangeDict.isMaxInclusive 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/routing/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./QueryRange"; 2 | export * from "./inMemoryCollectionRoutingMap"; 3 | export * from "./partitionKeyRangeCache"; 4 | export * from "./smartRoutingMapProvider"; 5 | -------------------------------------------------------------------------------- /src/routing/partitionKeyRangeCache.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../ClientContext"; 2 | import { getIdFromLink } from "../common/helper"; 3 | import { createCompleteRoutingMap } from "./CollectionRoutingMapFactory"; 4 | import { InMemoryCollectionRoutingMap } from "./inMemoryCollectionRoutingMap"; 5 | import { QueryRange } from "./QueryRange"; 6 | 7 | /** @hidden */ 8 | export class PartitionKeyRangeCache { 9 | private collectionRoutingMapByCollectionId: { 10 | [key: string]: Promise; 11 | }; 12 | 13 | constructor(private clientContext: ClientContext) { 14 | this.collectionRoutingMapByCollectionId = {}; 15 | } 16 | /** 17 | * Finds or Instantiates the requested Collection Routing Map 18 | * @param {string} collectionLink - Requested collectionLink 19 | * @ignore 20 | */ 21 | public async onCollectionRoutingMap(collectionLink: string): Promise { 22 | const collectionId = getIdFromLink(collectionLink); 23 | if (this.collectionRoutingMapByCollectionId[collectionId] === undefined) { 24 | this.collectionRoutingMapByCollectionId[collectionId] = this.requestCollectionRoutingMap(collectionLink); 25 | } 26 | return this.collectionRoutingMapByCollectionId[collectionId]; 27 | } 28 | 29 | /** 30 | * Given the query ranges and a collection, invokes the callback on the list of overlapping partition key ranges 31 | * @param collectionLink 32 | * @param queryRanges 33 | * @ignore 34 | */ 35 | public async getOverlappingRanges(collectionLink: string, queryRanges: QueryRange) { 36 | const crm = await this.onCollectionRoutingMap(collectionLink); 37 | return crm.getOverlappingRanges(queryRanges); 38 | } 39 | 40 | private async requestCollectionRoutingMap(collectionLink: string) { 41 | const { resources } = await this.clientContext.queryPartitionKeyRanges(collectionLink).fetchAll(); 42 | return createCompleteRoutingMap(resources.map(r => [r, true])); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/session/SessionContext.ts: -------------------------------------------------------------------------------- 1 | import { OperationType, ResourceType } from "../common"; 2 | 3 | /** 4 | * @ignore 5 | */ 6 | export interface SessionContext { 7 | resourceId?: string; 8 | resourceAddress?: string; 9 | resourceType?: ResourceType; 10 | isNameBased?: boolean; 11 | operationType?: OperationType; 12 | } 13 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "importHelpers": true, 8 | "noImplicitAny": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "../dist-esm", 12 | "preserveConstEnums": true, 13 | "removeComments": false, 14 | "target": "es6", 15 | "sourceMap": true, 16 | "newLine": "LF", 17 | "resolveJsonModule": true, 18 | "composite": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "stripInternal": true 22 | }, 23 | "include": ["./**/*"] 24 | } 25 | -------------------------------------------------------------------------------- /src/typings/atob.d.ts: -------------------------------------------------------------------------------- 1 | declare module "atob" { 2 | const _atob: typeof atob; 3 | export = _atob; 4 | } 5 | -------------------------------------------------------------------------------- /src/typings/binary-search-bounds.d.ts: -------------------------------------------------------------------------------- 1 | declare module "binary-search-bounds" { 2 | const _bs: any; 3 | export = _bs; 4 | } 5 | -------------------------------------------------------------------------------- /test/common/BaselineTest.PathParser.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | path: "/", 4 | parts: [] 5 | }, 6 | { 7 | path: "/*", 8 | parts: ["*"] 9 | }, 10 | { 11 | path: '/"Key1"/*', 12 | parts: ["Key1", "*"] 13 | }, 14 | { 15 | path: '/"Key1"/"StringValue"/*', 16 | parts: ["Key1", "StringValue", "*"] 17 | }, 18 | { 19 | path: "/'Key1'/'StringValue'/*", 20 | parts: ["Key1", "StringValue", "*"] 21 | }, 22 | { 23 | path: "/'Ke\\\"\\\"y1'/'Strin\\\"gValue'/*", 24 | parts: ['Ke\\"\\"y1', 'Strin\\"gValue', "*"] 25 | }, 26 | { 27 | path: '/\'Ke\\"\\"y1\'/"Strin\'gValue"/*', 28 | parts: ['Ke\\"\\"y1', "Strin'gValue", "*"] 29 | }, 30 | { 31 | path: "/'Key1'/'StringValue'/*", 32 | parts: ["Key1", "StringValue", "*"] 33 | }, 34 | { 35 | path: '/"Key1"/"Key2"/*', 36 | parts: ["Key1", "Key2", "*"] 37 | }, 38 | { 39 | path: '/"Key1"/"Key2"/"Key3"/*', 40 | parts: ["Key1", "Key2", "Key3", "*"] 41 | }, 42 | { 43 | path: '/"A"/"B"/"R"/[]/"Address"/[]/*', 44 | parts: ["A", "B", "R", "[]", "Address", "[]", "*"] 45 | }, 46 | { 47 | path: '/"A"/"B"/"R"/[]/"Address"/[]/*', 48 | parts: ["A", "B", "R", "[]", "Address", "[]", "*"] 49 | }, 50 | { 51 | path: '/"A"/"B"/"R"/[]/"Address"/*', 52 | parts: ["A", "B", "R", "[]", "Address", "*"] 53 | }, 54 | { 55 | path: '/"Key1"/"Key2"/?', 56 | parts: ["Key1", "Key2", "?"] 57 | }, 58 | { 59 | path: '/"Key1"/"Key2"/*', 60 | parts: ["Key1", "Key2", "*"] 61 | }, 62 | { 63 | path: '/"123"/"StringValue"/*', 64 | parts: ["123", "StringValue", "*"] 65 | }, 66 | { 67 | path: "/'!@#$%^&*()_+='/'StringValue'/*", 68 | parts: ["!@#$%^&*()_+=", "StringValue", "*"] 69 | }, 70 | { 71 | path: '/"_ts"/?', 72 | parts: ["_ts", "?"] 73 | }, 74 | { 75 | path: '/[]/"City"/*', 76 | parts: ["[]", "City", "*"] 77 | }, 78 | { 79 | path: "/[]/*", 80 | parts: ["[]", "*"] 81 | }, 82 | { 83 | path: '/[]/"fine!"/*', 84 | parts: ["[]", "fine!", "*"] 85 | }, 86 | { 87 | path: 88 | '/"this is a long key with speicial characters (*)(*)__)((*&*(&*&\'*(&)()(*_)()(_(_)*!@#$%^ and numbers 132654890"/*', 89 | parts: [ 90 | "this is a long key with speicial characters (*)(*)__)((*&*(&*&'*(&)()(*_)()(_(_)*!@#$%^ and numbers 132654890", 91 | "*" 92 | ] 93 | }, 94 | { 95 | path: "/ Key 1 / Key 2 ", 96 | parts: ["Key 1", "Key 2"] 97 | } 98 | ]; 99 | -------------------------------------------------------------------------------- /test/common/MockClientContext.ts: -------------------------------------------------------------------------------- 1 | import { MockedQueryIterator } from "./MockQueryIterator"; 2 | 3 | /** @hidden */ 4 | export class MockedClientContext { 5 | constructor(private partitionKeyRanges: any, private collectionId: any) {} 6 | public readPartitionKeyRanges(collectionLink: any) { 7 | return new MockedQueryIterator(this.partitionKeyRanges); 8 | } 9 | 10 | public queryPartitionKeyRanges(collectionLink: any) { 11 | return new MockedQueryIterator(this.partitionKeyRanges); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/common/MockQueryIterator.ts: -------------------------------------------------------------------------------- 1 | /** @hidden */ 2 | export class MockedQueryIterator { 3 | constructor(private results: any) {} 4 | public async fetchAll() { 5 | return { resources: this.results }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/common/TestData.ts: -------------------------------------------------------------------------------- 1 | /** @hidden */ 2 | export class TestData { 3 | public numberOfDocuments: number; 4 | public field: string; 5 | public numberOfDocsWithSamePartitionKey: number; 6 | public numberOfDocumentsWithNumbericId: number; 7 | public sum: number; 8 | public docs: any[]; 9 | constructor(public partitionKey: string, public uniquePartitionKey: string) { 10 | this.numberOfDocuments = 50; 11 | this.field = "field"; 12 | const docs = []; 13 | 14 | const values = [null, false, true, "abc", "cdfg", "opqrs", "ttttttt", "xyz", "oo", "ppp"]; 15 | for (const value of values) { 16 | const d: any = {}; 17 | d[partitionKey] = value; 18 | docs.push(d); 19 | } 20 | 21 | this.numberOfDocsWithSamePartitionKey = 20; 22 | for (let i = 0; i < this.numberOfDocsWithSamePartitionKey; ++i) { 23 | const d: any = {}; 24 | d[partitionKey] = uniquePartitionKey; 25 | d["resourceId"] = i.toString(); 26 | d[this.field] = i + 1; 27 | docs.push(d); 28 | } 29 | 30 | this.numberOfDocumentsWithNumbericId = 31 | this.numberOfDocuments - values.length - this.numberOfDocsWithSamePartitionKey; 32 | for (let i = 0; i < this.numberOfDocumentsWithNumbericId; ++i) { 33 | const d: any = {}; 34 | d[partitionKey] = i + 1; 35 | docs.push(d); 36 | } 37 | 38 | this.sum = (this.numberOfDocumentsWithNumbericId * (this.numberOfDocumentsWithNumbericId + 1)) / 2.0; 39 | 40 | this.docs = docs; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/common/_testConfig.ts: -------------------------------------------------------------------------------- 1 | // [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine")] 2 | const masterKey = 3 | process.env.ACCOUNT_KEY || "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; 4 | const endpoint = process.env.ACCOUNT_HOST || "https://localhost:8081"; 5 | 6 | // This is needed to disable SSL verification for the tests running against emulator. 7 | if (endpoint.includes("https://localhost")) { 8 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 9 | } 10 | 11 | export { masterKey, endpoint }; 12 | -------------------------------------------------------------------------------- /test/common/setup.ts: -------------------------------------------------------------------------------- 1 | require("source-map-support").install(); 2 | 3 | process.on("unhandledRejection", error => { 4 | if (error.body) { 5 | try { 6 | error.body = JSON.parse(error.body); 7 | } catch (err) { 8 | /* NO OP */ 9 | } 10 | } 11 | console.error(new Error("Unhandled exception found")); 12 | console.error(JSON.stringify(error, null, " ")); 13 | }); 14 | -------------------------------------------------------------------------------- /test/functional/databaseaccount.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { CosmosClient } from "../../dist-esm"; 3 | import { endpoint, masterKey } from "../common/_testConfig"; 4 | import { removeAllDatabases } from "../common/TestHelpers"; 5 | 6 | const client = new CosmosClient({ endpoint, key: masterKey }); 7 | 8 | describe("NodeJS CRUD Tests", function() { 9 | this.timeout(process.env.MOCHA_TIMEOUT || 10000); 10 | beforeEach(async function() { 11 | this.timeout(process.env.MOCHA_TIMEOUT || 10000); 12 | }); 13 | 14 | describe("validate database account functionality", function() { 15 | it("nativeApi Should get database account successfully name based", async function() { 16 | const { resource: databaseAccount, headers } = await client.getDatabaseAccount(); 17 | assert.equal(databaseAccount.DatabasesLink, "/dbs/"); 18 | assert.equal(databaseAccount.MediaLink, "/media/"); 19 | assert.equal(databaseAccount.MaxMediaStorageUsageInMB, headers["x-ms-max-media-storage-usage-mb"]); // TODO: should use constants here 20 | assert.equal(databaseAccount.CurrentMediaStorageUsageInMB, headers["x-ms-media-storage-usage-mb"]); 21 | assert(databaseAccount.ConsistencyPolicy !== undefined); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/functional/npcontainer.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { CosmosClient, Constants, Container, PluginConfig, CosmosClientOptions } from "../../dist-esm"; 3 | import { removeAllDatabases, getTestContainer } from "../common/TestHelpers"; 4 | import { endpoint, masterKey } from "../common/_testConfig"; 5 | import { ResourceType, HTTPMethod, StatusCodes } from "../../dist-esm/common"; 6 | 7 | const plugins: PluginConfig[] = [ 8 | { 9 | on: "request", 10 | plugin: (context, next) => { 11 | // Intercepts the API request to create a non-partitioned container using an old API version 12 | if (context.resourceType === ResourceType.container && context.method === HTTPMethod.post) { 13 | context.body = JSON.stringify({ id: JSON.parse(context.body).id }); 14 | } 15 | context.headers[Constants.HttpHeaders.Version] = "2018-06-18"; 16 | return next(context); 17 | } 18 | } 19 | ]; 20 | 21 | const options: CosmosClientOptions = { 22 | endpoint, 23 | key: masterKey 24 | }; 25 | 26 | const legacyClient = new CosmosClient({ ...options, plugins } as any); 27 | 28 | const client = new CosmosClient({ 29 | endpoint, 30 | key: masterKey 31 | }); 32 | 33 | describe("Non Partitioned Container", function() { 34 | let container: Container; 35 | before(async () => { 36 | await removeAllDatabases(); 37 | const npContainer = await getTestContainer("Validate Container CRUD", legacyClient); 38 | container = client.database(npContainer.database.id).container(npContainer.id); 39 | }); 40 | 41 | it("should handle item CRUD", async () => { 42 | // read items 43 | const { resources: items } = await container.items.readAll().fetchAll(); 44 | assert(Array.isArray(items), "Value should be an array"); 45 | 46 | // create an item 47 | const name = "sample document"; 48 | const { resource: item1 } = await container.items.create({ 49 | id: "a", 50 | name, 51 | foo: "bar", 52 | key: "value" 53 | }); 54 | 55 | assert.equal(item1.name, name); 56 | 57 | // read an item 58 | const { resource: item2 } = await container.item(item1.id, undefined).read(); 59 | assert.equal(item2.id, item1.id); 60 | 61 | // upsert an item 62 | const { resource: item3 } = await container.items.upsert({ 63 | id: "b", 64 | name: "sample document", 65 | foo: "bar", 66 | key: "value" 67 | }); 68 | assert.equal(item3.name, name); 69 | 70 | // replace an item 71 | const newProp = "baz"; 72 | const { resource: item4 } = await container.item("a", undefined).replace({ 73 | id: "a", 74 | newProp 75 | }); 76 | assert.equal(item4.newProp, newProp); 77 | 78 | // read documents after creation 79 | const { resources: documents } = await container.items.readAll().fetchAll(); 80 | assert.equal(documents.length, 2, "create should increase the number of documents"); 81 | 82 | // query documents 83 | const { resources: results } = await container.items.query("SELECT * FROM root r").fetchAll(); 84 | assert(results.length === 2, "Container should contain two items"); 85 | 86 | // delete a document 87 | await container.item(item1.id, undefined).delete(); 88 | 89 | // read documents after deletion 90 | const response = await container.item(item1.id, undefined).read(); 91 | assert.equal(response.statusCode, StatusCodes.NotFound); 92 | assert.equal(response.resource, undefined); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/functional/spatial.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { Database, DataType, IndexKind } from "../../dist-esm"; 3 | import { createOrUpsertItem, getTestDatabase, removeAllDatabases } from "../common/TestHelpers"; 4 | 5 | describe("Spatial Indexes", function() { 6 | this.timeout(process.env.MOCHA_TIMEOUT || 10000); 7 | beforeEach(async function() { 8 | await removeAllDatabases(); 9 | }); 10 | 11 | const spatialIndexTest = async function(isUpsertTest: boolean) { 12 | // create database 13 | const database: Database = await getTestDatabase("validate spatial index"); 14 | 15 | // create container using an indexing policy with spatial index. 16 | const indexingPolicy = { 17 | includedPaths: [ 18 | { 19 | path: '/"Location"/?', 20 | indexes: [ 21 | { 22 | kind: IndexKind.Spatial, 23 | dataType: DataType.Point 24 | } 25 | ] 26 | }, 27 | { 28 | path: "/" 29 | } 30 | ] 31 | }; 32 | const entropy = Math.floor(Math.random() * 10000); 33 | const { resource: containerDef } = await database.containers.create({ 34 | id: `sample container${entropy}`, 35 | indexingPolicy 36 | }); 37 | const container = database.container(containerDef.id); 38 | 39 | const location1 = { 40 | id: "location1", 41 | Location: { 42 | type: "Point", 43 | coordinates: [20.0, 20.0] 44 | } 45 | }; 46 | await createOrUpsertItem(container, location1, undefined, isUpsertTest); 47 | const location2 = { 48 | id: "location2", 49 | Location: { 50 | type: "Point", 51 | coordinates: [100.0, 100.0] 52 | } 53 | }; 54 | await createOrUpsertItem(container, location2, undefined, isUpsertTest); 55 | const query = 56 | "SELECT * FROM root WHERE (ST_DISTANCE(root.Location, {type: 'Point', coordinates: [20.1, 20]}) < 20000) "; 57 | const { resources: results } = await container.items.query(query).fetchAll(); 58 | assert.equal(1, results.length); 59 | assert.equal("location1", results[0].id); 60 | }; 61 | 62 | it("nativeApi Should support spatial index name based", async function() { 63 | await spatialIndexTest(false); 64 | }); 65 | 66 | it("nativeApi Should support spatial index name based with upsert", async function() { 67 | await spatialIndexTest(true); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/functional/udf.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { UserDefinedFunctionDefinition, Container } from "../../dist-esm/client"; 3 | import { removeAllDatabases, getTestContainer } from "../common/TestHelpers"; 4 | 5 | describe("User Defined Function", function() { 6 | this.timeout(process.env.MOCHA_TIMEOUT || 10000); 7 | let container: Container; 8 | 9 | beforeEach(async function() { 10 | await removeAllDatabases(); 11 | // get container 12 | container = await getTestContainer("UDFTests"); 13 | }); 14 | it("nativeApi Should do UDF CRUD operations successfully", async function() { 15 | const { resources: udfs } = await container.scripts.userDefinedFunctions.readAll().fetchAll(); 16 | 17 | // create a udf 18 | const beforeCreateUdfsCount = udfs.length; 19 | const udfDefinition: UserDefinedFunctionDefinition = { 20 | id: "sample udf", 21 | body: "function () { const x = 10; }" 22 | }; 23 | 24 | // TODO also handle upsert case 25 | const { resource: udf } = await container.scripts.userDefinedFunctions.create(udfDefinition); 26 | 27 | assert.equal(udf.id, udfDefinition.id); 28 | assert.equal(udf.body, "function () { const x = 10; }"); 29 | 30 | // read udfs after creation 31 | const { resources: udfsAfterCreate } = await container.scripts.userDefinedFunctions.readAll().fetchAll(); 32 | assert.equal(udfsAfterCreate.length, beforeCreateUdfsCount + 1, "create should increase the number of udfs"); 33 | 34 | // query udfs 35 | const querySpec = { 36 | query: "SELECT * FROM root r WHERE r.id=@id", 37 | parameters: [ 38 | { 39 | name: "@id", 40 | value: udfDefinition.id 41 | } 42 | ] 43 | }; 44 | const { resources: results } = await container.scripts.userDefinedFunctions.query(querySpec).fetchAll(); 45 | assert(results.length > 0, "number of results for the query should be > 0"); 46 | 47 | // replace udf 48 | udfDefinition.body = "function () { const x = 10; }"; 49 | const { resource: replacedUdf } = await container.scripts 50 | .userDefinedFunction(udfDefinition.id) 51 | .replace(udfDefinition); 52 | 53 | assert.equal(replacedUdf.id, udfDefinition.id); 54 | assert.equal(replacedUdf.body, "function () { const x = 10; }"); 55 | 56 | // read udf 57 | const { resource: udfAfterReplace } = await container.scripts.userDefinedFunction(replacedUdf.id).read(); 58 | 59 | assert.equal(replacedUdf.id, udfAfterReplace.id); 60 | 61 | // delete udf 62 | const { resource: res } = await container.scripts.userDefinedFunction(replacedUdf.id).delete(); 63 | 64 | // read udfs after deletion 65 | try { 66 | const { resource: badudf } = await container.scripts.userDefinedFunction(replacedUdf.id).read(); 67 | assert.fail("Must fail to read after delete"); 68 | } catch (err) { 69 | const notFoundErrorCode = 404; 70 | assert.equal(err.code, notFoundErrorCode, "response should return error code 404"); 71 | } 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/functional/user.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { UserDefinition } from "../../dist-esm/client"; 3 | import { createOrUpsertUser, getTestDatabase, removeAllDatabases } from "../common/TestHelpers"; 4 | 5 | describe("NodeJS CRUD Tests", function() { 6 | this.timeout(process.env.MOCHA_TIMEOUT || 10000); 7 | beforeEach(async function() { 8 | await removeAllDatabases(); 9 | }); 10 | describe("Validate User CRUD", function() { 11 | const userCRUDTest = async function(isUpsertTest: boolean) { 12 | // create database 13 | const database = await getTestDatabase("Validate user CRUD"); 14 | 15 | // list users 16 | const { resources: users } = await database.users.readAll().fetchAll(); 17 | assert.equal(users.constructor, Array, "Value should be an array"); 18 | const beforeCreateCount = users.length; 19 | 20 | // create user 21 | const { resource: userDef } = await createOrUpsertUser(database, { id: "new user" }, undefined, isUpsertTest); 22 | assert.equal(userDef.id, "new user", "user name error"); 23 | let user = database.user(userDef.id); 24 | 25 | // list users after creation 26 | const { resources: usersAfterCreation } = await database.users.readAll().fetchAll(); 27 | assert.equal(usersAfterCreation.length, beforeCreateCount + 1); 28 | 29 | // query users 30 | const querySpec = { 31 | query: "SELECT * FROM root r WHERE r.id=@id", 32 | parameters: [ 33 | { 34 | name: "@id", 35 | value: "new user" 36 | } 37 | ] 38 | }; 39 | const { resources: results } = await database.users.query(querySpec).fetchAll(); 40 | assert(results.length > 0, "number of results for the query should be > 0"); 41 | 42 | // replace user 43 | userDef.id = "replaced user"; 44 | let replacedUser: UserDefinition; 45 | if (isUpsertTest) { 46 | const r = await database.users.upsert(userDef); 47 | replacedUser = r.resource; 48 | } else { 49 | const r = await user.replace(userDef); 50 | replacedUser = r.resource; 51 | } 52 | assert.equal(replacedUser.id, "replaced user", "user name should change"); 53 | assert.equal(userDef.id, replacedUser.id, "user id should stay the same"); 54 | user = database.user(replacedUser.id); 55 | 56 | // read user 57 | const { resource: userAfterReplace } = await user.read(); 58 | assert.equal(replacedUser.id, userAfterReplace.id); 59 | 60 | // delete user 61 | const { resource: res } = await user.delete(); 62 | 63 | // read user after deletion 64 | try { 65 | await user.read(); 66 | assert.fail("Must fail to read user after deletion"); 67 | } catch (err) { 68 | const notFoundErrorCode = 404; 69 | assert.equal(err.code, notFoundErrorCode, "response should return error code 404"); 70 | } 71 | }; 72 | 73 | it("nativeApi Should do User CRUD operations successfully name based", async function() { 74 | await userCRUDTest(false); 75 | }); 76 | 77 | it("nativeApi Should do User CRUD operations successfully name based with upsert", async function() { 78 | await userCRUDTest(true); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/integration/extractPartitionKey.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { extractPartitionKey } from "../../dist-esm/extractPartitionKey"; 3 | 4 | describe("extractPartitionKey", function() { 5 | describe("With undefined partitionKeyDefinition", function() { 6 | it("should return undefined", function() { 7 | const document: any = {}; 8 | const result = extractPartitionKey(document, undefined); 9 | assert.equal(result, undefined); 10 | }); 11 | }); 12 | 13 | describe("With a defined partitionKeyDefinition", function() { 14 | const partitionKeyDefinition = { paths: ["/a/b"] }; 15 | const migratedPartitionKeyDefinition = { paths: ["/_partitionKey"], isSystemKey: true }; 16 | 17 | it("should return [{}] when document has no partition key value", function() { 18 | const document = {}; 19 | const result = extractPartitionKey(document, partitionKeyDefinition); 20 | assert.deepEqual(result, [{}]); 21 | }); 22 | 23 | it("should return [] when container is migrated from non-partitioned and document has no partition key value", function() { 24 | const document = {}; 25 | const result = extractPartitionKey(document, migratedPartitionKeyDefinition); 26 | assert.deepEqual(result, [{}]); 27 | }); 28 | 29 | it("should return [null] when document has a null partition key value", function() { 30 | const document: any = { a: { b: null } }; 31 | const result = extractPartitionKey(document, partitionKeyDefinition); 32 | assert.deepEqual(result, [null]); 33 | }); 34 | 35 | it("should return [{}] when document has a partially defined partition key value", function() { 36 | const document = { a: "some value" }; 37 | const result = extractPartitionKey(document, partitionKeyDefinition); 38 | assert.deepEqual(result, [{}]); 39 | }); 40 | 41 | it("should return [value] when document has a valid partition key value", function() { 42 | const document = { a: { b: "some value" } }; 43 | const result = extractPartitionKey(document, partitionKeyDefinition); 44 | assert.deepEqual(result, ["some value"]); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/integration/multiregion.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | 3 | import { CosmosClient } from "../../dist-esm/CosmosClient"; 4 | import { DatabaseAccount } from "../../dist-esm/documents"; 5 | 6 | import { endpoint, masterKey } from "../common/_testConfig"; 7 | 8 | // This test requires a multi-region write enabled account with at least two regions. 9 | (process.env.TESTS_MULTIREGION ? describe : describe.skip)("Multi-region tests", function() { 10 | this.timeout(process.env.MOCHA_TIMEOUT || "30000"); 11 | let PreferredLocations: string[] = []; 12 | let dbAccount: DatabaseAccount; 13 | 14 | before(async function() { 15 | const client = new CosmosClient({ endpoint, key: masterKey }); 16 | ({ resource: dbAccount } = await client.getDatabaseAccount()); 17 | // We reverse the order of the preferred locations list to make sure 18 | // we don't just follow the order we got back from the server 19 | PreferredLocations = dbAccount.readableLocations.map(v => v.name).reverse(); 20 | assert( 21 | PreferredLocations.length > 1, 22 | "Not a multi-region account. Please add a region before running this test again." 23 | ); 24 | }); 25 | 26 | it("Preferred locations should be honored for readEndpoint", async function() { 27 | const client = new CosmosClient({ 28 | endpoint, 29 | key: masterKey, 30 | connectionPolicy: { preferredLocations: PreferredLocations } 31 | }); 32 | const currentReadEndpoint = await client.getReadEndpoint(); 33 | assert( 34 | currentReadEndpoint.includes(PreferredLocations[0].toLowerCase().replace(/ /g, "")), 35 | "The readendpoint should be the first preferred location" 36 | ); 37 | }); 38 | 39 | it("Preferred locations should be honored for writeEndpoint", async function() { 40 | assert( 41 | dbAccount.enableMultipleWritableLocations, 42 | "MultipleWriteableLocations must be set on your database account for this test to work" 43 | ); 44 | const client = new CosmosClient({ 45 | endpoint, 46 | key: masterKey, 47 | connectionPolicy: { 48 | preferredLocations: PreferredLocations, 49 | useMultipleWriteLocations: true 50 | } 51 | }); 52 | const currentWriteEndpoint = await client.getWriteEndpoint(); 53 | assert( 54 | currentWriteEndpoint.includes(PreferredLocations[0].toLowerCase().replace(/ /g, "")), 55 | "The writeendpoint should be the first preferred location" 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/integration/proxy.spec.ts: -------------------------------------------------------------------------------- 1 | import * as http from "http"; 2 | import * as net from "net"; 3 | import { URL } from "url"; 4 | import ProxyAgent from "proxy-agent"; 5 | import { CosmosClient } from "../../dist-esm/index"; 6 | import { endpoint, masterKey } from "../common/_testConfig"; 7 | import { addEntropy } from "../common/TestHelpers"; 8 | 9 | const isBrowser = new Function("try {return this===window;}catch(e){ return false;}"); 10 | if (!isBrowser()) { 11 | describe("Validate http proxy setting in environment variable", function() { 12 | const proxy = http.createServer((req, resp) => { 13 | resp.writeHead(200, { "Content-Type": "text/plain" }); 14 | resp.end(); 15 | }); 16 | 17 | proxy.on("connect", (req, clientSocket, head) => { 18 | const serverUrl = new URL(`http://${req.url}`); 19 | const serverSocket = net.connect( 20 | parseInt(serverUrl.port, 10), 21 | serverUrl.hostname, 22 | () => { 23 | clientSocket.write("HTTP/1.1 200 Connection Established\r\n" + "Proxy-agent: Node.js-Proxy\r\n" + "\r\n"); 24 | serverSocket.write(head); 25 | serverSocket.pipe(clientSocket); 26 | clientSocket.pipe(serverSocket); 27 | } 28 | ); 29 | }); 30 | 31 | const proxyPort = 8989; 32 | const agent = new ProxyAgent(`http://127.0.0.1:${8989}`); 33 | 34 | it("nativeApi Client Should successfully execute request", async function() { 35 | return new Promise((resolve, reject) => { 36 | proxy.listen(proxyPort, "127.0.0.1", async () => { 37 | try { 38 | const client = new CosmosClient({ 39 | endpoint, 40 | key: masterKey, 41 | agent 42 | }); 43 | // create database 44 | await client.databases.create({ 45 | id: addEntropy("ProxyTest") 46 | }); 47 | resolve(); 48 | } catch (err) { 49 | throw err; 50 | } finally { 51 | proxy.close(); 52 | } 53 | }); 54 | }); 55 | }); 56 | 57 | it("nativeApi Client Should execute request in error while the proxy setting is not correct", async function() { 58 | this.timeout(process.env.MOCHA_TIMEOUT || 30000); 59 | return new Promise((resolve, reject) => { 60 | proxy.listen(proxyPort + 1, "127.0.0.1", async () => { 61 | try { 62 | const client = new CosmosClient({ 63 | endpoint, 64 | key: masterKey, 65 | agent 66 | }); 67 | // create database 68 | await client.databases.create({ 69 | id: addEntropy("ProxyTest") 70 | }); 71 | reject(new Error("Should create database in error while the proxy setting is not correct")); 72 | } catch (err) { 73 | resolve(); 74 | } finally { 75 | proxy.close(); 76 | } 77 | }); 78 | }); 79 | }); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /test/integration/query.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { FeedOptions } from "../../dist-esm"; 3 | import { getTestContainer, getTestDatabase, removeAllDatabases } from "../common/TestHelpers"; 4 | 5 | const doc = { id: "myId", pk: "pk" }; 6 | 7 | describe("ResourceLink Trimming of leading and trailing slashes", function() { 8 | this.timeout(process.env.MOCHA_TIMEOUT || 10000); 9 | const containerId = "testcontainer"; 10 | 11 | beforeEach(async function() { 12 | await removeAllDatabases(); 13 | }); 14 | 15 | it("validate correct execution of query using named container link with leading and trailing slashes", async function() { 16 | const containerDefinition = { 17 | id: containerId, 18 | partitionKey: { 19 | paths: ["/pk"] 20 | } 21 | }; 22 | const containerOptions = { offerThroughput: 10100 }; 23 | 24 | const container = await getTestContainer( 25 | "validate correct execution of query", 26 | undefined, 27 | containerDefinition, 28 | containerOptions 29 | ); 30 | 31 | await container.items.create(doc); 32 | const query = "SELECT * from " + containerId; 33 | const queryIterator = container.items.query(query); 34 | 35 | const { resources } = await queryIterator.fetchAll(); 36 | assert.equal(resources[0]["id"], "myId"); 37 | }); 38 | }); 39 | 40 | describe("Test Query Metrics", function() { 41 | this.timeout(process.env.MOCHA_TIMEOUT || 20000); 42 | const collectionId = "testCollection2"; 43 | 44 | const testQueryMetricsOnSinglePartition = async function(document: any) { 45 | try { 46 | const database = await getTestDatabase("query metrics test db"); 47 | 48 | const collectionDefinition = { id: collectionId }; 49 | const collectionOptions = { offerThroughput: 4000 }; 50 | 51 | const { resource: createdCollectionDef } = await database.containers.create( 52 | collectionDefinition, 53 | collectionOptions 54 | ); 55 | const createdContainer = database.container(createdCollectionDef.id); 56 | 57 | await createdContainer.items.create(document); 58 | const query = "SELECT * from " + collectionId; 59 | const queryOptions: FeedOptions = { populateQueryMetrics: true }; 60 | const queryIterator = createdContainer.items.query(query, queryOptions); 61 | 62 | while (queryIterator.hasMoreResults()) { 63 | const { resources: results, queryMetrics, activityId, requestCharge } = await queryIterator.fetchNext(); 64 | assert(activityId, "activityId must exist"); 65 | assert(requestCharge, "requestCharge must exist"); 66 | 67 | if (results === undefined) { 68 | // no more results 69 | break; 70 | } 71 | 72 | assert.notEqual(queryMetrics, null); 73 | } 74 | } catch (err) { 75 | throw err; 76 | } 77 | }; 78 | 79 | afterEach(async function() { 80 | await removeAllDatabases(); 81 | }); 82 | 83 | beforeEach(async function() { 84 | await removeAllDatabases(); 85 | }); 86 | 87 | it("validate that query metrics are correct for a single partition query", async function() { 88 | await testQueryMetricsOnSinglePartition(doc); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/integration/split.spec.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "../../dist-esm/client"; 2 | import { bulkInsertItems, getTestContainer, removeAllDatabases } from "../common/TestHelpers"; 3 | import { Constants, CosmosClient, PluginOn, CosmosClientOptions, PluginConfig } from "../../dist-esm"; 4 | import { masterKey, endpoint } from "../common/_testConfig"; 5 | import { SubStatusCodes } from "../../dist-esm/common"; 6 | import assert from "assert"; 7 | 8 | const splitError = new Error("Fake Partition Split") as any; 9 | splitError.code = 410; 10 | splitError.substatus = SubStatusCodes.PartitionKeyRangeGone; 11 | 12 | const generateDocuments = function(docSize: number) { 13 | const docs = []; 14 | for (let i = 0; i < docSize; i++) { 15 | docs.push({ id: i.toString() }); 16 | } 17 | return docs; 18 | }; 19 | 20 | const documentDefinitions = generateDocuments(20); 21 | 22 | describe("Partition Splits", () => { 23 | let container: Container; 24 | 25 | before(async function() { 26 | await removeAllDatabases(); 27 | container = await getTestContainer( 28 | "Partition Splits", 29 | undefined, 30 | { 31 | id: "partitionSplits", 32 | partitionKey: { 33 | paths: ["/id"] 34 | } 35 | }, 36 | { offerThroughput: 25100 } 37 | ); 38 | await bulkInsertItems(container, documentDefinitions); 39 | }); 40 | 41 | it("handles one split part way through iteration", async () => { 42 | let hasSplit = false; 43 | const partitionKeyRanges = new Set(); 44 | const options: CosmosClientOptions = { endpoint, key: masterKey }; 45 | const plugins: PluginConfig[] = [ 46 | { 47 | on: PluginOn.request, 48 | plugin: async (context, next) => { 49 | const partitionKeyRangeId = context.headers[Constants.HttpHeaders.PartitionKeyRangeID]; 50 | if (partitionKeyRanges.has(partitionKeyRangeId) && hasSplit === false) { 51 | hasSplit = true; 52 | const error = new Error("Fake Partition Split") as any; 53 | error.code = 410; 54 | error.substatus = SubStatusCodes.PartitionKeyRangeGone; 55 | throw error; 56 | } 57 | if (partitionKeyRangeId) { 58 | partitionKeyRanges.add(partitionKeyRangeId); 59 | } 60 | return next(context); 61 | } 62 | } 63 | ]; 64 | const client = new CosmosClient({ 65 | ...options, 66 | plugins 67 | } as any); 68 | const { resources } = await client 69 | .database(container.database.id) 70 | .container(container.id) 71 | .items.query("SELECT * FROM root r", { maxItemCount: 2, maxDegreeOfParallelism: 1 }) 72 | .fetchAll(); 73 | 74 | // TODO. These should be equal but right now they are not 75 | // I suspect injecting a random 410 with out actually splitting the documents 76 | // results in duplicates by trying to read from two partitions 77 | assert(resources.length >= documentDefinitions.length); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/integration/sslVerification.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { CosmosClient } from "../../dist-esm"; 3 | import { getTestDatabase } from "../common/TestHelpers"; 4 | import https from "https"; 5 | 6 | const endpoint = "https://localhost:8081"; 7 | const masterKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; 8 | 9 | describe("Validate SSL verification check for emulator", function() { 10 | it("should throw exception", async function() { 11 | try { 12 | const client = new CosmosClient({ endpoint, key: masterKey }); 13 | // create database 14 | await getTestDatabase("ssl verification", client); 15 | } catch (err) { 16 | // connecting to emulator should throw SSL verification error, 17 | assert.equal(err.code, "DEPTH_ZERO_SELF_SIGNED_CERT", "client should throw exception"); 18 | } 19 | }); 20 | 21 | it("disable ssl check via agent", async function() { 22 | const client = new CosmosClient({ 23 | endpoint, 24 | key: masterKey, 25 | agent: new https.Agent({ 26 | rejectUnauthorized: false 27 | }) 28 | }); 29 | 30 | // create database 31 | await getTestDatabase("ssl verification", client); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | Follow these instructions to run the tests locally. 2 | 3 | ## Prerequisites 4 | 5 | 1. Clone Azure/azure-documentdb-node repository 6 | Please clone the source and tests from [https://github.com/Azure/azure-documentdb-node](https://github.com/Azure/azure-documentdb-node) 7 | 8 | 2. Install Node.js and npm 9 | [https://docs.npmjs.com/getting-started/installing-node](https://docs.npmjs.com/getting-started/installing-node) 10 | 11 | 3. Install mocha package globally 12 | > npm install -g mocha 13 | 14 | ## Running the tests 15 | Using your command-line tool, from the root of your local copy of azure-documentdb-node repository: 16 | If you are contributing changes and submitting PR then you need to ensure that you run the tests against your local copy of the source, and not the published npm package. 17 | 18 | If you just want to run the tests against the published npm package then skip steps #1 & #2 proceed directly to step #3 19 | 20 | 1. Remove documentdb, if previously installed 21 | > npm remove documentdb 22 | 23 | 2. Install documentdb 24 | > npm install source 25 | 26 | 3. Change to `test` directory 27 | > cd test 28 | 29 | 3. Run the tests 30 | > mocha -t 0 -R spec 31 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "noImplicitAny": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "preserveConstEnums": true, 11 | "removeComments": false, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "newLine": "LF", 15 | "composite": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "only-arrow-functions": false, 5 | "space-before-function-paren": false, 6 | "no-console": false, 7 | "no-implicit-dependencies": [true, "dev"], 8 | "import-blacklist": false, 9 | "no-invalid-this": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/types/proxy-agent.ts: -------------------------------------------------------------------------------- 1 | declare module "proxy-agent" { 2 | import { Agent } from "http"; 3 | interface ProxyAgentConstructor { 4 | (options: string): Agent; 5 | new (options: string): Agent; 6 | } 7 | 8 | const proxy: ProxyAgentConstructor; 9 | 10 | export = proxy; 11 | } 12 | -------------------------------------------------------------------------------- /test/unit/defaultQueryExecutionContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { FetchFunctionCallback, DefaultQueryExecutionContext } from "../../dist-esm/queryExecutionContext"; 2 | import { FeedOptions } from "../../dist-esm"; 3 | import assert from "assert"; 4 | import { sleep, Constants } from "../../dist-esm/common"; 5 | 6 | describe("defaultQueryExecutionContext", function() { 7 | it("should not buffer items if bufferItems is false", async function() { 8 | let calledCount = 0; 9 | const fetchFunction: FetchFunctionCallback = async () => { 10 | calledCount++; 11 | return { 12 | code: 200, 13 | headers: { 14 | "x-ms-continuation": "any random text" 15 | }, 16 | result: [ 17 | { 18 | item: "foo" 19 | } 20 | ], 21 | substatus: 0 22 | }; 23 | }; 24 | 25 | const options: FeedOptions = { 26 | bufferItems: false 27 | }; 28 | 29 | const context = new DefaultQueryExecutionContext(options, fetchFunction); 30 | 31 | assert.strictEqual(calledCount, 0, "Nothing should be fetched at this point"); 32 | 33 | await context.fetchMore(); 34 | 35 | await sleep(10); //small sleep to make sure we give up event loop so any other fetch functions can get called 36 | 37 | assert.strictEqual(calledCount, 1, "Should have only fetched 1 page"); 38 | 39 | await context.fetchMore(); 40 | 41 | await sleep(10); //small sleep to make sure we give up event loop so any other fetch functions can get called 42 | 43 | assert.strictEqual(calledCount, 2, "Should have only fetched 2 pages"); 44 | }); 45 | 46 | it("should buffer items if bufferItems is true", async function() { 47 | let calledCount = 0; 48 | const fetchFunction: FetchFunctionCallback = async () => { 49 | calledCount++; 50 | return { 51 | code: 200, 52 | headers: { 53 | "x-ms-continuation": "any random text" 54 | }, 55 | result: [ 56 | { 57 | item: "foo" 58 | } 59 | ], 60 | substatus: 0 61 | }; 62 | }; 63 | 64 | const options: FeedOptions = { 65 | bufferItems: true 66 | }; 67 | 68 | const context = new DefaultQueryExecutionContext(options, fetchFunction); 69 | 70 | assert.strictEqual(calledCount, 0, "Nothing should be fetched at this point"); 71 | 72 | await context.fetchMore(); 73 | 74 | await sleep(10); //small sleep to make sure we give up event loop so any other fetch functions can get called 75 | 76 | assert.strictEqual(calledCount, 2, "Should have fetched 2 pages (one buffered)"); 77 | 78 | await context.fetchMore(); 79 | 80 | await sleep(10); //small sleep to make sure we give up event loop so any other fetch functions can get called 81 | 82 | assert.strictEqual(calledCount, 3, "Should have only fetched 3 pages (one buffered)"); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/unit/helper.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { isResourceValid, parseConnectionString } from "../../dist-esm/common"; 3 | 4 | describe("Helper methods", function() { 5 | describe("isResourceValid Unit Tests", function() { 6 | it("id is not string", function() { 7 | const err = {}; 8 | const result = isResourceValid({ id: 1 }, err); 9 | 10 | assert.equal(result, false); 11 | assert.deepEqual(err, { message: "Id must be a string." }); 12 | }); 13 | }); 14 | describe("parseConnectionString", function() { 15 | it("parses a valid connection string", function() { 16 | const connectionString = 17 | "AccountEndpoint=https://test-account.documents.azure.com:443/;AccountKey=c213asdasdefgdfgrtweaYPpgoeCsHbpRTHhxuMsTaw==;"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Not a real key")] 18 | const connectionObject = parseConnectionString(connectionString); 19 | 20 | assert.equal(connectionObject.endpoint, "https://test-account.documents.azure.com:443/"); 21 | assert.equal(connectionObject.key, "c213asdasdefgdfgrtweaYPpgoeCsHbpRTHhxuMsTaw=="); // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Not a real key")] 22 | }); 23 | it("throws on invalid connection string", function() { 24 | const connectionString = "asdqweqsdfd==;==sfd;asdqwe;asdqwe"; 25 | assert.throws(() => parseConnectionString(connectionString)); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/platform.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { Constants } from "../../dist-esm/index"; 3 | import { getUserAgent } from "../../dist-esm/common"; 4 | 5 | const packageJson = require("../../package.json"); 6 | const packageVersion = packageJson["version"]; 7 | const constantVersion = Constants.SDKVersion; 8 | 9 | const userAgent = getUserAgent(); 10 | 11 | describe("getUserAgent", function() { 12 | it("should contain the current SDK version", () => { 13 | assert(userAgent.includes(packageVersion)); 14 | }); 15 | 16 | it("should contain the current node version", () => { 17 | assert(userAgent.includes(process.version.replace("v", ""))); 18 | }); 19 | }); 20 | 21 | describe("Version", function() { 22 | it("should have matching constant version & package version", function() { 23 | assert.equal(constantVersion, packageVersion, "Package.json and Constants.SDKVersion don't match"); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": {}, 3 | "files": [], 4 | "references": [{ "path": "./src" }, { "path": "./test" }] 5 | } 6 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "exclude": "./node_modules", 4 | "rules": { 5 | "interface-name": false, 6 | "no-string-literal": false, 7 | "object-literal-sort-keys": false, 8 | "member-ordering": false, // TODO: might want to look at this eventually... 9 | "no-floating-promises": true, 10 | "import-blacklist": [true, "assert", "util"], 11 | "no-unnecessary-class": true, 12 | "no-invalid-this": true 13 | }, 14 | "linterOptions": { 15 | "exclude": ["*.json"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /writeSDKVersion.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const version = require("./package.json").version; 3 | const fileName = "./dist-esm/common/constants.js"; 4 | let contents = fs.readFileSync(fileName, "utf8"); 5 | contents = contents.replace("REPLACE_SDK_VERSION", version); 6 | fs.writeFileSync(fileName, contents); 7 | --------------------------------------------------------------------------------