@azure/cosmos
Version:
Microsoft Azure Cosmos DB Service Node.js SDK for NOSQL API
681 lines (680 loc) • 26.3 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Container_exports = {};
__export(Container_exports, {
Container: () => Container
});
module.exports = __toCommonJS(Container_exports);
var import_common = require("../../common/index.js");
var import_documents = require("../../documents/index.js");
var import_request = require("../../request/index.js");
var import_ErrorResponse = require("../../request/ErrorResponse.js");
var import_Conflict = require("../Conflict/index.js");
var import_Item = require("../Item/index.js");
var import_Scripts = require("../Script/Scripts.js");
var import_ContainerResponse = require("./ContainerResponse.js");
var import_Offer = require("../Offer/index.js");
var import_OfferResponse = require("../Offer/OfferResponse.js");
var import_ChangeFeed = require("../ChangeFeed/index.js");
var import_diagnostics = require("../../utils/diagnostics.js");
var import_CosmosDiagnostics = require("../../CosmosDiagnostics.js");
var import_encryption = require("../../encryption/index.js");
class Container {
/**
* Returns a container instance. Note: You should get this from `database.container(id)`, rather than creating your own object.
* @param database - The parent {@link Database}.
* @param id - The id of the given container.
* @hidden
*/
constructor(database, id, clientContext, encryptionManager, _rid) {
this.database = database;
this.id = id;
this.clientContext = clientContext;
this.encryptionManager = encryptionManager;
this._rid = _rid;
if (this.clientContext.enableEncryption) {
this.encryptionProcessor = new import_encryption.EncryptionProcessor(
this.id,
this._rid,
this.database,
this.clientContext,
this.encryptionManager
);
}
}
$items;
/**
* Operations for creating new items, and reading/querying all items
*
* For reading, replacing, or deleting an existing item, use `.item(id)`.
*
* @example Create a new item
* ```ts snippet:ContainerItems
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
*
* const { container } = await database.containers.createIfNotExists({ id: "Test Container" });
*
* const { resource: createdItem } = await container.items.create({
* id: "<item id>",
* properties: {},
* });
* ```
*/
get items() {
if (!this.$items) {
this.$items = new import_Item.Items(this, this.clientContext);
}
return this.$items;
}
$scripts;
/**
* All operations for Stored Procedures, Triggers, and User Defined Functions
*/
get scripts() {
if (!this.$scripts) {
this.$scripts = new import_Scripts.Scripts(this, this.clientContext);
}
return this.$scripts;
}
$conflicts;
/**
* Operations for reading and querying conflicts for the given container.
*
* For reading or deleting a specific conflict, use `.conflict(id)`.
*/
get conflicts() {
if (!this.$conflicts) {
this.$conflicts = new import_Conflict.Conflicts(this, this.clientContext);
}
return this.$conflicts;
}
/**
* Returns a reference URL to the resource. Used for linking in Permissions.
*/
get url() {
return (0, import_common.createDocumentCollectionUri)(this.database.id, this.id);
}
/**
* @internal
*/
encryptionProcessor;
/**
* @internal
*/
_rid;
isEncryptionInitialized = false;
encryptionInitializationPromise;
/**
* Used to read, replace, or delete a specific, existing {@link Item} by id.
*
* Use `.items` for creating new items, or querying/reading all items.
*
* @param id - The id of the {@link Item}.
* @param partitionKeyValue - The value of the {@link Item} partition key
* @example Replace an item
* ```ts snippet:ContainerItem
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
*
* const { container } = await database.containers.createIfNotExists({ id: "Test Container" });
*
* const { body: replacedItem } = await container
* .item("<item id>", "<partition key value>")
* .replace({ id: "<item id>", title: "Updated post", authorID: 5 });
* ```
*/
item(id, partitionKeyValue) {
return new import_Item.Item(this, id, this.clientContext, partitionKeyValue);
}
/**
* Used to read, replace, or delete a specific, existing {@link Conflict} by id.
*
* Use `.conflicts` for creating new conflicts, or querying/reading all conflicts.
* @param id - The id of the {@link Conflict}.
* @example
* ```ts snippet:ConflictRead
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
* const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
* const container = database.container("Test Container");
*
* const { resource: conflict } = await container.conflict("<conflict-id>").read();
* ```
*/
conflict(id, partitionKey) {
return new import_Conflict.Conflict(this, id, this.clientContext, partitionKey);
}
/**
* Read the container's definition
* @example
* ```ts snippet:ContainerRead
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { resource: database } = await client.database("<db id>").container("<container id>").read();
* ```
*/
async read(options) {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
return this.readInternal(diagnosticNode, options);
}, this.clientContext);
}
/**
* @hidden
*/
async readInternal(diagnosticNode, options) {
const path = (0, import_common.getPathFromLink)(this.url);
const id = (0, import_common.getIdFromLink)(this.url);
const response = await this.clientContext.read({
path,
resourceType: import_common.ResourceType.container,
resourceId: id,
options,
diagnosticNode
});
this.clientContext.partitionKeyDefinitionCache[this.url] = response.result.partitionKey;
return new import_ContainerResponse.ContainerResponse(
response.result,
response.headers,
response.code,
this,
(0, import_diagnostics.getEmptyCosmosDiagnostics)()
);
}
/**
* Replace the container's definition
* @example
* ```ts snippet:ContainerReplace
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
*
* const containerDefinition = {
* id: "Test Container",
* partitionKey: {
* paths: ["/key1"],
* },
* throughput: 1000,
* };
* const { container } = await database.containers.createIfNotExists(containerDefinition);
*
* containerDefinition.throughput = 400;
* const { container: replacedContainer } = await container.replace(containerDefinition);
* ```
*/
async replace(body, options) {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
const err = {};
if (!(0, import_common.isResourceValid)(body, err)) {
throw err;
}
const path = (0, import_common.getPathFromLink)(this.url);
const id = (0, import_common.getIdFromLink)(this.url);
const response = await this.clientContext.replace({
body,
path,
resourceType: import_common.ResourceType.container,
resourceId: id,
options,
diagnosticNode
});
return new import_ContainerResponse.ContainerResponse(
response.result,
response.headers,
response.code,
this,
(0, import_diagnostics.getEmptyCosmosDiagnostics)()
);
}, this.clientContext);
}
/**
* Delete the container
* @example
* ```ts snippet:DatabaseDeleteContainer
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* await client.database("<db id>").container("<container id>").delete();
* ```
*/
async delete(options) {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
const path = (0, import_common.getPathFromLink)(this.url);
const id = (0, import_common.getIdFromLink)(this.url);
const response = await this.clientContext.delete({
path,
resourceType: import_common.ResourceType.container,
resourceId: id,
options,
diagnosticNode
});
return new import_ContainerResponse.ContainerResponse(
response.result,
response.headers,
response.code,
this,
(0, import_diagnostics.getEmptyCosmosDiagnostics)()
);
}, this.clientContext);
}
/**
* Gets the partition key definition first by looking into the cache otherwise by reading the collection.
* @deprecated This method has been renamed to readPartitionKeyDefinition.
*/
async getPartitionKeyDefinition() {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
return this.readPartitionKeyDefinition(diagnosticNode);
}, this.clientContext);
}
/**
* Gets the partition key definition first by looking into the cache otherwise by reading the collection.
* @hidden
*/
async readPartitionKeyDefinition(diagnosticNode) {
if (this.url in this.clientContext.partitionKeyDefinitionCache) {
diagnosticNode.addData({ readFromCache: true });
return new import_request.ResourceResponse(
this.clientContext.partitionKeyDefinitionCache[this.url],
{},
0,
(0, import_diagnostics.getEmptyCosmosDiagnostics)()
);
}
const { headers, statusCode, diagnostics } = await (0, import_diagnostics.withMetadataDiagnostics)(
async (node) => {
return this.readInternal(node);
},
diagnosticNode,
import_CosmosDiagnostics.MetadataLookUpType.ContainerLookUp
);
return new import_request.ResourceResponse(
this.clientContext.partitionKeyDefinitionCache[this.url],
headers,
statusCode,
diagnostics
);
}
/**
* Gets offer on container. If none exists, returns an OfferResponse with undefined.
* @example
* ```ts snippet:ContainerReadOffer
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { resource: offer } = await client
* .database("<db id>")
* .container("<container id>")
* .readOffer();
* ```
*/
async readOffer(options = {}) {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
const { resource: container } = await this.read();
const path = "/offers";
const url = container._self;
const response = await this.clientContext.queryFeed({
path,
resourceId: "",
resourceType: import_common.ResourceType.offer,
query: `SELECT * from root where root.resource = "${url}"`,
resultFn: (result) => result.Offers,
options,
diagnosticNode
});
const offer = response.result[0] ? new import_Offer.Offer(this.database.client, response.result[0].id, this.clientContext) : void 0;
return new import_OfferResponse.OfferResponse(
response.result[0],
response.headers,
response.code,
(0, import_diagnostics.getEmptyCosmosDiagnostics)(),
offer
);
}, this.clientContext);
}
async getQueryPlan(query) {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
const path = (0, import_common.getPathFromLink)(this.url);
return this.clientContext.getQueryPlan(
path + "/docs",
import_common.ResourceType.item,
(0, import_common.getIdFromLink)(this.url),
query,
{},
diagnosticNode
);
}, this.clientContext);
}
/**
* Gets the partition key ranges for the container.
* @param feedOptions - Options for the request.
* @returns An iterator of partition key ranges.
* @example
* ```ts snippet:ContainerReadPartitionKeyRanges
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
*
* const { container } = await database.containers.createIfNotExists({ id: "Test Container" });
*
* const { resources: ranges } = await container.readPartitionKeyRanges().fetchAll();
* ```
*/
readPartitionKeyRanges(feedOptions) {
feedOptions = feedOptions || {};
return this.clientContext.queryPartitionKeyRanges(this.url, void 0, feedOptions);
}
/**
*
* @returns all the feed ranges for which changefeed could be fetched.
* @example
* ```ts snippet:ContainerGetFeedRanges
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
*
* const { container } = await database.containers.createIfNotExists({ id: "Test Container" });
*
* const { resources: ranges } = await container.getFeedRanges();
* ```
*/
async getFeedRanges() {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
const { resources } = await this.readPartitionKeyRanges().fetchAllInternal(diagnosticNode);
const feedRanges = [];
for (const resource of resources) {
const feedRange = new import_ChangeFeed.FeedRangeInternal(resource.minInclusive, resource.maxExclusive);
Object.freeze(feedRange);
feedRanges.push(feedRange);
}
return feedRanges;
}, this.clientContext);
}
/**
* Delete all documents belong to the container for the provided partition key value
* @param partitionKey - The partition key value of the items to be deleted
* @example
* ```ts snippet:ContainerDeleteAllItemsForPartitionKey
* import { CosmosClient } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const client = new CosmosClient({ endpoint, key });
*
* const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
*
* const { container } = await database.containers.createIfNotExists({
* id: "Test Container",
* partitionKey: {
* paths: ["/state"],
* },
* });
*
* const cities = [
* { id: "1", name: "Olympia", state: "WA", isCapitol: true },
* { id: "2", name: "Redmond", state: "WA", isCapitol: false },
* { id: "3", name: "Olympia", state: "IL", isCapitol: false },
* ];
* for (const city of cities) {
* await container.items.create(city);
* }
*
* await container.deleteAllItemsForPartitionKey("WA");
* ```
*/
async deleteAllItemsForPartitionKey(partitionKey, options) {
return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
let path = (0, import_common.getPathFromLink)(this.url);
const id = (0, import_common.getIdFromLink)(this.url);
path = path + "/operations/partitionkeydelete";
if (this.clientContext.enableEncryption) {
await this.checkAndInitializeEncryption();
options = options || {};
options.containerRid = this._rid;
diagnosticNode.beginEncryptionDiagnostics(import_common.Constants.Encryption.DiagnosticsEncryptOperation);
const partitionKeyInternal = (0, import_documents.convertToInternalPartitionKey)(partitionKey);
const { partitionKeyList, encryptedCount } = await this.encryptionProcessor.getEncryptedPartitionKeyValue(partitionKeyInternal);
partitionKey = partitionKeyList;
diagnosticNode.endEncryptionDiagnostics(
import_common.Constants.Encryption.DiagnosticsEncryptOperation,
encryptedCount
);
}
let response;
try {
response = await this.clientContext.delete({
path,
resourceType: import_common.ResourceType.container,
resourceId: id,
options,
partitionKey,
method: import_common.HTTPMethod.post,
diagnosticNode
});
} catch (error) {
if (this.clientContext.enableEncryption) {
await this.throwIfRequestNeedsARetryPostPolicyRefresh(error);
}
throw error;
}
return new import_ContainerResponse.ContainerResponse(
response.result,
response.headers,
response.code,
this,
(0, import_diagnostics.getEmptyCosmosDiagnostics)()
);
}, this.clientContext);
}
/**
* Warms up encryption related caches for the container.
* @example
* ```ts snippet:ContainerIntializeEncryption
* import { ClientSecretCredential } from "@azure/identity";
* import {
* AzureKeyVaultEncryptionKeyResolver,
* CosmosClient,
* EncryptionType,
* EncryptionAlgorithm,
* ClientEncryptionIncludedPath,
* ClientEncryptionPolicy,
* } from "@azure/cosmos";
*
* const endpoint = "https://your-account.documents.azure.com";
* const key = "<database account masterkey>";
* const credentials = new ClientSecretCredential("<tenant-id>", "<client-id>", "<app-secret>");
* const keyResolver = new AzureKeyVaultEncryptionKeyResolver(credentials);
* const client = new CosmosClient({
* endpoint,
* key,
* clientEncryptionOptions: {
* keyEncryptionKeyResolver: keyResolver,
* },
* });
* const { database } = await client.databases.createIfNotExists({ id: "<db id>" });
*
* const paths = ["/path1", "/path2", "/path3"].map(
* (path) =>
* ({
* path: path,
* clientEncryptionKeyId: "< cek - id >",
* encryptionType: EncryptionType.DETERMINISTIC,
* encryptionAlgorithm: EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256,
* }) as ClientEncryptionIncludedPath,
* );
* const clientEncryptionPolicy: ClientEncryptionPolicy = {
* includedPaths: paths,
* policyFormatVersion: 2,
* };
* const containerDefinition = {
* id: "Test Container",
* partitionKey: {
* paths: ["/id"],
* },
* clientEncryptionPolicy: clientEncryptionPolicy,
* };
* const { container } = await database.containers.createIfNotExists(containerDefinition);
*
* await container.initializeEncryption();
* ```
*/
async initializeEncryption() {
if (!this.clientContext.enableEncryption) {
throw new import_ErrorResponse.ErrorResponse("Encryption is not enabled for the client.");
} else {
await (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => {
const readResponse = await this.readInternal(diagnosticNode);
if (!readResponse || !readResponse.resource) {
throw new import_ErrorResponse.ErrorResponse(
"Failed to initialize encryption: The container's resource definition could not be retrieved."
);
}
this._rid = readResponse.resource._rid;
this.encryptionProcessor.containerRid = this._rid;
const clientEncryptionPolicy = readResponse.resource.clientEncryptionPolicy;
if (!clientEncryptionPolicy) return;
const partitionKeyPaths = readResponse.resource.partitionKey.paths;
const databaseResponse = await this.database.readInternal(diagnosticNode);
if (!databaseResponse || !databaseResponse.resource) {
throw new import_ErrorResponse.ErrorResponse(
"Failed to initialize encryption: The database's resource definition could not be retrieved."
);
}
this.database._rid = databaseResponse.resource._rid;
const encryptionSettingKey = this.database._rid + "/" + this._rid;
await this.encryptionManager.encryptionSettingsCache.create(
encryptionSettingKey,
this._rid,
partitionKeyPaths,
clientEncryptionPolicy
);
const clientEncryptionKeyIds = [
...new Set(
clientEncryptionPolicy.includedPaths.map((item) => item.clientEncryptionKeyId)
)
];
for (const clientEncryptionKeyId of clientEncryptionKeyIds) {
const res = await this.database.readClientEncryptionKey(clientEncryptionKeyId);
if (!res || !res.clientEncryptionKeyProperties) {
throw new import_ErrorResponse.ErrorResponse(
`Failed to initialize encryption: The client encryption key ${clientEncryptionKeyId} could not be retrieved.`
);
}
const encryptionKeyProperties = res.clientEncryptionKeyProperties;
const key = this.database._rid + "/" + clientEncryptionKeyId;
this.encryptionManager.clientEncryptionKeyPropertiesCache.set(
key,
encryptionKeyProperties
);
}
this.isEncryptionInitialized = true;
}, this.clientContext);
}
}
/**
* @internal
*/
async checkAndInitializeEncryption() {
if (!this.isEncryptionInitialized) {
if (!this.encryptionInitializationPromise) {
this.encryptionInitializationPromise = this.initializeEncryption();
}
await this.encryptionInitializationPromise;
}
}
/**
* @internal
* This function handles the scenario where a container is deleted(say from different Client) and recreated with same Id but with different client encryption policy.
* The idea is to have the container Rid cached and sent out as part of RequestOptions with Container Rid set in "x-ms-cosmos-intended-collection-rid" header.
* So, when the container being referenced here gets recreated we would end up with a stale encryption settings and container Rid and this would result in BadRequest (and a substatus 1024).
* This would allow us to refresh the encryption settings and Container Rid, on the premise that the container recreated could possibly be configured with a new encryption policy.
*/
async throwIfRequestNeedsARetryPostPolicyRefresh(errorResponse) {
const key = this.database._rid + "/" + this._rid;
const encryptionSetting = this.encryptionManager.encryptionSettingsCache.get(key);
if (!errorResponse?.code || !errorResponse?.headers?.[import_common.Constants.HttpHeaders.SubStatus]) {
return;
}
const subStatusCode = errorResponse.headers[import_common.Constants.HttpHeaders.SubStatus];
const isPartitionKeyMismatch = Number(subStatusCode) === import_common.SubStatusCodes.PartitionKeyMismatch;
const isIncorrectContainerRidSubstatus = Number(subStatusCode) === import_common.SubStatusCodes.IncorrectContainerRidSubstatus;
if (errorResponse.code === import_common.StatusCodes.BadRequest && (isPartitionKeyMismatch || isIncorrectContainerRidSubstatus)) {
if (isPartitionKeyMismatch && encryptionSetting.partitionKeyPaths.length) {
let encryptionSettingsForProperty = null;
for (const path of encryptionSetting.partitionKeyPaths) {
const partitionKeyPath = path.split("/")[1];
encryptionSettingsForProperty = encryptionSetting.getEncryptionSettingForProperty(partitionKeyPath);
if (encryptionSettingsForProperty) {
break;
}
}
if (encryptionSettingsForProperty == null) {
return;
}
}
const currentContainerRid = encryptionSetting.containerRid;
const forceRefresh = true;
const updatedContainerRid = (await this.encryptionProcessor.getEncryptionSetting(forceRefresh)).containerRid;
if (currentContainerRid === updatedContainerRid) {
return;
}
await this.initializeEncryption();
throw new import_ErrorResponse.ErrorResponse(
"Operation has failed due to a possible mismatch in Client Encryption Policy configured on the container. Retrying may fix the issue. Please refer to https://aka.ms/CosmosClientEncryption for more details." + errorResponse.message
);
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Container
});