@cfworker/cosmos
Version:
Azure Cosmos DB client for Cloudflare Workers and service workers
287 lines (286 loc) • 13.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CosmosClient = void 0;
const response_js_1 = require("./response.js");
const retry_js_1 = require("./retry.js");
const session_js_1 = require("./session.js");
const signer_js_1 = require("./signer.js");
const util_js_1 = require("./util.js");
class CosmosClient {
endpoint;
signer;
retryPolicy;
consistencyLevel;
dbId;
collId;
enableCrossPartitionQueries;
systemFetch;
sessions;
requestCharges = 0;
retries = { count: 0, delayMs: 0 };
constructor(config) {
let { endpoint, masterKey } = 'connectionString' in config
? (0, util_js_1.parseConnectionString)(config.connectionString)
: config;
if (endpoint.endsWith('/')) {
endpoint = endpoint.slice(0, -1);
}
this.endpoint = endpoint;
this.signer = (0, signer_js_1.getSigner)(masterKey);
this.retryPolicy = config.retryPolicy ?? retry_js_1.defaultRetryPolicy;
this.consistencyLevel = config.consistencyLevel ?? 'Session';
this.dbId = config.dbId;
this.collId = config.collId;
this.enableCrossPartitionQueries =
config.enableCrossPartitionQueries ?? true;
this.sessions = config.sessions ?? new session_js_1.DefaultSessionContainer();
this.systemFetch = config.fetch ?? fetch.bind(globalThis);
}
async getDatabases(args = {}) {
const url = this.endpoint + `/dbs`;
const request = new Request(url, { method: 'GET' });
this.setHeaders(request.headers, args);
const response = await this.fetchWithRetry(request);
const next = this.getNext(response, args, this.getDatabases);
return new response_js_1.FeedResponse(response, next, 'Databases');
}
async getDatabase(args = {}) {
const { dbId = this.dbId, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}`;
const request = new Request(url, { method: 'GET' });
this.setHeaders(request.headers, headers);
const response = await this.fetchWithRetry(request);
return new response_js_1.ItemResponse(response);
}
async getCollections(args = {}) {
const { dbId = this.dbId, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls`;
const request = new Request(url, { method: 'GET' });
this.setHeaders(request.headers, headers);
const response = await this.fetchWithRetry(request);
const next = this.getNext(response, args, this.getCollections);
return new response_js_1.FeedResponse(response, next, 'DocumentCollections');
}
async getCollection(args = {}) {
const { dbId = this.dbId, collId = this.collId, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}`;
const request = new Request(url, { method: 'GET' });
this.setHeaders(request.headers, headers);
const response = await this.fetchWithRetry(request);
return new response_js_1.ItemResponse(response);
}
async createCollection(args) {
const { dbId = this.dbId, collId = this.collId, indexingPolicy, partitionKey, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls`;
const body = JSON.stringify({ id: collId, indexingPolicy, partitionKey });
const request = new Request(url, { method: 'POST', body });
this.setHeaders(request.headers, headers);
request.headers.set('content-type', 'application/json');
const response = await this.fetchWithRetry(request);
return new response_js_1.ItemResponse(response);
}
async replaceCollection(args) {
const { dbId = this.dbId, collId = this.collId, indexingPolicy, partitionKey, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}`;
const body = JSON.stringify({ id: collId, indexingPolicy, partitionKey });
const request = new Request(url, { method: 'PUT', body });
this.setHeaders(request.headers, headers);
request.headers.set('content-type', 'application/json');
const response = await this.fetchWithRetry(request);
return new response_js_1.ItemResponse(response);
}
async deleteCollection(args = {}) {
const { dbId = this.dbId, collId = this.collId, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}`;
const request = new Request(url, { method: 'DELETE' });
this.setHeaders(request.headers, headers);
const response = await this.fetchWithRetry(request);
return response;
}
async getDocuments(args) {
const { dbId = this.dbId, collId = this.collId, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}/docs`;
const request = new Request(url, { method: 'GET' });
this.setHeaders(request.headers, headers);
const response = await this.fetchWithRetry(request);
const next = this.getNext(response, args, this.getDocuments);
return new response_js_1.FeedResponse(response, next, 'Documents');
}
async getDocument(args) {
const { dbId = this.dbId, collId = this.collId, docId, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}/docs/${docId}`;
const request = new Request(url, { method: 'GET' });
this.setHeaders(request.headers, headers);
const response = await this.fetchWithRetry(request);
return new response_js_1.ItemResponse(response);
}
async createDocument(args) {
const { dbId = this.dbId, collId = this.collId, document, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}/docs`;
const body = toBodyInit(document);
const request = new Request(url, { method: 'POST', body });
this.setHeaders(request.headers, headers);
request.headers.set('content-type', 'application/json');
const response = await this.fetchWithRetry(request);
return new response_js_1.ItemResponse(response);
}
async replaceDocument(args) {
const { dbId = this.dbId, collId = this.collId, docId, document, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}/docs/${docId}`;
const body = toBodyInit(document);
const request = new Request(url, { method: 'PUT', body });
this.setHeaders(request.headers, headers);
request.headers.set('content-type', 'application/json');
const response = await this.fetchWithRetry(request);
return new response_js_1.ItemResponse(response);
}
async deleteDocument(args) {
const { dbId = this.dbId, collId = this.collId, docId, ...headers } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}/docs/${docId}`;
const request = new Request(url, { method: 'DELETE' });
this.setHeaders(request.headers, headers);
const response = await this.fetchWithRetry(request);
return response;
}
async queryDocuments(args) {
const { dbId = this.dbId, collId = this.collId, query, parameters = [], ...headerArgs } = args;
(0, util_js_1.assertArg)('dbId', dbId);
(0, util_js_1.assertArg)('collId', collId);
const headers = Object.assign({
enableCrossPartition: this.enableCrossPartitionQueries,
enableScan: false,
maxItems: -1,
populateMetrics: false,
isQuery: true
}, headerArgs);
const url = this.endpoint + (0, util_js_1.uri) `/dbs/${dbId}/colls/${collId}/docs`;
const body = JSON.stringify({ query, parameters });
const request = new Request(url, { method: 'POST', body });
this.setHeaders(request.headers, headers);
request.headers.set('content-type', 'application/query+json');
const response = await this.fetchWithRetry(request);
const next = this.getNext(response, args, this.queryDocuments);
return new response_js_1.FeedResponse(response, next, 'Documents');
}
getNext(response, args, fn) {
return () => {
const continuation = response.headers.get('x-ms-continuation');
if (!continuation) {
throw new Error(`Response is not continuable.`);
}
return fn.call(this, Object.assign({}, args, { continuation }));
};
}
async fetchWithRetry(request, context) {
const retryRequest = request.clone();
const response = await this.fetch(request);
context ??= { attempts: 0, cumulativeWaitMs: 0, request, response };
context.attempts++;
context.request = retryRequest;
context.response = response;
const { retry, delayMs } = await this.retryPolicy.shouldRetry(context);
if (!retry) {
retryRequest.body?.cancel();
return response;
}
if (delayMs !== 0) {
await new Promise(resolve => setTimeout(resolve, delayMs));
context.cumulativeWaitMs += delayMs;
this.retries.delayMs += delayMs;
}
this.retries.count++;
return this.fetchWithRetry(retryRequest, context);
}
async fetch(request) {
this.sessions.setRequestSession(request);
await this.signer.sign(request);
const response = await this.systemFetch(request);
this.sessions.readResponseSession(response);
const requestCharge = +(response.headers.get('x-ms-request-charge') || 0);
this.requestCharges += requestCharge;
return response;
}
setHeaders(headers, args) {
headers.set('accept', 'application/json');
headers.set('cache-control', 'no-cache');
headers.set('x-ms-version', '2018-12-31');
if (args.activityId) {
headers.set('x-ms-activity-id', args.activityId);
}
const consistencyLevel = args.consistencyLevel || this.consistencyLevel;
headers.set('x-ms-consistency-level', consistencyLevel);
if (args.continuation) {
headers.set('x-ms-continuation', args.continuation);
}
if (args.enableScan) {
headers.set('x-ms-documentdb-query-enable-scan', 'true');
}
if (args.ifMatch) {
headers.set('if-match', args.ifMatch);
}
if (args.ifModifiedSince) {
headers.set('if-modified-since', args.ifModifiedSince.toUTCString());
}
if (args.ifNoneMatch) {
headers.set('if-none-match', args.ifNoneMatch);
}
if (args.indexingDirective) {
headers.set('x-ms-indexing-directive', args.indexingDirective);
}
if (args.isQuery) {
headers.set('x-ms-documentdb-isquery', args.isQuery.toString());
}
if (args.isUpsert) {
headers.set('x-ms-documentdb-is-upsert', args.isUpsert.toString());
}
if (args.maxItems) {
headers.set('x-ms-max-item-count', args.maxItems.toString(10));
}
if (args.offerType) {
headers.set('x-ms-offer-type', args.offerType);
}
if (args.offerThroughput) {
headers.set('x-ms-offer-throughput', args.offerThroughput.toString());
}
if (args.populateMetrics) {
headers.set('x-ms-documentdb-populatequerymetrics', 'true');
}
if (args.partitionKey) {
headers.set('x-ms-documentdb-partitionkey', (0, util_js_1.escapeNonASCII)(JSON.stringify([args.partitionKey])));
if (args.isQuery) {
headers.set('x-ms-documentdb-query-enablecrosspartition', 'false');
}
}
else if (args.enableCrossPartition !== undefined && args.isQuery) {
headers.set('x-ms-documentdb-query-enablecrosspartition', args.enableCrossPartition.toString());
}
}
}
exports.CosmosClient = CosmosClient;
function toBodyInit(obj) {
if ((typeof obj === 'string' || DataView.prototype.isPrototypeOf(obj),
ArrayBuffer.isView(obj) || obj instanceof ReadableStream)) {
return obj;
}
return JSON.stringify(obj);
}