├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── esnext-async-iterators.d.ts ├── package.json ├── src ├── Client.ts ├── Collection.ts ├── Database.ts ├── DocumentStream.ts ├── Util.ts ├── _DocumentDB.ts ├── index.ts └── tsconfig.json └── typings-compat.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Do not store transpiler output 2 | typings/ 3 | dist/ 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # node-waf configuration 28 | .lock-wscript 29 | 30 | # Compiled binary addons (http://nodejs.org/api/addons.html) 31 | build/Release 32 | 33 | # Dependency directories 34 | node_modules 35 | jspm_packages 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | *.log 4 | npm-debug.log* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jelmer Cormont 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # documentdb-typescript 2 | 3 | This package is no longer maintained. 4 | 5 | Please use the official CosmosDB Node API instead. 6 | 7 | -------------------------------------------------------------------------------- /esnext-async-iterators.d.ts: -------------------------------------------------------------------------------- 1 | // These declarations are extracted from the Typescript ESNext lib files, 2 | // so that this module can still be used with ES5 / TS < 2.3 3 | 4 | interface SymbolConstructor { 5 | /** 6 | * A method that returns the default async iterator for an object. Called by the semantics of 7 | * the for-await-of statement. 8 | */ 9 | readonly asyncIterator: symbol; 10 | } 11 | 12 | declare var Symbol: SymbolConstructor; 13 | 14 | interface IteratorResult { 15 | done: boolean; 16 | value: T; 17 | } 18 | 19 | interface AsyncIterator { 20 | next(value?: any): Promise>; 21 | return?(value?: any): Promise>; 22 | throw?(e?: any): Promise>; 23 | } 24 | 25 | interface AsyncIterable { 26 | [Symbol.asyncIterator](): AsyncIterator; 27 | } 28 | 29 | interface AsyncIterableIterator extends AsyncIterator { 30 | [Symbol.asyncIterator](): AsyncIterableIterator; 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentdb-typescript", 3 | "version": "1.0.7", 4 | "description": "TypeScript API for Microsoft Azure DocumentDB", 5 | "keywords": [ 6 | "DocumentDB", 7 | "DocDB", 8 | "Azure", 9 | "TypeScript" 10 | ], 11 | "main": "./dist/index.js", 12 | "typings": "./typings-compat.d.ts", 13 | "scripts": { 14 | "build": "tsc -p src", 15 | "prepublish": "tsc -p src" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/jcormont/documentdb-typescript.git" 20 | }, 21 | "author": "Jelmer Cormont", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/jcormont/documentdb-typescript/issues" 25 | }, 26 | "homepage": "https://github.com/jcormont/documentdb-typescript#readme", 27 | "dependencies": { 28 | "documentdb": "^1.12.0" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^7.0.8", 32 | "typescript": "^2.3.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Client.ts: -------------------------------------------------------------------------------- 1 | import * as _DocumentDB from "./_DocumentDB"; 2 | import { curryPromise, sleepAsync } from "./Util"; 3 | import { Database } from "./Database"; 4 | 5 | /** List of opened/opening clients for specific endpoint/key combinations */ 6 | var _openClients = new Map(); 7 | 8 | /** Next UID */ 9 | var _uid = 1; 10 | 11 | /** Represents a DocumentDB endpoint */ 12 | export class Client { 13 | constructor(url?: string, masterKey = "") { 14 | this.url = url || ""; 15 | this.authenticationOptions = { masterKey }; 16 | } 17 | 18 | /** Global concurrency limit for all server requests */ 19 | public static concurrencyLimit = 25; 20 | 21 | /** Set to true to log all requests to the console */ 22 | public enableConsoleLog = false; 23 | 24 | /** Timeout (ms) used for all requests (defaults to 40s) */ 25 | public timeout = 40000; 26 | 27 | /** The endpoint URL to connect to */ 28 | public url: string; 29 | 30 | /** The authentication object used to connect to this endpoint */ 31 | public authenticationOptions: _DocumentDB.AuthenticationOptions; 32 | 33 | /** The connection policy used for the connection to this endpoint, if specified; note that the retry policy here is implemented at a lower level than the maxRetries parameter that can be specified when calling some of this module's methods */ 34 | public connectionPolicy: _DocumentDB.ConnectionPolicy | undefined; 35 | 36 | /** The consistency level (string: "Strong" | "BoundedStaleness" | "Session" | "Eventual") used for the connection to this endpoint, if specified */ 37 | public consistencyLevel: _DocumentDB.ConsistencyLevel | undefined; 38 | 39 | /** The native DocumentClient instance; throws an error if this client is currently not connected (check using .isOpen, or await .openAsync() first) */ 40 | public get documentClient(): _DocumentDB.DocumentClient { 41 | if (this._closed) throw new Error("Client already closed"); 42 | if (!this._client) throw new Error("Document DB client is not connected"); 43 | return this._client; 44 | } 45 | 46 | /** Returns true if this client is currently connected through a native DocumentClient instance */ 47 | public get isOpen() { return !!this._client && !this._closed } 48 | 49 | /** Connect to the endpoint represented by this client and validate the connection, unless already connected */ 50 | public openAsync(maxRetries = 3): PromiseLike { 51 | if (this._closed) throw new Error("Client already closed"); 52 | if (this._open) return this._open; 53 | 54 | // check if another instance is already connected/-ing 55 | var key = this.url + ":" + 56 | JSON.stringify(this.authenticationOptions) + ":" + 57 | JSON.stringify(this.connectionPolicy) + ":" + 58 | this.consistencyLevel; 59 | if (_openClients.has(key)) { 60 | var other = _openClients.get(key)!; 61 | this._client = other._client; 62 | this._databaseResources = other._databaseResources; 63 | return this._open = other._open!; 64 | } 65 | _openClients.set(key, this); 66 | 67 | // create a new DocumentClient instance 68 | this._client = new _DocumentDB.DocumentClient(this.url, 69 | this.authenticationOptions, this.connectionPolicy, 70 | this.consistencyLevel); 71 | 72 | // return a promise that resolves when databases are read 73 | return this._open = new Promise(resolve => { 74 | let tryConnect = (callback: (err: any, result: any) => void) => 75 | this.log("Connecting to " + this.url) && 76 | this._client!.readDatabases({ maxItemCount: 1000 }) 77 | .toArray(callback); 78 | resolve(curryPromise(tryConnect, this.timeout, maxRetries)() 79 | .then(dbs => { this._resolve_databases!(dbs) })); 80 | }); 81 | } 82 | 83 | /** Get a (cached) list of Database instances for all databases in this account */ 84 | public async listDatabasesAsync(forceReload?: boolean, maxRetries?: number) { 85 | if (!this._open) await this.openAsync(maxRetries); 86 | if (forceReload) { 87 | // create new promise for list of DB resources 88 | this._databaseResources = 89 | new Promise<_DocumentDB.DatabaseResource[]>(resolve => { 90 | this._resolve_databases = resolve; 91 | }); 92 | 93 | // read all databases again and resolve promise 94 | let tryReadDBs = (callback: (err: any, result: any) => void) => 95 | this.log("Reading list of databases") && 96 | this._client!.readDatabases({ maxItemCount: 1000 }) 97 | .toArray(callback); 98 | this._resolve_databases!( 99 | await curryPromise(tryReadDBs, this.timeout, maxRetries)()); 100 | } 101 | var databaseResources = await this._databaseResources; 102 | return databaseResources.map(r => 103 | new Database(r.id, this, r._self)); 104 | } 105 | 106 | /** @internal Create a database (and add it to the list returned by listDatabasesAsync) */ 107 | public async createDatabaseAsync(id: string, maxRetries?: number, 108 | options?: _DocumentDB.RequestOptions) { 109 | await this.openAsync(); 110 | let tryCreateDB = (callback: (err: any, result: any) => void) => 111 | this.log("Creating database: " + id) && 112 | this._client!.createDatabase({ id }, options, callback); 113 | await curryPromise(tryCreateDB, this.timeout, maxRetries)(); 114 | 115 | // reload all database resources until the created DB appears 116 | // (this is to allow for consistency less than session consistency) 117 | var times = Math.ceil(this.timeout / 100); 118 | while (times-- > 0) { 119 | var dbs = await this.listDatabasesAsync(true); 120 | if (dbs.some(db => db.id === id)) return; 121 | await sleepAsync(100); 122 | } 123 | throw new Error("Timeout"); 124 | } 125 | 126 | /** Get account information */ 127 | public async getAccountInfoAsync() { 128 | let tryGetInfo = (callback: (err: any, result: any) => void) => 129 | this.log("Getting account info") && 130 | this._client!.getDatabaseAccount(callback); 131 | return <_DocumentDB.DatabaseAccount>await curryPromise( 132 | tryGetInfo, this.timeout)(); 133 | } 134 | 135 | /** Remove the current connection; an attempt to open the same endpoint again in another instance will open and validate the connection again, but the current instance cannot be re-opened */ 136 | public close() { 137 | this._closed = true; 138 | _openClients.forEach((client, key) => { 139 | if (client === this) _openClients.delete(key); 140 | }); 141 | } 142 | 143 | /** @internal Log a message; always returns true */ 144 | public log(message: string): true { 145 | if (this.enableConsoleLog) 146 | console.log(`[${process.pid}]{${this._uid}} ${Date.now()} ${message}`); 147 | return true; 148 | } 149 | 150 | /** @internal List of databases found in the account, resolved if and when opened */ 151 | private _databaseResources = new Promise<_DocumentDB.DatabaseResource[]>(resolve => { 152 | this._resolve_databases = resolve; 153 | }); 154 | 155 | /** @internal */ 156 | private _resolve_databases?: (data: any) => void; 157 | 158 | /** @internal */ 159 | private _open?: PromiseLike; 160 | 161 | /** @internal */ 162 | private _client?: _DocumentDB.DocumentClient; 163 | 164 | /** @internal */ 165 | private _closed?: boolean; 166 | 167 | /** @internal */ 168 | private _uid = _uid++; 169 | } 170 | -------------------------------------------------------------------------------- /src/Collection.ts: -------------------------------------------------------------------------------- 1 | import * as _DocumentDB from "./_DocumentDB"; 2 | import { Database } from "./Database"; 3 | import { Client } from "./Client"; 4 | import { DocumentStream } from "./DocumentStream"; 5 | import { curryPromise, sleepAsync } from "./Util"; 6 | 7 | /** Global query ID, used to tag reads in the log */ 8 | var _queryUID = 0; 9 | 10 | /** Modes that can be used for storing resources in a collection */ 11 | export enum StoreMode { Upsert, CreateOnly, UpdateOnly, UpdateOnlyIfNoChange }; 12 | 13 | /** Combined option objects for operations that may invoke multiple network calls */ 14 | export type AllOptions = _DocumentDB.FeedOptions & _DocumentDB.RequestOptions; 15 | 16 | /** Represents a DocumentDB collection */ 17 | export class Collection { 18 | /** Refer to a collection by name, from given database */ 19 | constructor(id: string, database: Database); 20 | /** @internal Refer to a collection by name, from given database and with given link */ 21 | constructor(id: string, database: Database, selfLink: string); 22 | /** Refer to a collection by name, from given database (by name), connected through given client */ 23 | constructor(id: string, dbName: string, client: Client); 24 | /** Refer to a collection by name, from given database (by name), using given URL and master key (implicitly creates a new client instance) */ 25 | constructor(id: string, dbName: string, url?: string, masterKey?: string); 26 | constructor(id: string, ...info: any[]) { 27 | if (!id) throw new Error("Collections must have a name"); 28 | this.id = id; 29 | if (info[0] instanceof Database) { 30 | // use given database and possibly self link passed by database 31 | this.database = info[0]; 32 | this._self = info[1]; 33 | } 34 | else { 35 | // construct database with given data 36 | this.database = new Database(info[0], info[1], info[2]); 37 | } 38 | } 39 | 40 | /** The name of the collection that this instance refers to */ 41 | public readonly id: string; 42 | 43 | /** The database used for all operations */ 44 | public readonly database: Database; 45 | 46 | /** The partial resource URI for this database, i.e. `"/dbs/.../colls/..."` */ 47 | public get path() { 48 | return this._self || 49 | "dbs/" + this.database.id + "/colls/" + encodeURIComponent(this.id); 50 | } 51 | 52 | /** Open and validate the connection, check that this collection exists */ 53 | public async openAsync(maxRetries?: number, options?: AllOptions) { 54 | if (this._self) return this; 55 | await this.database.openAsync(maxRetries); 56 | let tryGetCollection = (callback: (err: any, result: any) => void) => 57 | this.database.client.log("Reading collection " + this.path) && 58 | this.database.client.documentClient.readCollection( 59 | this.path, options, callback); 60 | var resource = await curryPromise<_DocumentDB.CollectionResource>( 61 | tryGetCollection, 62 | this.database.client.timeout, maxRetries)(); 63 | this._self = resource["_self"]; 64 | return this; 65 | } 66 | 67 | /** Open and validate the connection, find or create collection resource (does not create the database) */ 68 | public async openOrCreateAsync(createThroughput?: number, 69 | indexingPolicy?: _DocumentDB.IndexingPolicy, 70 | defaultTtl?: number, maxRetries?: number, options?: AllOptions) { 71 | if (this._self) return this; 72 | await this.database.openAsync(maxRetries); 73 | var resource: _DocumentDB.CollectionResource; 74 | try { 75 | let tryGetCollection = (callback: (err: any, result: any) => void) => 76 | this.database.client.log("Reading collection " + this.id) && 77 | this.database.client.documentClient.readCollection( 78 | this.path, options, callback); 79 | resource = await curryPromise<_DocumentDB.CollectionResource>( 80 | tryGetCollection, 81 | this.database.client.timeout, maxRetries)(); 82 | } 83 | catch (err) { 84 | if (err.code == 404 /* not found */) { 85 | // create the collection now 86 | var data: any = { id: this.id }; 87 | if (indexingPolicy) data.indexingPolicy = indexingPolicy; 88 | if (defaultTtl !== undefined) data.defaultTtl = defaultTtl; 89 | try { 90 | let tryCreateCollection = (callback: (err: any, result: any) => void) => 91 | this.database.client.log("Creating collection " + this.id) && 92 | this.database.client.documentClient.createCollection( 93 | this.database.path, data, 94 | { offerThroughput: createThroughput }, 95 | callback); 96 | resource = await curryPromise<_DocumentDB.CollectionResource>( 97 | tryCreateCollection, 98 | this.database.client.timeout)(); 99 | } 100 | catch (err) { 101 | if (err.code == 409 /* conflict */) { 102 | this.database.client.log("Collection conflict, retrying..."); 103 | await sleepAsync(1000); 104 | return await this.openAsync(); 105 | } 106 | throw err; 107 | } 108 | } 109 | else throw err; 110 | } 111 | this._self = resource["_self"]; 112 | return this; 113 | } 114 | 115 | /** Open and validate the connection, find or create collection resource (also creates the database if needed) */ 116 | public async openOrCreateDatabaseAsync(createThroughput?: number, 117 | indexingPolicy?: _DocumentDB.IndexingPolicy, 118 | defaultTtl?: number, maxRetries?: number) { 119 | if (this._self) return this; 120 | await this.database.openOrCreateAsync(maxRetries); 121 | await this.openOrCreateAsync(createThroughput, indexingPolicy, 122 | defaultTtl, maxRetries); 123 | return this; 124 | } 125 | 126 | /** Get offer (throughput provisioning) information */ 127 | public async getOfferInfoAsync(maxRetries?: number, options?: AllOptions) { 128 | await this.openAsync(); 129 | let tryGetOffer = (callback: (err: any, result: any) => void) => 130 | this.database.client.log("Getting offer info for " + this.id) && 131 | this.database.client.documentClient.queryOffers({ 132 | query: "select * from root r where r.resource = @selflink", 133 | parameters: [{ name: "@selflink", value: this._self }] 134 | }, options).toArray(callback); 135 | var offers: any[] = await curryPromise( 136 | tryGetOffer, this.database.client.timeout, maxRetries)(); 137 | if (!offers.length) throw new Error("Offer not found"); 138 | this._offer = offers[0]; 139 | return <_DocumentDB.OfferResource>JSON.parse(JSON.stringify(offers[0])); 140 | } 141 | 142 | /** Set provisioned throughput */ 143 | public async setOfferInfoAsync(throughput: number) { 144 | await this.openAsync(); 145 | if (!this._offer) await this.getOfferInfoAsync(); 146 | var offer = this._offer!; 147 | if (!offer.content || !offer.content.offerThroughput) 148 | throw new Error("Unknown offer type"); 149 | offer.content.offerThroughput = throughput; 150 | let trySetOffer = (callback: (err: any, result: any) => void) => 151 | this.database.client.log("Setting offer info for " + this.id) && 152 | this.database.client.documentClient.replaceOffer( 153 | offer._self, offer, callback); 154 | this._offer = await curryPromise(trySetOffer, 155 | this.database.client.timeout)(); 156 | } 157 | 158 | /** Delete this collection */ 159 | public async deleteAsync(maxRetries?: number, options?: AllOptions) { 160 | await this.openAsync(); 161 | let tryDelete = (callback: (err: any, result: any) => void) => 162 | this.database.client.log("Deleting collection: " + this.id) && 163 | this.database.client.documentClient.deleteCollection(this._self!, 164 | options, callback); 165 | await curryPromise(tryDelete, this.database.client.timeout, 166 | maxRetries, 500, true)(); 167 | delete this._self; 168 | } 169 | 170 | /** Create or update the document with given data (must include an `.id` or `._self` property if store mode is `UpdateOnly`, and must also include an `_etag` property if store mode is `UpdateOnlyIfNoChange`); returns the stored data as a plain object, including meta properties such as `._etag` and `._self` */ 171 | public async storeDocumentAsync>( 172 | data: T & object, 173 | mode?: StoreMode, maxRetries?: number, options?: AllOptions): 174 | Promise { 175 | await this.openAsync(); 176 | if (!(data instanceof Object)) throw new TypeError(); 177 | var tryStore: (callback: (err: any, result: any) => void) => any; 178 | switch (mode) { 179 | case StoreMode.UpdateOnlyIfNoChange: 180 | if (!data._etag) throw new Error("Document _etag missing"); 181 | options = Object.assign({ 182 | accessCondition: { 183 | type: "IfMatch", 184 | condition: data._etag 185 | } 186 | }, options); 187 | // continue with update... 188 | case StoreMode.UpdateOnly: 189 | if (data.id === undefined) throw new Error("Document ID missing"); 190 | tryStore = (callback) => 191 | this.database.client.log("Replacing document: " + data.id) && 192 | this.database.client.documentClient.replaceDocument( 193 | this._getDocURI(data), 194 | data, options, callback); 195 | break; 196 | case StoreMode.CreateOnly: 197 | tryStore = (callback) => 198 | this.database.client.log("Creating document: " + 199 | (data.id !== undefined && data.id !== "" ? data.id : 200 | "(auto generated ID)")) && 201 | this.database.client.documentClient.createDocument(this._self!, 202 | data, options, callback); 203 | break; 204 | default: 205 | tryStore = (callback) => 206 | this.database.client.log("Upserting document: " + 207 | (data.id !== undefined && data.id !== "" ? data.id : 208 | "(auto generated ID)")) && 209 | this.database.client.documentClient.upsertDocument(this._self!, 210 | data, options, callback); 211 | } 212 | return await curryPromise( 213 | tryStore, this.database.client.timeout, maxRetries)(); 214 | } 215 | 216 | /** Find the document with given ID */ 217 | public async findDocumentAsync(id: string, 218 | maxRetries?: number, options?: AllOptions): 219 | Promise; 220 | /** Reload given document from the database using its `._self` property */ 221 | public async findDocumentAsync(doc: { _self: string }, 222 | maxRetries?: number, options?: AllOptions): 223 | Promise; 224 | /** Find the document with exactly the same values for all properties (i.e. where _all_ own properties of the given object match exactly) */ 225 | public async findDocumentAsync(obj: Partial & object, 226 | maxRetries?: number, options?: AllOptions): 227 | Promise; 228 | public async findDocumentAsync( 229 | obj: string | { id?: string, _self?: string, [p: string]: any }, 230 | maxRetries?: number, options?: any) { 231 | await this.openAsync(); 232 | 233 | // read using readDocument if possible 234 | if (typeof obj === "string" || 235 | obj && (typeof obj._self === "string" || 236 | (typeof obj.id === "string"))) { 237 | var docURI: string | undefined; 238 | try { docURI = this._getDocURI(obj) } catch (all) { } 239 | if (docURI) { 240 | // if got a well-formed URI, go ahead (and retry on 404, for 241 | // lower consistency modes) 242 | let tryReadDoc = (callback: (err: any, result: any) => void) => 243 | this.database.client.log("Reading document " + docURI) && 244 | this.database.client.documentClient.readDocument( 245 | docURI!, options, callback); 246 | let result = await curryPromise(tryReadDoc, 247 | this.database.client.timeout, maxRetries, undefined, true)(); 248 | if (typeof obj !== "string" && !obj._self) { 249 | // check that other properties match, too 250 | for (var prop in <{}>obj) { 251 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 252 | if (obj[prop] !== result[prop]) 253 | throw new Error("Resource not found"); 254 | } 255 | } 256 | } 257 | return result; 258 | } 259 | else if (typeof obj === "string") { 260 | // select by ID property (e.g. when contains spaces) 261 | obj = { id: obj }; 262 | } 263 | } 264 | 265 | // use queryDocuments with given query/properties 266 | var q: _DocumentDB.SqlQuery = { 267 | query: "select top 1 * from c where", 268 | parameters: [] 269 | }; 270 | if (obj instanceof Object) { 271 | var i = 0; 272 | for (var prop in <{}>obj) { 273 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 274 | // add an exact match for this property 275 | if (q.parameters.length) q.query += " and"; 276 | q.query += ` c[${JSON.stringify(prop)}] = @_value_${++i}`; 277 | q.parameters.push({ 278 | name: "@_value_" + i, 279 | value: obj[prop] 280 | }); 281 | } 282 | } 283 | } 284 | if (!q.parameters.length) q.query += " true"; 285 | 286 | // sort by time stamp to get latest document first 287 | q.query += " order by c._ts desc"; 288 | 289 | // return a single resource 290 | let tryQuery = (callback: (err: any, result: any) => void) => 291 | this.database.client.log("Querying collection " + this.id + ": " + 292 | JSON.stringify(q)) && 293 | this.database.client.documentClient.queryDocuments( 294 | this._self!, q, options).toArray(callback); 295 | var results = await curryPromise(tryQuery, 296 | this.database.client.timeout, maxRetries)(); 297 | if (!results || !results.length) 298 | throw new Error("Resource not found"); 299 | return results[0]; 300 | } 301 | 302 | /** Check if a document with given ID exists (without reading the document) */ 303 | public async existsAsync(id: string, 304 | maxRetries?: number, options?: AllOptions): Promise; 305 | /** Check if a document with given properties exists (i.e. where _all_ own properties of the given object match exactly) */ 306 | public async existsAsync(obj: {}, 307 | maxRetries?: number, options?: AllOptions): Promise; 308 | public async existsAsync( 309 | obj: string | { id?: string, _self?: string, [p: string]: any }, 310 | maxRetries?: number, options?: any) { 311 | await this.openAsync(); 312 | 313 | // use queryDocuments with given ID or properties 314 | var q: _DocumentDB.SqlQuery = { 315 | query: "select value count(1) from c where", 316 | parameters: [] 317 | }; 318 | if (typeof obj === "string") { 319 | q.query += " c.id = @id"; 320 | q.parameters.push({ name: "@id", value: obj }); 321 | } 322 | else if (obj instanceof Object) { 323 | var i = 0; 324 | for (var prop in <{}>obj) { 325 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 326 | // add an exact match for this property 327 | if (q.parameters.length) q.query += " and"; 328 | q.query += ` c[${JSON.stringify(prop)}] = @_value_${++i}`; 329 | q.parameters.push({ 330 | name: "@_value_" + i, 331 | value: obj[prop] 332 | }); 333 | } 334 | } 335 | } 336 | if (!q.parameters.length) q.query += " true"; 337 | 338 | // run the query and return true only if count >= 1 339 | let tryQuery = (callback: (err: any, result: any) => void) => 340 | this.database.client.log("Querying collection " + this.id + ": " + 341 | JSON.stringify(q)) && 342 | this.database.client.documentClient 343 | .queryDocuments(this._self!, q, options) 344 | .toArray(callback); 345 | var results = await curryPromise(tryQuery, 346 | this.database.client.timeout, maxRetries)(); 347 | return !!results && results[0] >= 1; 348 | } 349 | 350 | /** Query documents in this collection using a SQL query string, or SQL query object (i.e. `{ query: "...", parameters: [{ name: "@...", value: ... }, ...] }`) */ 351 | public queryDocuments(query: _DocumentDB.SqlQuery, batchSize?: number): 352 | DocumentStream; 353 | /** Query documents in this collection using a SQL query string, or SQL query object (i.e. `{ query: "...", parameters: [{ name: "@...", value: ... }, ...] }`) */ 354 | public queryDocuments(query: _DocumentDB.SqlQuery, options?: AllOptions): 355 | DocumentStream; 356 | /** Query all documents in this collection */ 357 | public queryDocuments(query?: undefined, batchSize?: number): 358 | DocumentStream; 359 | /** Query all documents in this collection */ 360 | public queryDocuments(query?: undefined, options?: AllOptions): 361 | DocumentStream; 362 | public queryDocuments(query?: _DocumentDB.SqlQuery, options?: number | AllOptions) { 363 | if (typeof options === "number") options = { maxItemCount: options }; 364 | var uid = ++_queryUID; 365 | if (query === undefined) { 366 | // use readDocuments to get all documents 367 | return DocumentStream.create(this, uid, this.openAsync().then(() => 368 | this.database.client.log( 369 | `[${uid}>>] Reading all documents from ${this.id}`) && 370 | this.database.client.documentClient.readDocuments( 371 | this._self!, options))); 372 | } 373 | else { 374 | // submit given query 375 | return DocumentStream.create(this, uid, this.openAsync().then(() => 376 | this.database.client.log( 377 | `[${uid}>>] Querying collection ${this.id}: ` + 378 | JSON.stringify(query)) && 379 | this.database.client.documentClient.queryDocuments( 380 | this._self!, query, options))); 381 | } 382 | } 383 | 384 | /** Delete the document with given ID */ 385 | public async deleteDocumentAsync(id: string, 386 | maxRetries?: number, options?: AllOptions): Promise; 387 | /** Delete the given document (must have a `_self` property, e.g. the result of `storeDocumentAsync` or `findDocumentAsync`, OR a valid `id` property, note that other properties are NOT matched against the document to be deleted) */ 388 | public async deleteDocumentAsync(doc: { _self: string } | { id: string }, 389 | maxRetries?: number, options?: AllOptions): Promise; 390 | public async deleteDocumentAsync(v: string | { id?: string, _self?: string }, 391 | maxRetries?: number, options?: AllOptions) { 392 | await this.openAsync(); 393 | var id = typeof v === "string" ? v : v.id; 394 | var docURI: string | undefined; 395 | try { docURI = this._getDocURI(v) } catch (all) { } 396 | if (!docURI) { 397 | // ID may contain invalid characters, find _self instead 398 | var obj = await this.queryDocuments<{ _self: string }>({ 399 | query: "select c._self from c where c.id = @id", 400 | parameters: [{ name: "@id", value: id }] 401 | }, options).read(); 402 | if (!obj) throw new Error("Resource not found"); 403 | docURI = obj._self; 404 | } 405 | 406 | // use deleteDocument to delete by URI (retry on 404 a few times) 407 | let tryDelete = (callback: (err: any, result: any) => void) => 408 | this.database.client.log("Deleting: " + (id || "")) && 409 | this.database.client.documentClient.deleteDocument( 410 | docURI!, options, callback); 411 | await curryPromise(tryDelete, this.database.client.timeout, 412 | maxRetries, 500, true)(); 413 | } 414 | 415 | /** @internal Helper function that returns a document URI for given ID or object */ 416 | private _getDocURI(v: string | { id?: string, _self?: string }): string { 417 | if (typeof v !== "string") { 418 | if (v._self) return v._self; 419 | v = String(v.id || ""); 420 | } 421 | var chars = /[\/\\\?#]/; 422 | if (!v || chars.test(v) || chars.test(this.id)) 423 | throw new Error("Invalid resource ID: " + JSON.stringify(v)); 424 | return "dbs/" + this.database.id + 425 | "/colls/" + this.id + 426 | "/docs/" + v; 427 | } 428 | 429 | /** @internal Self link */ 430 | private _self?: string; 431 | 432 | /** @internal Offer link, if known */ 433 | private _offer?: _DocumentDB.OfferResource; 434 | } 435 | -------------------------------------------------------------------------------- /src/Database.ts: -------------------------------------------------------------------------------- 1 | import * as _DocumentDB from "./_DocumentDB"; 2 | import { Client } from "./Client"; 3 | import { curryPromise, sleepAsync } from "./Util"; 4 | import { Collection } from "./Collection"; 5 | 6 | /** Represents a DocumentDB database */ 7 | export class Database { 8 | /** Refer to a database by name, using given client */ 9 | constructor(id: string, client: Client); 10 | /** @internal Refer to a database by name, using given client and self link */ 11 | constructor(id: string, client: Client, selfLink: string); 12 | /** Refer to a database by name, using given URL and master key (implicitly creates a new client instance) */ 13 | constructor(id: string, url?: string, masterKey?: string); 14 | constructor(id: string, ...info: any[]) { 15 | if (!id) throw new Error("Databases must have a name"); 16 | this.id = id; 17 | if (info[0] instanceof Client) { 18 | // store client reference and optional self link (given by client) 19 | this.client = info[0]; 20 | this._self = info[1]; 21 | } 22 | else { 23 | // create client using given auth data 24 | this.client = new Client(info[0], info[1]); 25 | } 26 | } 27 | 28 | /** The name of the database that this instance refers to */ 29 | public readonly id: string; 30 | 31 | /** The client used for all operations */ 32 | public readonly client: Client; 33 | 34 | /** The partial resource URI for this database, i.e. `"/dbs/..."` */ 35 | public get path() { 36 | return this._self || "dbs/" + encodeURIComponent(this.id) + "/"; 37 | } 38 | 39 | /** Open and validate the connection, check that this database exists */ 40 | public async openAsync(maxRetries?: number) { 41 | if (this._self) return this; 42 | await this.client.openAsync(maxRetries); 43 | 44 | // find this database's self link from client's list of databases 45 | var dbs = await this.client.listDatabasesAsync(false, maxRetries); 46 | dbs.some(r => (r.id === this.id ? !!(this._self = r._self) : false)); 47 | 48 | if (!this._self) throw new Error("Database does not exist: " + this.id); 49 | return this; 50 | } 51 | 52 | /** Open and validate connection, find or create database resource */ 53 | public async openOrCreateAsync(maxRetries = 3) { 54 | if (this._self) return this; 55 | await this.client.openAsync(maxRetries); 56 | 57 | // find this database's self link from client's list of databases 58 | var forceReload = false; 59 | while (true) { 60 | var dbs = await this.client.listDatabasesAsync(forceReload); 61 | dbs.some(db => (db.id === this.id ? !!(this._self = db._self) : false)); 62 | if (!this._self) { 63 | try { 64 | // create the database now 65 | await this.client.createDatabaseAsync(this.id); 66 | return this; 67 | } 68 | catch (err) { 69 | if (err.code <= 404 || maxRetries-- <= 0) throw err; 70 | // otherwise, continue and maybe pick up the created DB 71 | // in the next iteration 72 | await sleepAsync(100); 73 | forceReload = true; 74 | } 75 | } 76 | return this; 77 | } 78 | } 79 | 80 | /** Get a list of Collection instances for this database */ 81 | public async listCollectionsAsync(maxRetries?: number, options?: _DocumentDB.FeedOptions) { 82 | await this.openAsync(maxRetries); 83 | 84 | // get all collections using readCollections 85 | let tryListAll = (callback: (err: any, result: any) => void) => 86 | this.client.log("Reading collections in " + this.id) && 87 | this.client.documentClient.readCollections(this._self!, options) 88 | .toArray(callback); 89 | var resources = await curryPromise(tryListAll, this.client.timeout, maxRetries)(); 90 | 91 | // map resources to Collection instances 92 | return (resources).map(r => new Collection(r.id, this, r._self)); 93 | } 94 | 95 | /** Delete this database */ 96 | public async deleteAsync(maxRetries?: number, options?: _DocumentDB.RequestOptions) { 97 | await this.openAsync(maxRetries); 98 | 99 | // use deleteDatabase to delete the database (duh...) 100 | let tryDelete = (callback: (err: any, result: any) => void) => 101 | this.client.log("Deleting database: " + this.id) && 102 | this.client.documentClient.deleteDatabase(this._self!, options, callback); 103 | await curryPromise(tryDelete, this.client.timeout)(); 104 | delete this._self; 105 | } 106 | 107 | /** @internal Self link */ 108 | private _self?: string; 109 | } 110 | -------------------------------------------------------------------------------- /src/DocumentStream.ts: -------------------------------------------------------------------------------- 1 | import * as _DocumentDB from "./_DocumentDB"; 2 | import { Collection } from "./Collection"; 3 | import { curryPromise } from "./Util"; 4 | 5 | // polyfill Symbol.asyncIterator 6 | if (!(Symbol).asyncIterator) { 7 | (Symbol).asyncIterator = Symbol("Symbol.asyncIterator"); 8 | } 9 | 10 | /** Represents asynchronously loaded query result sets as a stream; the type parameter represents the query result type, i.e. a full document resource type for `SELECT * FROM` queries, an object with only projected properties for `SELECT x, y, ... FROM` queries, or even a scalar value for `SELECT VALUE ... FROM` queries */ 11 | export class DocumentStream implements AsyncIterable { 12 | /** @internal create a document stream from a query iterator promise */ 13 | public static create(_collection: Collection, _uid: number, 14 | _qiP: Promise<_DocumentDB.QueryIterator>) { 15 | return new DocumentStream(_collection, _uid, _qiP); 16 | } 17 | 18 | /** Private constructor */ 19 | private constructor(private _collection: Collection, private _uid: number, 20 | private _qiP: Promise<_DocumentDB.QueryIterator>) { 21 | // nothing here 22 | } 23 | 24 | /** Timeout (ms) used for all operations; set to the Client timeout initially, set this to a large number if reading a large result set using `toArray` */ 25 | public timeout = this._collection.database.client.timeout; 26 | 27 | /** Get the next result (asynchronously), if any; promise resolves to the result, or to `null` if there are no results left in the set, or is rejected if an error occurred; subsequent calls to this function will return promises for results after the current result (i.e. requests are queued) */ 28 | public async read(): Promise { 29 | var nextResult = await this.next(); 30 | return nextResult.done ? null : nextResult.value!; 31 | } 32 | 33 | /** This property makes the entire instance usable as an async iterator */ 34 | [Symbol.asyncIterator] = () => this; 35 | 36 | /** Get the next result (asynchronously), if any; promise resolves to a `{ value, done }` pair, or is rejected if an error occurred; subsequent calls to this function will return promises for results after the current result (i.e. requests are queued) */ 37 | public async next(): Promise> { 38 | var qi = this._qi || (this._qi = await this._qiP); 39 | var readNextAsync = curryPromise(qi.nextItem.bind(qi), this.timeout, 0, 100); 40 | var next: T = await (this._nextP = this._nextP.then(() => 41 | this._collection.database.client.log( 42 | `[>>${this._uid}] Reading from stream...`) && 43 | readNextAsync())); 44 | return next !== undefined ? 45 | { value: next, done: false } : 46 | { value: undefined, done: true }; 47 | } 48 | 49 | /** Call a function for each result, until all results have been processed or the callback returns `false` or throws an error; returned promise resolves to true if all results have been processed, or false otherwise, or is rejected if an error occurred */ 50 | public forEach(f: (doc: T) => any) { 51 | let next = (): PromiseLike => { 52 | return this.next().then(n => { 53 | if (n.done) return true; 54 | if (f(n.value) === false) return false; 55 | return next(); 56 | }); 57 | }; 58 | return next(); 59 | } 60 | 61 | /** Call a function for each result; returns a promise for an array with all return values, which is resolved only when all results have been processed, or is rejected if the callback throws an error */ 62 | public async mapAsync(f: (doc: T) => ResultT) { 63 | var result = []; 64 | while (true) { 65 | var n = await this.next(); 66 | if (n.done) return result; 67 | result.push(f(n.value)); 68 | } 69 | } 70 | 71 | /** Reset the stream to the beginning of the set (synchronously); returns the stream itself */ 72 | public reset(): this { 73 | this._qi && this._qi.reset(); 74 | return this; 75 | } 76 | 77 | /** Reset the stream to the beginning of the set (asynchronously, i.e. after all queued operations have completed) */ 78 | public resetAsync() { 79 | return this._nextP.then(() => { 80 | this._qi && this._qi.reset(); 81 | }); 82 | } 83 | 84 | /** Load all results into an array */ 85 | public async toArray(): Promise { 86 | var qi = this._qi || (this._qi = await this._qiP); 87 | var readArrayAsync = curryPromise(qi.toArray.bind(qi), this.timeout, 0); 88 | return await (this._nextP = this._nextP.then(() => 89 | this._collection.database.client.log( 90 | `[>>${this._uid}] Reading into array from stream...`) && 91 | readArrayAsync())); 92 | } 93 | 94 | /** @internal The resolved query iterator, if any */ 95 | private _qi?: _DocumentDB.QueryIterator; 96 | 97 | /** @internal Promise for the last operation's result */ 98 | private _nextP: PromiseLike = Promise.resolve(true); 99 | } 100 | -------------------------------------------------------------------------------- /src/Util.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "./Client"; 2 | 3 | /** Current number of pending requests */ 4 | var _pending = 0; 5 | 6 | /** Return a curried version of given function that appends a callback as a last parameter, which rejects or resolves a promise; the promise is returned immediately; also handles DocumentDB errors when possible */ 7 | export function curryPromise(f: Function, timeout = 60000, 8 | maxRetries = 0, retryTimer?: number, retryOn404?: boolean) { 9 | return (...args: any[]): Promise => { 10 | // return Promise for result or error 11 | var started = false, done: any; 12 | var retries = maxRetries, timeoutTimer: any; 13 | return new Promise(function exec(resolve, reject) { 14 | if (done) return; 15 | if (!started) { 16 | if (_pending >= Client.concurrencyLimit) { 17 | setTimeout(exec, retryTimer || 100, resolve, reject); 18 | return; 19 | } 20 | _pending++; 21 | started = true; 22 | } 23 | 24 | // set timeout timer, reject when reached 25 | function setTimeoutTimer() { 26 | let t = timeoutTimer = setTimeout(() => { 27 | if (t === timeoutTimer) { 28 | if (retries-- > 0) 29 | retry(); 30 | else 31 | reject(done = new Error("Timeout")); 32 | } 33 | }, timeout); 34 | } 35 | function clearTimeoutTimer() { 36 | clearTimeout(timeoutTimer); 37 | timeoutTimer = undefined; 38 | } 39 | var retried = false; 40 | function retry(err?: any, headers?: any) { 41 | if (retried) return; 42 | retried = true; 43 | clearTimeoutTimer(); 44 | var t = retryTimer || 100; 45 | if (err && err.code === 429 && 46 | headers && headers["x-ms-retry-after-ms"]) 47 | t = parseFloat(headers["x-ms-retry-after-ms"]); 48 | setTimeout(exec, t, resolve, reject); 49 | } 50 | setTimeoutTimer(); 51 | 52 | // append own callback 53 | args.push((err: any, result: any, headers?: any) => { 54 | if (err) { 55 | // retry or reject 56 | if (err.code !== 400 && err.code !== 401 && 57 | err.code !== 403 && (retryOn404 || err.code !== 404) && 58 | err.code !== 409 && err.code !== 412 && 59 | err.code !== 413 && retries-- > 0) { 60 | retry(err, headers); 61 | } 62 | else { 63 | var error: Error | undefined, body: any; 64 | try { body = JSON.parse(err.body) } catch (all) { } 65 | if (body) { 66 | error = new Error(body.message || "Database error"); 67 | (error).code = err.code; 68 | error.name = body.code; 69 | } 70 | reject(error || err); 71 | done = true; 72 | clearTimeoutTimer(); 73 | } 74 | } 75 | else { 76 | // resolve the promise 77 | resolve(result); 78 | done = true; 79 | clearTimeoutTimer(); 80 | } 81 | }); 82 | try { 83 | f.apply(undefined, args); 84 | } 85 | catch (err) { 86 | reject(err); 87 | } 88 | }).then( 89 | result => { 90 | if (started) _pending--; 91 | return result; 92 | }, 93 | err => { 94 | if (started) _pending--; 95 | throw err; 96 | }); 97 | }; 98 | } 99 | 100 | /** Return a promise that resolves after a given timeout */ 101 | export function sleepAsync(ms: number, value: any = undefined) { 102 | return new Promise(resolve => { 103 | setTimeout(() => resolve(value), ms); 104 | }); 105 | } -------------------------------------------------------------------------------- /src/_DocumentDB.ts: -------------------------------------------------------------------------------- 1 | // import DocumentDB module 2 | const documentdb = require("documentdb"); 3 | 4 | /** Constructor for the DocumentClient instance */ 5 | export const DocumentClient: { 6 | new (urlConnection: string, auth: AuthenticationOptions, connectionPolicy?: ConnectionPolicy, consistencyLevel?: ConsistencyLevel): DocumentClient; 7 | } = documentdb.DocumentClient; 8 | 9 | /** A DocumentClient instance */ 10 | export interface DocumentClient { 11 | createCollection(dbLink: string, body: Partial & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 12 | createDatabase(body: Partial & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 13 | createDocument(collectionLink: string, body: Partial, options: RequestOptions | undefined, callback: Callback): void; 14 | createPermission(userLink: string, body: Partial, options: RequestOptions | undefined, callback: Callback): void; 15 | createStoredProcedure(collectionLink: string, sproc: WriteSprocResource & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 16 | createUser(dbLink: string, body: Partial & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 17 | deleteCollection(collectionLink: string, options: RequestOptions | undefined, callback: Callback): void; 18 | deleteDatabase(dbLink: string, options: RequestOptions | undefined, callback: Callback): void; 19 | deleteDocument(documentLink: string, options: RequestOptions | undefined, callback: Callback): void; 20 | deletePermission(permissionLink: string, options: RequestOptions | undefined, callback: Callback): void; 21 | deleteStoredProcedure(sprocLink: string, options: RequestOptions | undefined, callback: Callback): void; 22 | deleteUser(userLink: string, options: RequestOptions | undefined, callback: Callback): void; 23 | executeStoredProcedure(sprocLink: string, params: any[], options: RequestOptions | undefined, callback: Callback): void; 24 | getDatabaseAccount(callback: (error: ClientError, databaseAccount: DatabaseAccount) => void): void; 25 | getReadEndpoint(callback: (url: string) => void): void; 26 | getWriteEndpoint(callback: (url: string) => void): void; 27 | queryCollections(dbLink: string, query: SqlQuery, options?: FeedOptions): QueryIterator; 28 | queryConflicts(collectionLink: string, query: SqlQuery, options?: FeedOptions): QueryIterator; 29 | queryDatabases(query: SqlQuery, options?: FeedOptions): QueryIterator; 30 | queryDocuments(collectionLink: string, query: SqlQuery, options?: FeedOptions): QueryIterator; 31 | queryOffers(query: SqlQuery, options?: FeedOptions): QueryIterator; 32 | queryPermissions(userLink: string, query: SqlQuery, options?: FeedOptions): QueryIterator; 33 | queryStoredProcedures(collectionLink: string, query: SqlQuery, options?: FeedOptions): QueryIterator; 34 | queryUsers(dbLink: string, query: SqlQuery, options?: FeedOptions): QueryIterator; 35 | readCollection(collectionLink: string, options: RequestOptions | undefined, callback: Callback): void; 36 | readCollections(dbLink: string, options?: FeedOptions): QueryIterator; 37 | readConflict(conflictLink: string, options: RequestOptions | undefined, callback: Callback): void; 38 | readConflicts(collectionLink: string, options?: FeedOptions): QueryIterator; 39 | readDatabase(dbLink: string, options: RequestOptions | undefined, callback: Callback): void; 40 | readDatabases(options?: FeedOptions): QueryIterator; 41 | readDocument(documentLink: string, options: RequestOptions | undefined, callback: Callback): void; 42 | readDocuments(collectionLink: string, options?: FeedOptions): QueryIterator; 43 | readOffer(offerLink: string, callback: Callback): void; 44 | readOffers(options?: FeedOptions): QueryIterator; 45 | readPermission(permissionLink: string, options: RequestOptions | undefined, callback: Callback): void; 46 | readPermissions(userLink: string, options?: FeedOptions): QueryIterator; 47 | readStoredProcedure(sprocLink: string, options: RequestOptions | undefined, callback: Callback): void; 48 | readStoredProcedures(collectionLink: string, options?: FeedOptions): QueryIterator; 49 | readUser(userLink: string, options: RequestOptions | undefined, callback: Callback): void; 50 | readUsers(dbLink: string, options?: FeedOptions): QueryIterator; 51 | replaceDocument(documentLink: string, document: Partial & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 52 | replaceOffer(offerLink: string, offer: OfferResource, callback: Callback): void; 53 | replacePermission(permissionLink: string, permission: PermissionResource, options: RequestOptions | undefined, callback: Callback): void; 54 | replaceStoredProcedure(sprocLink: string, sproc: SprocResource, options: RequestOptions | undefined, callback: Callback): void; 55 | replaceUser(userLink: string, user: UserResource, options: RequestOptions | undefined, callback: Callback): void; 56 | upsertDocument(collectionLink: string, body: Partial, options: RequestOptions | undefined, callback: Callback): void; 57 | upsertPermission(userLink: string, body: Partial & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 58 | upsertStoredProcedure(collectionLink: string, sproc: Partial & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 59 | upsertUser(dbLink: string, body: Partial & Identifiable, options: RequestOptions | undefined, callback: Callback): void; 60 | 61 | // TODO: add typings for these methods: 62 | createAttachment: any; 63 | createAttachmentAndUploadMedia: any; 64 | createTrigger: any; 65 | createUserDefinedFunction: any; 66 | deleteAttachment: any; 67 | deleteConflict: any; 68 | deleteTrigger: any; 69 | deleteUserDefinedFunction: any; 70 | queryAttachments: any; 71 | queryTriggers: any; 72 | queryUserDefinedFunctions: any; 73 | readAttachment: any; 74 | readAttachments: any; 75 | readMedia: any; 76 | readTrigger: any; 77 | readTriggers: any; 78 | readUserDefinedFunction: any; 79 | readUserDefinedFunctions: any; 80 | replaceAttachment: any; 81 | replaceCollection: any; 82 | replaceTrigger: any; 83 | replaceUserDefinedFunction: any; 84 | updateMedia: any; 85 | upsertAttachment: any; 86 | upsertAttachmentAndUploadMedia: any; 87 | upsertTrigger: any; 88 | upsertUserDefinedFunction: any; 89 | } 90 | 91 | /** Callback as used by DocumentClient */ 92 | export type Callback = 93 | (error: ClientError, resource: T, responseHeaders: {}) => void; 94 | 95 | /** Object with an ID string */ 96 | export interface Identifiable { id: string }; 97 | 98 | /** Error returned by documentdb methods */ 99 | export interface ClientError { 100 | /** HTTP status code (e.g. 400 for malformed requests, 401 and 403 for auth errors, 404 for resource not found, 408 for when an internal operation timed out, 409 for resource conflicts, 412 for etag mismatches, 413 for entity too large, 429 for request rate too large, 449 for generic write errors that can be retried, and 500+ for server errors) */ 101 | code: number; 102 | /** Error body, usually another object encoded as JSON */ 103 | body: string; 104 | } 105 | 106 | /** Resoure object */ 107 | export interface Resource extends Object { 108 | /** Unique resource ID, less than 255 characters */ 109 | id: string; 110 | /** System generated resource ID for this resource */ 111 | _rid: string; 112 | /** System generated unique addressable URI for this resource */ 113 | _self: string; 114 | /** System generated entity tag for this resource in its current version, for optimistic concurrency control */ 115 | _etag: string; 116 | /** System generated last updated timestamp for this resource (in seconds since UNIX epoch) */ 117 | _ts: number; 118 | /** System generated addressable path for the attachments resource */ 119 | _attachments: string; 120 | } 121 | 122 | /** User resource (no special properties) */ 123 | export interface UserResource extends Resource { 124 | /** System generated addressable path for the feed of permissions resource */ 125 | _permissions: string; 126 | } 127 | 128 | /** Document resource */ 129 | export interface DocumentResource extends Resource { 130 | ttl?: number; 131 | } 132 | 133 | /** Database resource */ 134 | export interface DatabaseResource extends Resource { 135 | /** System generated addressable path for the collections resource */ 136 | _colls: string; 137 | /** System generated addressable path for the users resource */ 138 | _users: string; 139 | }; 140 | 141 | /** Collection resource */ 142 | export interface CollectionResource extends Resource { 143 | /** Indexing policy for this collection, if any */ 144 | indexingPolicy?: IndexingPolicy; 145 | /** Partition key for this collection, if any */ 146 | partitionKey?: PartitionKeyDefinition; 147 | /** Default TTL value for document resources, if any (in seconds, OR -1 for no expiry by default) */ 148 | defaultTtl?: number; 149 | /** System generated addressable path for the documents resource */ 150 | _docs: string; 151 | /** System generated addressable path for the stored procedures resource */ 152 | _sprocs: string; 153 | /** System generated addressable path for the triggers resource */ 154 | _triggers: string; 155 | /** System generated addressable path for the UDFs resource */ 156 | _udfs: string; 157 | /** System generated addressable path for the conflicts resource */ 158 | _conflicts: string; 159 | }; 160 | 161 | /** Permission resource */ 162 | export interface PermissionResource extends Resource { 163 | /** Access mode on the resource for the user: All or Read */ 164 | permissionMode: "Read" | "All"; 165 | /** Full addressable path of the resource associated with the permission */ 166 | resource: string; 167 | }; 168 | 169 | /** Stored procedure resource */ 170 | export interface SprocResource extends Resource { 171 | /** The unique name of this stored procedure */ 172 | id: string; 173 | /** The stored procedure function as a string */ 174 | body: string; 175 | } 176 | 177 | /** Stored procedure resource, with script as a JavaScript function */ 178 | export type WriteSprocResource = Partial & { 179 | /** The unique name of this stored procedure */ 180 | id: string; 181 | /** The stored procedure function as a JavaScript Function instance */ 182 | serverScript: Function; 183 | } | Partial & { 184 | /** The unique name of this stored procedure */ 185 | id: string; 186 | /** The stored procedure function as a string */ 187 | body: string; 188 | }; 189 | 190 | /** Offer (throughput provisioning) information resource */ 191 | export interface OfferResource extends Resource { 192 | offerVersion: "V2"; 193 | offerType: "Invalid"; 194 | content: { 195 | /** Throughput for the associated collection, must be in a multiple of 100 */ 196 | offerThroughput: number; 197 | }; 198 | offerResourceId: string; 199 | resource: string; 200 | } 201 | 202 | /** Consistency level constants */ 203 | export type ConsistencyLevel = "Strong" | "BoundedStaleness" | "Session" | "Eventual" | "ConsistentPrefix"; 204 | 205 | /** Query type: either a plain string or a structure with parameters */ 206 | export type SqlQuery = string | { 207 | /** The SQL query expressed as a string */ 208 | query: string; 209 | /** SQL query parameters as a list of name-value pairs */ 210 | parameters: Array<{ name: string; value: any }>; 211 | }; 212 | 213 | /** Authentication options used by the DocumentClient constructor */ 214 | export interface AuthenticationOptions { 215 | masterKey: string; 216 | resourceTokens?: { 217 | [resourceId: string]: string 218 | }; 219 | permissionFeed?: any[]; 220 | } 221 | 222 | /** DocumentClient connection policy interface */ 223 | export interface ConnectionPolicy { 224 | MediaReadMode?: "Buffered" | "Streamed"; 225 | MediaRequestTimeout?: number; 226 | RequestTimeout?: number; 227 | EnableEndpointDiscovery?: boolean; 228 | PreferredLocations?: string[]; 229 | RetryOptions: { 230 | MaxRetryAttemptCount?: number; 231 | FixedRetryIntervalInMilliseconds?: number; 232 | MaxWaitTimeInSeconds?: number; 233 | }; 234 | DisableSSLVerification?: boolean; 235 | ProxyUrl?: string; 236 | } 237 | 238 | /** DocumentClient indexing policy interface */ 239 | export interface IndexingPolicy { 240 | automatic?: boolean; 241 | indexingMode?: "Consistent" | "Lazy"; 242 | includedPaths: { 243 | Path: string; 244 | Indexes: { 245 | Kind: "Hash" | "Range" | "Spatial"; 246 | DataType: string; 247 | Precision: number; 248 | }[]; 249 | }[]; 250 | excludedPaths: { 251 | Path: string; 252 | }[]; 253 | } 254 | 255 | /** DocumentClient partition key definition interface */ 256 | export interface PartitionKeyDefinition { 257 | paths: string[]; 258 | kind: "Hash" 259 | } 260 | 261 | /** DocumentClient request options interface */ 262 | export interface RequestOptions { 263 | preTriggerInclude?: string; 264 | postTriggerInclude?: string; 265 | accessCondition?: { 266 | type: "IfMatch" | "IfNoneMatch", condition: string; 267 | }; 268 | indexingDirective?: string; 269 | consistencyLevel?: ConsistencyLevel; 270 | sessionToken?: string; 271 | resourceTokenExpirySeconds?: number; 272 | offerType?: string; 273 | offerEnableRUPerMinuteThroughput?: boolean; 274 | disableRUPerMinuteUsage?: boolean; 275 | offerThroughput?: number; 276 | partitionKey?: {}; 277 | disableAutomaticIdGeneration?: boolean; 278 | enableScriptLogging?: boolean; 279 | } 280 | 281 | /** Account information */ 282 | export interface DatabaseAccount { 283 | DatabasesLink: string; 284 | MediaLink: string; 285 | MaxMediaStorageUsageInMB: string | number; 286 | CurrentMediaStorageUsageInMB: string | number; 287 | ConsumedDocumentStorageInMB: number; 288 | ReservedDocumentStorageInMB: number; 289 | ProvisionedDocumentStorageInMB: number; 290 | ConsistencyPolicy: { 291 | defaultConsistencyLevel: ConsistencyLevel; 292 | maxStalenessPrefix: number; 293 | maxStalenessIntervalInSeconds: number; 294 | }; 295 | WritableLocations: string[]; 296 | ReadableLocations: string[]; 297 | } 298 | 299 | /** DocumentClient feed options interface */ 300 | export interface FeedOptions { 301 | maxItemCount?: number; 302 | continuation?: string; 303 | sessionToken?: string; 304 | partitionKey?: {}; 305 | enableScanInQuery?: boolean; 306 | enableCrossPartitionQuery?: boolean; 307 | } 308 | 309 | /** DocumentClient query iterator for iterating over a (future) result set */ 310 | export interface QueryIterator { 311 | current(callback: (error: ClientError, element: T) => void): void; 312 | executeNext(callback: (error: ClientError, list: T[]) => void): void; 313 | forEach(callback: (error: ClientError, element: T | undefined) => void): void; 314 | nextItem(callback: (error: ClientError, element: T) => void): void; 315 | reset(): void; 316 | toArray(callback: (error: ClientError, list: T[]) => void): void; 317 | } 318 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Client"; 2 | export * from "./Database"; 3 | export * from "./Collection"; 4 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationDir": "../typings", 5 | "target": "es6", 6 | "lib": [ "esnext", "esnext.asynciterable" ], 7 | "module": "commonjs", 8 | "strict": true, 9 | "stripInternal": true, 10 | "outDir": "../dist" 11 | } 12 | } -------------------------------------------------------------------------------- /typings-compat.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export * from "./typings"; 3 | --------------------------------------------------------------------------------