UNPKG

@azure/data-tables

Version:
632 lines • 28.6 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { GeneratedClient } from "./generated/index.js"; import { isNamedKeyCredential, isSASCredential, isTokenCredential } from "@azure/core-auth"; import { COSMOS_SCOPE, STORAGE_SCOPE, TablesLoggingAllowedHeaderNames } from "./utils/constants.js"; import { decodeContinuationToken, encodeContinuationToken } from "./utils/continuationToken.js"; import { deserialize, deserializeObjectsArray, deserializeSignedIdentifier, serialize, serializeQueryOptions, serializeSignedIdentifiers, } from "./serialization.js"; import { parseXML, stringifyXML } from "@azure/core-xml"; import { InternalTableTransaction } from "./TableTransaction.js"; import { Uuid } from "./utils/uuid.js"; import { apiVersionPolicy } from "./utils/apiVersionPolicy.js"; import { cosmosPatchPolicy } from "./cosmosPathPolicy.js"; import { escapeQuotes } from "./odata.js"; import { getClientParamsFromConnectionString } from "./utils/connectionString.js"; import { handleTableAlreadyExists } from "./utils/errorHelpers.js"; import { isCosmosEndpoint } from "./utils/isCosmosEndpoint.js"; import { isCredential } from "./utils/isCredential.js"; import { logger } from "./logger.js"; import { setTokenChallengeAuthenticationPolicy } from "./utils/challengeAuthenticationUtils.js"; import { tablesNamedKeyCredentialPolicy } from "./tablesNamedCredentialPolicy.js"; import { tablesSASTokenPolicy } from "./tablesSASTokenPolicy.js"; import { tracingClient } from "./utils/tracing.js"; /** * A TableClient represents a Client to the Azure Tables service allowing you * to perform operations on a single table. */ export class TableClient { /** * Table Account URL */ url; /** * Represents a pipeline for making a HTTP request to a URL. * Pipelines can have multiple policies to manage manipulating each request before and after it is made to the server. */ pipeline; table; generatedClient; credential; clientOptions; allowInsecureConnection; /** * Name of the table to perform operations on. */ tableName; constructor(url, tableName, credentialOrOptions, options = {}) { 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 = this.clientOptions.allowInsecureConnection ?? false; const internalPipelineOptions = { ...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 * ```ts snippet:ReadmeSampleDeleteTable * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * await client.deleteTable(); * ``` */ 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 * ```ts snippet:ReadmeSampleTableClientCreateTable * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * // If the table 'newTable' already exists, createTable doesn't throw * await client.createTable(); * ``` */ createTable(options = {}) { return tracingClient.withSpan("TableClient.createTable", options, async (updatedOptions) => { try { await this.table.create({ name: this.tableName }, updatedOptions); } catch (e) { handleTableAlreadyExists(e, { ...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 * ```ts snippet:ReadmeSampleGetEntity * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * const entity = await client.getEntity("<partitionKey>", "<rowKey>"); * console.log(`Entity: PartitionKey: ${entity.partitionKey} RowKey: ${entity.rowKey}`); * ``` */ getEntity(partitionKey, rowKey, // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { if (partitionKey === undefined || partitionKey === null) { throw new Error("The entity's partitionKey cannot be undefined or null."); } if (rowKey === undefined || rowKey === null) { throw new Error("The entity's rowKey cannot be undefined or null."); } 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, ...getEntityOptions } = updatedOptions; await this.table.queryEntitiesWithPartitionAndRowKey(this.tableName, escapeQuotes(partitionKey), escapeQuotes(rowKey), { ...getEntityOptions, queryOptions: serializeQueryOptions(queryOptions || {}), onResponse, }); const tableEntity = deserialize(parsedBody, disableTypeConversion ?? false); return tableEntity; }); } /** * Queries entities in a table. * @param options - The options parameters. * * Example listing entities * ```ts snippet:ReadmeSampleListEntities * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * let i = 0; * const entities = client.listEntities(); * for await (const entity of entities) { * console.log(`Entity${++i}: PartitionKey: ${entity.partitionKey} RowKey: ${entity.rowKey}`); * } * ``` */ 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 = { ...options, queryOptions: { ...options.queryOptions, top: settings?.maxPageSize }, }; if (settings?.continuationToken) { pageOptions.continuationToken = settings.continuationToken; } return this.listEntitiesPage(tableName, pageOptions); }, }; } async *listEntitiesAll(tableName, options) { const firstPage = await this._listEntities(tableName, options); yield* firstPage; if (firstPage.continuationToken) { const optionsWithContinuation = { ...options, continuationToken: firstPage.continuationToken, }; for await (const page of this.listEntitiesPage(tableName, optionsWithContinuation)) { yield* page; } } } async *listEntitiesPage(tableName, options = {}) { let result = await tracingClient.withSpan("TableClient.listEntitiesPage", options, (updatedOptions) => this._listEntities(tableName, updatedOptions)); yield result; while (result.continuationToken) { const optionsWithContinuation = { ...options, continuationToken: result.continuationToken, }; result = await tracingClient.withSpan("TableClient.listEntitiesPage", optionsWithContinuation, (updatedOptions, span) => { span.setAttribute("continuationToken", result.continuationToken); return this._listEntities(tableName, updatedOptions); }); yield result; } } async _listEntities(tableName, options = {}) { const { disableTypeConversion = false } = options; const queryOptions = serializeQueryOptions(options.queryOptions || {}); const listEntitiesOptions = { ...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 ?? [], 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 * ```ts snippet:ReadmeSampleCreateEntity * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * const testEntity = { * partitionKey: "P1", * rowKey: "R1", * foo: "foo", * bar: 123, * }; * await client.createEntity(testEntity); * ``` */ createEntity(entity, options = {}) { return tracingClient.withSpan("TableClient.createEntity", options, (updatedOptions) => { const { ...createTableEntity } = updatedOptions || {}; return this.table.insertEntity(this.tableName, { ...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 * ```ts snippet:ReadmeSampleDeleteEntity * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * // deleteEntity deletes the entity that matches exactly the partitionKey and rowKey * await client.deleteEntity("<partitionKey>", "<rowKey>"); * ``` */ deleteEntity(partitionKey, rowKey, // eslint-disable-next-line @azure/azure-sdk/ts-naming-options options = {}) { if (partitionKey === undefined || partitionKey === null) { throw new Error("The entity's partitionKey cannot be undefined or null."); } if (rowKey === undefined || rowKey === null) { throw new Error("The entity's rowKey cannot be undefined or null."); } return tracingClient.withSpan("TableClient.deleteEntity", options, (updatedOptions) => { const { etag = "*", ...rest } = updatedOptions; const deleteOptions = { ...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 * ```ts snippet:ReadmeSampleUpdateEntity * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * 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 = {}) { if (entity.partitionKey === undefined || entity.partitionKey === null) { throw new Error("The entity's partitionKey cannot be undefined or null."); } if (entity.rowKey === undefined || entity.rowKey === null) { throw new Error("The entity's rowKey cannot be undefined or null."); } return tracingClient.withSpan("TableClient.updateEntity", options, async (updatedOptions) => { const partitionKey = escapeQuotes(entity.partitionKey); const rowKey = escapeQuotes(entity.rowKey); const { etag = "*", ...updateEntityOptions } = updatedOptions || {}; if (mode === "Merge") { return this.table.mergeEntity(this.tableName, partitionKey, rowKey, { tableEntityProperties: serialize(entity), ifMatch: etag, ...updateEntityOptions, }); } if (mode === "Replace") { return this.table.updateEntity(this.tableName, partitionKey, rowKey, { 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 * ```ts snippet:ReadmeSampleUpsertEntity * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * 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", options = {}) { return tracingClient.withSpan("TableClient.upsertEntity", options, async (updatedOptions) => { if (entity.partitionKey === undefined || entity.partitionKey === null) { throw new Error("The entity's partitionKey cannot be undefined or null."); } if (entity.rowKey === undefined || entity.rowKey === null) { throw new Error("The entity's rowKey cannot be undefined or null."); } const partitionKey = escapeQuotes(entity.partitionKey); const rowKey = escapeQuotes(entity.rowKey); if (mode === "Merge") { return this.table.mergeEntity(this.tableName, partitionKey, rowKey, { tableEntityProperties: serialize(entity), ...updatedOptions, }); } if (mode === "Replace") { return this.table.updateEntity(this.tableName, partitionKey, rowKey, { 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. */ 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, options = {}) { return tracingClient.withSpan("TableClient.setAccessPolicy", options, (updatedOptions) => { const serlializedAcl = serializeSignedIdentifiers(tableAcl); return this.table.setAccessPolicy(this.tableName, { ...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: * ```ts snippet:ReadmeSampleSubmitTransaction * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient, TransactionAction } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * const actions: TransactionAction[] = [ * ["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: * ```ts snippet:ReadmeSampleSubmitTransactionWithTableTransaction * import { DefaultAzureCredential } from "@azure/identity"; * import { TableClient, TableTransaction } from "@azure/data-tables"; * * const account = "<account>"; * const accountKey = "<accountkey>"; * const tableName = "<tableName>"; * * const credential = new DefaultAzureCredential(); * const client = new TableClient(`https://${account}.table.core.windows.net`, tableName, credential); * * const transaction = new TableTransaction(); * * // Call the available action in the TableTransaction object * transaction.createEntity({ partitionKey: "p1", rowKey: "1", data: "test1" }); * transaction.deleteEntity("p1", "2"); * transaction.updateEntity({ 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 * @param options - Options for the request. */ async submitTransaction(actions, options = {}) { 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(options); } /** * * 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