// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import { __asyncDelegator, __asyncGenerator, __asyncValues, __await, __rest } from "tslib"; import "@azure/core-paging"; import { GeneratedClient } from "./generated"; import { isNamedKeyCredential, isSASCredential, isTokenCredential, } from "@azure/core-auth"; import { STORAGE_SCOPE, TablesLoggingAllowedHeaderNames } from "./utils/constants"; import { decodeContinuationToken, encodeContinuationToken } from "./utils/continuationToken"; import { deserialize, deserializeObjectsArray, deserializeSignedIdentifier, serialize, serializeQueryOptions, serializeSignedIdentifiers, } from "./serialization"; import { parseXML, stringifyXML } from "@azure/core-xml"; import { InternalTableTransaction } from "./TableTransaction"; import { SpanStatusCode } from "@azure/core-tracing"; import { Uuid } from "./utils/uuid"; import { cosmosPatchPolicy } from "./cosmosPathPolicy"; import { createSpan } from "./utils/tracing"; import { escapeQuotes } from "./odata"; import { getClientParamsFromConnectionString } from "./utils/connectionString"; import { handleTableAlreadyExists } from "./utils/errorHelpers"; import { isCosmosEndpoint } from "./utils/isCosmosEndpoint"; import { isCredential } from "./utils/isCredential"; import { logger } from "./logger"; import { tablesNamedKeyCredentialPolicy } from "./tablesNamedCredentialPolicy"; import { tablesSASTokenPolicy } from "./tablesSASTokenPolicy"; /** * A TableClient represents a Client to the Azure Tables service allowing you * to perform operations on a single table. */ export class TableClient { constructor(url, tableName, credentialOrOptions, options = {}) { var _a; this.url = url; this.tableName = tableName; const credential = isCredential(credentialOrOptions) ? credentialOrOptions : undefined; this.credential = credential; this.clientOptions = (!isCredential(credentialOrOptions) ? credentialOrOptions : options) || {}; this.allowInsecureConnection = (_a = this.clientOptions.allowInsecureConnection) !== null && _a !== void 0 ? _a : false; this.clientOptions.endpoint = this.clientOptions.endpoint || this.url; const internalPipelineOptions = Object.assign(Object.assign(Object.assign({}, this.clientOptions), { loggingOptions: { logger: logger.info, additionalAllowedHeaderNames: [...TablesLoggingAllowedHeaderNames], }, deserializationOptions: { parseXML, }, serializationOptions: { stringifyXML, } }), (isTokenCredential(this.credential) && { credential: this.credential, credentialScopes: STORAGE_SCOPE, })); const generatedClient = new GeneratedClient(this.url, internalPipelineOptions); if (isNamedKeyCredential(credential)) { generatedClient.pipeline.addPolicy(tablesNamedKeyCredentialPolicy(credential)); } else if (isSASCredential(credential)) { generatedClient.pipeline.addPolicy(tablesSASTokenPolicy(credential)); } if (isCosmosEndpoint(this.url)) { generatedClient.pipeline.addPolicy(cosmosPatchPolicy()); } this.table = generatedClient.table; this.pipeline = generatedClient.pipeline; } /** * Permanently deletes the current table with all of its entities. * @param options - The options parameters. * * ### Example deleting a table * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * // calling deleteTable will delete the table used * // to instantiate the TableClient. * // Note: If the table doesn't exist this function doesn't fail. * await client.deleteTable(); * ``` */ // eslint-disable-next-line @azure/azure-sdk/ts-naming-options async deleteTable(options = {}) { const { span, updatedOptions } = createSpan("TableClient-deleteTable", options); try { await this.table.delete(this.tableName, updatedOptions); } catch (e) { if (e.statusCode === 404) { logger.info("TableClient-deleteTable: Table doesn't exist"); } else { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } } finally { span.end(); } } /** * Creates a table with the tableName passed to the client constructor * @param options - The options parameters. * * ### Example creating a table * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * // calling create table will create the table used * // to instantiate the TableClient. * // Note: If the table already * // exists this function doesn't throw. * await client.createTable(); * ``` */ // eslint-disable-next-line @azure/azure-sdk/ts-naming-options async createTable(options = {}) { const { span, updatedOptions } = createSpan("TableClient-createTable", options); try { await this.table.create({ name: this.tableName }, updatedOptions); } catch (e) { handleTableAlreadyExists(e, Object.assign(Object.assign({}, updatedOptions), { span, logger, tableName: this.tableName })); } finally { span.end(); } } /** * Returns a single entity in the table. * @param partitionKey - The partition key of the entity. * @param rowKey - The row key of the entity. * @param options - The options parameters. * * ### Example getting an entity * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * // getEntity will get a single entity stored in the service that * // matches exactly the partitionKey and rowKey used as parameters * // to the method. * const entity = await client.getEntity("<partitionKey>", "<rowKey>"); * console.log(entity); * ``` */ async getEntity(partitionKey, rowKey, // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { const { span, updatedOptions } = createSpan("TableClient-getEntity", options); let parsedBody; function onResponse(rawResponse, flatResponse) { parsedBody = rawResponse.parsedBody; if (updatedOptions.onResponse) { updatedOptions.onResponse(rawResponse, flatResponse); } } try { const _a = updatedOptions || {}, { disableTypeConversion, queryOptions } = _a, getEntityOptions = __rest(_a, ["disableTypeConversion", "queryOptions"]); await this.table.queryEntitiesWithPartitionAndRowKey(this.tableName, escapeQuotes(partitionKey), escapeQuotes(rowKey), Object.assign(Object.assign({}, getEntityOptions), { queryOptions: serializeQueryOptions(queryOptions || {}), onResponse })); const tableEntity = deserialize(parsedBody, disableTypeConversion !== null && disableTypeConversion !== void 0 ? disableTypeConversion : false); return tableEntity; } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } finally { span.end(); } } /** * Queries entities in a table. * @param options - The options parameters. * * Example listing entities * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * // list entities returns a AsyncIterableIterator * // this helps consuming paginated responses by * // automatically handling getting the next pages * const entities = client.listEntities(); * * // this loop will get all the entities from all the pages * // returned by the service * for await (const entity of entities) { * console.log(entity); * } * ``` */ listEntities( // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { const tableName = this.tableName; const iter = this.listEntitiesAll(tableName, options); return { next() { return iter.next(); }, [Symbol.asyncIterator]() { return this; }, byPage: (settings) => { const pageOptions = Object.assign(Object.assign({}, options), { queryOptions: Object.assign(Object.assign({}, options.queryOptions), { top: settings === null || settings === void 0 ? void 0 : settings.maxPageSize }) }); if (settings === null || settings === void 0 ? void 0 : settings.continuationToken) { pageOptions.continuationToken = settings.continuationToken; } return this.listEntitiesPage(tableName, pageOptions); }, }; } listEntitiesAll(tableName, options) { return __asyncGenerator(this, arguments, function* listEntitiesAll_1() { var e_1, _a; const firstPage = yield __await(this._listEntities(tableName, options)); yield __await(yield* __asyncDelegator(__asyncValues(firstPage))); if (firstPage.continuationToken) { const optionsWithContinuation = Object.assign(Object.assign({}, options), { continuationToken: firstPage.continuationToken }); try { for (var _b = __asyncValues(this.listEntitiesPage(tableName, optionsWithContinuation)), _c; _c = yield __await(_b.next()), !_c.done;) { const page = _c.value; yield __await(yield* __asyncDelegator(__asyncValues(page))); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) yield __await(_a.call(_b)); } finally { if (e_1) throw e_1.error; } } } }); } listEntitiesPage(tableName, options = {}) { return __asyncGenerator(this, arguments, function* listEntitiesPage_1() { const { span, updatedOptions } = createSpan("TableClient-listEntitiesPage", options); try { let result = yield __await(this._listEntities(tableName, updatedOptions)); yield yield __await(result); while (result.continuationToken) { const optionsWithContinuation = Object.assign(Object.assign({}, updatedOptions), { continuationToken: result.continuationToken }); result = yield __await(this._listEntities(tableName, optionsWithContinuation)); yield yield __await(result); } } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message, }); throw e; } finally { span.end(); } }); } async _listEntities(tableName, options = {}) { const { disableTypeConversion = false } = options; const queryOptions = serializeQueryOptions(options.queryOptions || {}); const listEntitiesOptions = Object.assign(Object.assign({}, options), { queryOptions }); // If a continuation token is used, decode it and set the next row and partition key if (options.continuationToken) { const continuationToken = decodeContinuationToken(options.continuationToken); listEntitiesOptions.nextRowKey = continuationToken.nextRowKey; listEntitiesOptions.nextPartitionKey = continuationToken.nextPartitionKey; } const { xMsContinuationNextPartitionKey: nextPartitionKey, xMsContinuationNextRowKey: nextRowKey, value, } = await this.table.queryEntities(tableName, listEntitiesOptions); const tableEntities = deserializeObjectsArray(value !== null && value !== void 0 ? value : [], disableTypeConversion); // Encode nextPartitionKey and nextRowKey as a single continuation token and add it as a // property to the page. const continuationToken = encodeContinuationToken(nextPartitionKey, nextRowKey); const page = Object.assign([...tableEntities], { continuationToken, }); return page; } /** * Insert entity in the table. * @param entity - The properties for the table entity. * @param options - The options parameters. * * ### Example creating an entity * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * // partitionKey and rowKey are required properties of the entity to create * // and accepts any other properties * await client.createEntity({partitionKey: "p1", rowKey: "r1", foo: "Hello!"}); * ``` */ async createEntity(entity, // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { const { span, updatedOptions } = createSpan("TableClient-createEntity", options); try { const createTableEntity = __rest(updatedOptions || {}, []); return await this.table.insertEntity(this.tableName, Object.assign(Object.assign({}, createTableEntity), { tableEntityProperties: serialize(entity), responsePreference: "return-no-content" })); } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } finally { span.end(); } } /** * Deletes the specified entity in the table. * @param partitionKey - The partition key of the entity. * @param rowKey - The row key of the entity. * @param options - The options parameters. * * ### Example deleting an entity * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * // deleteEntity deletes the entity that matches * // exactly the partitionKey and rowKey passed as parameters * await client.deleteEntity("<partitionKey>", "<rowKey>") * ``` */ async deleteEntity(partitionKey, rowKey, // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { const { span, updatedOptions } = createSpan("TableClient-deleteEntity", options); try { const _a = updatedOptions || {}, { etag = "*" } = _a, rest = __rest(_a, ["etag"]); const deleteOptions = Object.assign({}, rest); return await this.table.deleteEntity(this.tableName, escapeQuotes(partitionKey), escapeQuotes(rowKey), etag, deleteOptions); } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } finally { span.end(); } } /** * Update an entity in the table. * @param entity - The properties of the entity to be updated. * @param mode - The different modes for updating the entity: * - Merge: Updates an entity by updating the entity's properties without replacing the existing entity. * - Replace: Updates an existing entity by replacing the entire entity. * @param options - The options parameters. * * ### Example updating an entity * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * const entity = {partitionKey: "p1", rowKey: "r1", bar: "updatedBar"}; * * // Update uses update mode "Merge" as default * // merge means that update will match a stored entity * // that has the same partitionKey and rowKey as the entity * // passed to the method and then will only update the properties present in it. * // Any other properties that are not defined in the entity passed to updateEntity * // will remain as they are in the service * await client.updateEntity(entity) * * // We can also set the update mode to Replace, which will match the entity passed * // to updateEntity with one stored in the service and replace with the new one. * // If there are any missing properties in the entity passed to updateEntity, they * // will be removed from the entity stored in the service * await client.updateEntity(entity, "Replace") * ``` */ async updateEntity(entity, mode = "Merge", // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { const { span, updatedOptions } = createSpan(`TableClient-updateEntity-${mode}`, options); try { const partitionKey = escapeQuotes(entity.partitionKey); const rowKey = escapeQuotes(entity.rowKey); const _a = updatedOptions || {}, { etag = "*" } = _a, updateEntityOptions = __rest(_a, ["etag"]); if (mode === "Merge") { return await this.table.mergeEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity), ifMatch: etag }, updateEntityOptions)); } if (mode === "Replace") { return await this.table.updateEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity), ifMatch: etag }, updateEntityOptions)); } throw new Error(`Unexpected value for update mode: ${mode}`); } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } finally { span.end(); } } /** * Upsert an entity in the table. * @param entity - The properties for the table entity. * @param mode - The different modes for updating the entity: * - Merge: Updates an entity by updating the entity's properties without replacing the existing entity. * - Replace: Updates an existing entity by replacing the entire entity. * @param options - The options parameters. * * ### Example upserting an entity * ```js * const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables") * const account = "<storage account name>"; * const accountKey = "<account key>" * const tableName = "<table name>"; * const sharedKeyCredential = new AzureNamedKeyCredential(account, accountKey); * * const client = new TableClient( * `https://${account}.table.core.windows.net`, * `${tableName}`, * sharedKeyCredential * ); * * const entity = {partitionKey: "p1", rowKey: "r1", bar: "updatedBar"}; * * // Upsert uses update mode "Merge" as default. * // This behaves similarly to update but creates the entity * // if it doesn't exist in the service * await client.upsertEntity(entity) * * // We can also set the update mode to Replace. * // This behaves similarly to update but creates the entity * // if it doesn't exist in the service * await client.upsertEntity(entity, "Replace") * ``` */ async upsertEntity(entity, mode = "Merge", // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { const { span, updatedOptions } = createSpan(`TableClient-upsertEntity-${mode}`, options); try { const partitionKey = escapeQuotes(entity.partitionKey); const rowKey = escapeQuotes(entity.rowKey); if (mode === "Merge") { return await this.table.mergeEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity) }, updatedOptions)); } if (mode === "Replace") { return await this.table.updateEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity) }, updatedOptions)); } throw new Error(`Unexpected value for update mode: ${mode}`); } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } finally { span.end(); } } /** * Retrieves details about any stored access policies specified on the table that may be used with * Shared Access Signatures. * @param options - The options parameters. */ async getAccessPolicy(options = {}) { const { span, updatedOptions } = createSpan("TableClient-getAccessPolicy", options); try { const signedIdentifiers = await this.table.getAccessPolicy(this.tableName, updatedOptions); return deserializeSignedIdentifier(signedIdentifiers); } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } finally { span.end(); } } /** * Sets stored access policies for the table that may be used with Shared Access Signatures. * @param tableAcl - The Access Control List for the table. * @param options - The options parameters. */ async setAccessPolicy(tableAcl, options = {}) { const { span, updatedOptions } = createSpan("TableClient-setAccessPolicy", options); try { const serlializedAcl = serializeSignedIdentifiers(tableAcl); return await this.table.setAccessPolicy(this.tableName, Object.assign(Object.assign({}, updatedOptions), { tableAcl: serlializedAcl })); } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); throw e; } finally { span.end(); } } /** * Submits a Transaction which is composed of a set of actions. You can provide the actions as a list * or you can use {@link TableTransaction} to help building the transaction. * * Example usage: * ```typescript * const { TableClient } = require("@azure/data-tables"); * const connectionString = "<connection-string>" * const tableName = "<tableName>" * const client = TableClient.fromConnectionString(connectionString, tableName); * const actions = [ * ["create", {partitionKey: "p1", rowKey: "1", data: "test1"}], * ["delete", {partitionKey: "p1", rowKey: "2"}], * ["update", {partitionKey: "p1", rowKey: "3", data: "newTest"}, "Merge"] * ] * const result = await client.submitTransaction(actions); * ``` * * Example usage with TableTransaction: * ```js * const { TableClient } = require("@azure/data-tables"); * const connectionString = "<connection-string>" * const tableName = "<tableName>" * const client = TableClient.fromConnectionString(connectionString, tableName); * const transaction = new TableTransaction(); * // Call the available action in the TableTransaction object * transaction.create({partitionKey: "p1", rowKey: "1", data: "test1"}); * transaction.delete("p1", "2"); * transaction.update({partitionKey: "p1", rowKey: "3", data: "newTest"}, "Merge") * // submitTransaction with the actions list on the transaction. * const result = await client.submitTransaction(transaction.actions); * ``` * * @param actions - tuple that contains the action to perform, and the entity to perform the action with */ async submitTransaction(actions) { const partitionKey = actions[0][1].partitionKey; const transactionId = Uuid.generateUuid(); const changesetId = Uuid.generateUuid(); if (!this.transactionClient) { // Add pipeline this.transactionClient = new InternalTableTransaction(this.url, partitionKey, transactionId, changesetId, this.clientOptions, new TableClient(this.url, this.tableName), this.credential, this.allowInsecureConnection); } else { this.transactionClient.reset(transactionId, changesetId, partitionKey); } for (const item of actions) { const [action, entity, updateMode = "Merge"] = item; switch (action) { case "create": this.transactionClient.createEntity(entity); break; case "delete": this.transactionClient.deleteEntity(entity.partitionKey, entity.rowKey); break; case "update": this.transactionClient.updateEntity(entity, updateMode); break; case "upsert": this.transactionClient.upsertEntity(entity, updateMode); } } return this.transactionClient.submitTransaction(); } /** * * Creates an instance of TableClient from connection string. * * @param connectionString - Account connection string or a SAS connection string of an Azure storage account. * [ Note - Account connection string can only be used in NODE.JS runtime. ] * Account connection string example - * `DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=accountKey;EndpointSuffix=core.windows.net` * SAS connection string example - * `BlobEndpoint=https://myaccount.table.core.windows.net/;QueueEndpoint=https://myaccount.queue.core.windows.net/;FileEndpoint=https://myaccount.file.core.windows.net/;TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sasString` * @param options - Options to configure the HTTP pipeline. * @returns A new TableClient from the given connection string. */ static fromConnectionString(connectionString, tableName, // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options) { const { url, options: clientOptions, credential, } = getClientParamsFromConnectionString(connectionString, options); if (credential) { return new TableClient(url, tableName, credential, clientOptions); } else { return new TableClient(url, tableName, clientOptions); } } } //# sourceMappingURL=TableClient.js.map