@azure/data-tables
Version:
An isomorphic client library for the Azure Tables service.
585 lines • 28.4 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { __asyncDelegator, __asyncGenerator, __asyncValues, __await, __rest } from "tslib";
import { GeneratedClient } from "./generated";
import { isNamedKeyCredential, isSASCredential, isTokenCredential } from "@azure/core-auth";
import { COSMOS_SCOPE, 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 { Uuid } from "./utils/uuid";
import { apiVersionPolicy } from "./utils/apiVersionPolicy";
import { cosmosPatchPolicy } from "./cosmosPathPolicy";
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 { setTokenChallengeAuthenticationPolicy } from "./utils/challengeAuthenticationUtils";
import { tablesNamedKeyCredentialPolicy } from "./tablesNamedCredentialPolicy";
import { tablesSASTokenPolicy } from "./tablesSASTokenPolicy";
import { tracingClient } from "./utils/tracing";
/**
* 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 isCosmos = isCosmosEndpoint(this.url);
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;
const internalPipelineOptions = Object.assign(Object.assign({}, this.clientOptions), { endpoint: this.clientOptions.endpoint || this.url, loggingOptions: {
logger: logger.info,
additionalAllowedHeaderNames: [...TablesLoggingAllowedHeaderNames],
}, deserializationOptions: {
parseXML,
}, serializationOptions: {
stringifyXML,
} });
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 (isTokenCredential(credential)) {
const scope = isCosmos ? COSMOS_SCOPE : STORAGE_SCOPE;
setTokenChallengeAuthenticationPolicy(generatedClient.pipeline, credential, scope);
}
if (isCosmos) {
generatedClient.pipeline.addPolicy(cosmosPatchPolicy());
}
if (options.version) {
generatedClient.pipeline.addPolicy(apiVersionPolicy(options.version));
}
this.generatedClient = generatedClient;
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
deleteTable(options = {}) {
return tracingClient.withSpan("TableClient.deleteTable", options, async (updatedOptions) => {
try {
await this.table.delete(this.tableName, updatedOptions);
}
catch (e) {
if (e.statusCode === 404) {
logger.info("TableClient.deleteTable: Table doesn't exist");
}
else {
throw e;
}
}
});
}
/**
* 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
createTable(options = {}) {
return tracingClient.withSpan("TableClient.createTable", options, async (updatedOptions) => {
try {
await this.table.create({ name: this.tableName }, updatedOptions);
}
catch (e) {
handleTableAlreadyExists(e, Object.assign(Object.assign({}, updatedOptions), { logger, tableName: this.tableName }));
}
});
}
/**
* 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);
* ```
*/
getEntity(partitionKey, rowKey,
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("TableClient.getEntity", options, async (updatedOptions) => {
let parsedBody;
function onResponse(rawResponse, flatResponse) {
parsedBody = rawResponse.parsedBody;
if (updatedOptions.onResponse) {
updatedOptions.onResponse(rawResponse, flatResponse);
}
}
const { disableTypeConversion, queryOptions } = updatedOptions, getEntityOptions = __rest(updatedOptions, ["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;
});
}
/**
* 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 _a, e_1, _b, _c;
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 _d = true, _e = __asyncValues(this.listEntitiesPage(tableName, optionsWithContinuation)), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const page = _c;
yield __await(yield* __asyncDelegator(__asyncValues(page)));
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
}
finally { if (e_1) throw e_1.error; }
}
}
});
}
listEntitiesPage(tableName_1) {
return __asyncGenerator(this, arguments, function* listEntitiesPage_1(tableName, options = {}) {
let result = yield __await(tracingClient.withSpan("TableClient.listEntitiesPage", options, (updatedOptions) => this._listEntities(tableName, updatedOptions)));
yield yield __await(result);
while (result.continuationToken) {
const optionsWithContinuation = Object.assign(Object.assign({}, options), { continuationToken: result.continuationToken });
result = yield __await(tracingClient.withSpan("TableClient.listEntitiesPage", optionsWithContinuation, (updatedOptions, span) => {
span.setAttribute("continuationToken", result.continuationToken);
return this._listEntities(tableName, updatedOptions);
}));
yield yield __await(result);
}
});
}
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!"});
* ```
*/
createEntity(entity,
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("TableClient.createEntity", options, (updatedOptions) => {
const createTableEntity = __rest(updatedOptions || {}, []);
return this.table.insertEntity(this.tableName, Object.assign(Object.assign({}, createTableEntity), { tableEntityProperties: serialize(entity), responsePreference: "return-no-content" }));
});
}
/**
* 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>")
* ```
*/
deleteEntity(partitionKey, rowKey,
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("TableClient.deleteEntity", options, (updatedOptions) => {
const { etag = "*" } = updatedOptions, rest = __rest(updatedOptions, ["etag"]);
const deleteOptions = Object.assign({}, rest);
return this.table.deleteEntity(this.tableName, escapeQuotes(partitionKey), escapeQuotes(rowKey), etag, deleteOptions);
});
}
/**
* 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")
* ```
*/
updateEntity(entity, mode = "Merge",
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("TableClient.updateEntity", options, async (updatedOptions) => {
const partitionKey = escapeQuotes(entity.partitionKey);
const rowKey = escapeQuotes(entity.rowKey);
const _a = updatedOptions || {}, { etag = "*" } = _a, updateEntityOptions = __rest(_a, ["etag"]);
if (mode === "Merge") {
return this.table.mergeEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity), ifMatch: etag }, updateEntityOptions));
}
if (mode === "Replace") {
return this.table.updateEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity), ifMatch: etag }, updateEntityOptions));
}
throw new Error(`Unexpected value for update mode: ${mode}`);
}, {
spanAttributes: {
updateEntityMode: mode,
},
});
}
/**
* 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")
* ```
*/
upsertEntity(entity, mode = "Merge",
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("TableClient.upsertEntity", options, async (updatedOptions) => {
const partitionKey = escapeQuotes(entity.partitionKey);
const rowKey = escapeQuotes(entity.rowKey);
if (mode === "Merge") {
return this.table.mergeEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity) }, updatedOptions));
}
if (mode === "Replace") {
return this.table.updateEntity(this.tableName, partitionKey, rowKey, Object.assign({ tableEntityProperties: serialize(entity) }, updatedOptions));
}
throw new Error(`Unexpected value for update mode: ${mode}`);
}, {
spanAttributes: {
upsertEntityMode: mode,
},
});
}
/**
* Retrieves details about any stored access policies specified on the table that may be used with
* Shared Access Signatures.
* @param options - The options parameters.
*/
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
getAccessPolicy(options = {}) {
return tracingClient.withSpan("TableClient.getAccessPolicy", options, async (updatedOptions) => {
const signedIdentifiers = await this.table.getAccessPolicy(this.tableName, updatedOptions);
return deserializeSignedIdentifier(signedIdentifiers);
});
}
/**
* 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.
*/
setAccessPolicy(tableAcl,
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("TableClient.setAccessPolicy", options, (updatedOptions) => {
const serlializedAcl = serializeSignedIdentifiers(tableAcl);
return this.table.setAccessPolicy(this.tableName, Object.assign(Object.assign({}, updatedOptions), { tableAcl: serlializedAcl }));
});
}
/**
* 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();
// Add pipeline
const transactionClient = new InternalTableTransaction(this.url, partitionKey, transactionId, changesetId, this.generatedClient, new TableClient(this.url, this.tableName), this.credential, this.allowInsecureConnection);
for (const item of actions) {
const [action, entity, updateMode = "Merge", updateOptions] = item;
switch (action) {
case "create":
transactionClient.createEntity(entity);
break;
case "delete":
transactionClient.deleteEntity(entity.partitionKey, entity.rowKey);
break;
case "update":
transactionClient.updateEntity(entity, updateMode, updateOptions);
break;
case "upsert":
transactionClient.upsertEntity(entity, updateMode);
}
}
return 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