diff --git a/documentation/DPG-quickstart.md b/documentation/DPG-quickstart.md index 621eb4d8347b..37e3e81e567d 100644 --- a/documentation/DPG-quickstart.md +++ b/documentation/DPG-quickstart.md @@ -3,8 +3,6 @@ Getting Started: Generate JavaScript/TypeScript DPG Libraries with TypeSpec # Before you start -:warning: **Important**: Only proceed with DPG code generation for JavaScript/TypeScript if you have been explicitly instructed to do so by the AZSDK Architecture Board. Otherwise, please use the [RLC Quickstart Guide](https://aka.ms/azsdk/rlc/js). - :warning: Ensure that your TypeSpec definition has been merged into the main branch of the [Azure REST API specs repository](https://github.com/Azure/azure-rest-api-specs) before you begin. For an overview of the review and release process for new libraries, visit: https://aka.ms/azsdk/dpcodegen. diff --git a/documentation/MIGRATION-guide-for-modularized-libraries.md b/documentation/MIGRATION-guide-for-modularized-libraries.md new file mode 100644 index 000000000000..4295901081dc --- /dev/null +++ b/documentation/MIGRATION-guide-for-modularized-libraries.md @@ -0,0 +1,253 @@ +# Guide for migrating to the modularized libraries of Azure JavaScript SDK + +This guide helps developers transition their JavaScript/TypeScript applications to use the modularized Azure SDK libraries, also known as Modular SDKs. + +**For new customers of the JavaScript/TypeScript SDK ([azure-sdk-for-js](https://github.com/Azure/azure-sdk-for-js)), please see [quick start guide for modularized libraries](https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/modularized-libraries-quickstart.md).** + +## Current status + +Several packages released from Modular libraries have already reached General Availability (GA), including `@azure/arm-avs`, `@azure/arm-fabric`, `@azure/arm-oracledatabase`, `@azure/keyvault-admin`. We are actively working on releasing more packages and eventually cover all Azure services. Please find the latest version of those libraries in [npm](https://www.npmjs.com) and give them a try. + +## Why switching to the modularized libraries? + +We recommend reviewing the [complete guide for the modularized libraries](https://devblogs.microsoft.com/azure-sdk/azure-sdk-modularized-libraries-for-javascript/) for full details. Compared to the traditional libraries, Modular SDKs have following key benefits: + +1. Subpath exports: Modular SDKs use [subpath exports](https://nodejs.org/api/packages.html#subpath-exports)(available since Node.js version 12.7) to offer layered APIs. In which service client layer from `.` root subpath would provide similar experience to traditional client. And the API layer from `./api` subpath would provide more lightweight client context for shared state across operations. +1. Bundle size optimization: Modular SDKs leverage @azure-rest/core-client, which offers improved bundle size efficiency compared to the previous Azure core libraries. This core package provides a general-purpose REST client, while each service-specific package includes its own TypeScript type definitions. These TypeScript types are excluded from the final asset bundle, helping to minimize overall bundle size. +1. Long-running operations: Instead of two methods (beginDoSth and beginDoSthAndWait) in the traditional clients for each long-running operation, which are both redundant and confusing to customers. Modular SDKs offer a single method (doSth) that supports both async and sync usage. + + +## How to migrate to the modularized libraries? + +If you're updating an existing app to use Modular SDKs, focus on these areas: + +1. Long-running Operations +1. List Operations +1. Model Property Flattening + +### Long-running Operations + +Many operations may take a long time to finish before receiving the desired response named long-running operations. We re-designed LRO in Modular SDKs. The changes mainly are three parts: + +- Method signature changes +- LRO poller changes from SimplePollerLike to PollerLike +- Rehydration changes + +#### Method signature changes + +Taking a simple LRO operation as an example with operationId `IntegrationRuntimes_Start`. In traditional client we would have two methods([link](https://github.com/Azure/azure-sdk-for-js/blob/8c1c0027d79354d2b91b318c4ceb52e462f7db92/sdk/datafactory/arm-datafactory/src/operationsInterfaces/integrationRuntimes.ts#L193)). + +```ts +beginStart( + options?: IntegrationRuntimesStartOptionalParams, + ): Promise< + SimplePollerLike< + OperationState, + IntegrationRuntimesStartResponse + > + >; +beginStartAndWait( + options?: IntegrationRuntimesStartOptionalParams, + ): Promise; +``` +Now we would only have one method in Modular SDK. + +```ts +start(options?: IntegrationRuntimesStartOptionalParams): PollerLike< + OperationState, + IntegrationRuntimesStartResponse + >; +``` +So the before-and-after code would be like below: +| traditional client | Modular | +|----------------------------------------------|--------------------------------------------------------------------------------| +| const result = await beginStartAndWait(); | const result = await start(); // awaiting would get result directly | +| const poller = await beginStart(); | const poller = start(); // directly get the poller | +| | await poller.submitted(); // await the poller submitted if interested | +| const result = await poller.pollUntilDone(); | const result = await poller; // Or const result = await poller.pollUntilDone() | + +#### LRO poller change from SimplePollerLike to PollerLike + +In traditional client, the return type of `beginXXX` method is `SimplePollerLike`. Now the return type is changed to `PollerLike` in Modular and this interface is also a PromiseLike. The following table compares `SimplePollerLike` and `PollerLike`: + +| operation | `SimplePollerLike` | `PollerLike` | +| -------------------------------------------------------------------------- | --------------------- | ----------------- | +| return final results | `pollUntilDone()` | `pollUntilDone()` | +| poll | `poll()` | `poll()` | +| access the current state after receiving the response of each poll request | `onProgress()` | `onProgress()` | +| check whether the operation finished | `isDone()` | `isDone` | +| stop polling | `stopPolling()` | N/A | +| check if the polling stopped | `isStopped()` | N/A | +| get the current operation state | `getOperationState()` | `operationState` | +| access the final result | `getResult()` | `result` | +| serialize the poller state | `toString()` | `serialize()` | +| wait the poller submitted successfully | N/A | `submitted()` | + +Please note the operation `getOperationState(): TState` is changed to attribute `operationState: TState | undefined`, so the value could be `undefined` if the poller is not initialized yet. + +```ts +const status = poller.getOperationState().status; +``` + +now + +```ts +const status = poller?.operationState?.status; +``` + +If you want to serialize a poller, use the `serialize` instead. + +```ts +const serializeState = poller.toString(); +``` + +now + +```ts +const serializeState = await poller.serialize(); +``` +#### Rehydration change + +We also change the way to restore an existing LRO. The main change is we deliver the restore functionality as a helper function not binding with methods. + +In traditional client we build an option `resumeFrom`. + +```ts +export interface IntegrationRuntimesStartOptionalParams + extends coreClient.OperationOptions { + /** A serialized poller which can be used to resume an existing paused Long-Running-Operation. */ + resumeFrom?: string; +} +``` +Now we build a client-level helper for this. +```ts +export function restorePoller( + client: DataFactoryManagementClient, + serializedState: string, + sourceOperation: ( + ...args: any[] + ) => PollerLike, TResult>, + options?: RestorePollerOptions, +): PollerLike, TResult> +``` + +The before-and-after code would be like: + +```ts +// traditional client +const result = await client.beginStartAndWait({resumeFrom: serializedState}); + +// Modular +const result = await restorePoller(client, serializedState, client.start); +``` +If you are interested in more details in core-lro and here is the [migration guide](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-lro/docs/MIGRATION.md). + +### List Operations +In Modular we adjusted paging interfaces a little for better experience and mainly are two parts: +- Remove un-supported maxpagesize in PageSetting +- Use `continuationToken` to replace the helper `getContinuationToken` + +#### Remove un-supported maxpagesize in PageSetting +The `maxpagesize` is not supported in traditional client so in Modular we remove this setting within PageSettings. These changes are supposed to have no impact for customers. + +#### Remove the helper `getContinuationToken` +In traditional client we build an util function to help customers to get the continuation token([here](https://github.com/Azure/azure-sdk-for-js/blob/735677407c4fbbceea95200f6d6de00e29804740/sdk/datadog/arm-datadog/src/pagingHelper.ts#L22-L29)). + +So we could reference the token by code. + +```ts +const firstPage = await iter.byPage().next(); +const continuationToken = getContinuationToken(firstPage); +``` + +Now we directly deliver the `continuationToken` with byPage return. + +```ts +export type ContinuablePage = TPage & { + /** + * The token that keeps track of where to continue the iterator + */ + continuationToken?: string; +}; +``` +So we could reference the token like below: +```ts +const firstPage = await iter.byPage().next(); +const continuationToken = firstPage.value.continuationToken; +``` + +## Model Property Flattening + +Client libraries represent entities transferred to and from Azure services as model types. For the model types, in the traditional client we have supported the autorest extension [x-ms-client-flatten](https://azure.github.io/autorest/extensions/#x-ms-client-flatten). This extension allows to flatten deeply nested payloads into a top-level object structure. For example a payload that looks like this on the wire: + +```json +{ + "hcxEnterpriseSite": { + "name": "hcxEnterpriseSiteName", + "properties": { + "provisioningState": "succeed", + "activationKey": "value2", + "status": "ok" + }, + }, +} +``` +Can be transformed into the following client model and see [generated code](https://github.com/Azure/azure-sdk-for-js/blob/835b3dca8d8c635c1471a8264b025409a75298fc/sdk/avs/arm-avs/src/models/index.ts#L1196C1-L1213C2): +```ts +/** An HCX Enterprise Site resource */ +export interface HcxEnterpriseSite extends ProxyResource { + /** + * The provisioning state of the resource. + * NOTE: This property will not be serialized. It can only be populated by the server. + */ + readonly provisioningState?: HcxEnterpriseSiteProvisioningState; + /** + * The activation key + * NOTE: This property will not be serialized. It can only be populated by the server. + */ + readonly activationKey?: string; + /** + * The status of the HCX Enterprise Site + * NOTE: This property will not be serialized. It can only be populated by the server. + */ + readonly status?: HcxEnterpriseSiteStatus; +} +``` + +Modular SDKs no longer support flattening to reduce confusion and maintenance overhead. So now [the model](https://github.com/azure/azure-sdk-for-js/blob/181311fe630b5609e78d55306ad2242bb881dacf/sdk/avs/arm-avs/src/models/models.ts#L3171-L3174) would be generated like below: +```ts +/** An HCX Enterprise Site resource */ +export interface HcxEnterpriseSite extends ProxyResource { + /** The resource-specific properties for this resource. */ + properties?: HcxEnterpriseSiteProperties; +} + +/** The properties of an HCX Enterprise Site */ +export interface HcxEnterpriseSiteProperties { + /** The provisioning state of the resource. */ + readonly provisioningState?: HcxEnterpriseSiteProvisioningState; + /** The activation key */ + readonly activationKey?: string; + /** The status of the HCX Enterprise Site */ + readonly status?: HcxEnterpriseSiteStatus; +} +``` + +Which means for these changes, we need to update our code from `result.activationKey` to `result.properties?.activationKey`. So the before-and-after code would be like: + +```ts +// traditional client +const result = await client.hcxEnterpriseSites.get("resourceGroupName", "privateCloudName", "hcxEnterpriseSiteName"); +console.log(result.activationKey); + +// Modular client +const result = await client.hcxEnterpriseSites.get("resourceGroupName", "privateCloudName", "hcxEnterpriseSiteName"); +console.log(result.properties?.activationKey); +``` + +Please note for Azure models, majority of property flatten happened in `properties` property. + + +## Need help + +If you have encountered an issue during migration, please file an issue via [GitHub Issues](https://github.com/Azure/azure-sdk-for-js/issues) and make sure you add the 'Preview' label to the issue. diff --git a/documentation/RLC-quickstart.md b/documentation/RLC-quickstart.md index 982c6153443e..dc2f77dbc65c 100644 --- a/documentation/RLC-quickstart.md +++ b/documentation/RLC-quickstart.md @@ -3,6 +3,8 @@ Getting Started: Generate JavaScript/TypeScript RLC (Rest Level Client) Librarie # Before you start +:warning: **Important**: Only proceed with RLC code generation for JavaScript/TypeScript if you have been explicitly instructed to do so by the AZSDK Architecture Board. Otherwise, please use the [DPG Quickstart Guide](https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/DPG-quickstart.md). + :warning: Ensure that your TypeSpec definition has been merged into the main branch of the [Azure REST API specs repository](https://github.com/Azure/azure-rest-api-specs) before you begin. For an overview of the review and release process for new libraries, visit: https://aka.ms/azsdk/dpcodegen. diff --git a/documentation/modularized-libraries-quickstart.md b/documentation/modularized-libraries-quickstart.md new file mode 100644 index 000000000000..80abe4d977ca --- /dev/null +++ b/documentation/modularized-libraries-quickstart.md @@ -0,0 +1,316 @@ +# Getting Started - Azure SDK modularized libraries for JavaScript/TypeScript + +We are excited to introduce the Azure SDK modular libraries for JavaScript/TypeScript, also known as Modular SDKs. These libraries offer enhanced usability, built on top of Azure Core, delivering consistent tooling and out-of-the-box benefits. They also provide greater flexibility with reduced bundle sizes, while minimizing disruptions for existing client users. + +Several packages have already reached General Availability (GA), including `@azure/arm-avs`, `@azure/arm-fabric`, `@azure/arm-oracledatabase`, `@azure/keyvault-admin`. You can find the latest versions of these libraries on [npm](https://www.npmjs.com) and give them a try. + +Modular SDKs are now our recommended approach for both data-plane and management-plane libraries. This quickstart guide walks you through how to begin interacting with Azure management resources using Modular SDKs. For a deeper understanding, visit [the page](https://devblogs.microsoft.com/azure-sdk/azure-sdk-modularized-libraries-for-javascript/). + +## Migrating from an older generation of Azure libraries for JavaScript/TypeScript + +If you're currently using an older generation of the Azure SDK for JavaScript/TypeScript and want to upgrade, refer to our [migration guide](https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/MIGRATION-guide-for-modularized-libraries.md). + +## Prerequisites + +There are several possible approaches to Azure authentication. In this document we would use the application secret authentication. You will need the following values: + +- **Subscription ID** +- **Client ID** +- **Client Secret** +- **Tenant ID** + +These values can be obtained from the portal. You could follow the [instructions](https://learn.microsoft.com/azure/azure-portal/get-subscription-tenant-id) to get Subscription ID. And please refer to [this document](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal) to get Client ID, Client Secret, and Tenant ID. After you obtain the values, you need to set the following values as your environment variables. + +- `AZURE_CLIENT_ID` +- `AZURE_CLIENT_SECRET` +- `AZURE_TENANT_ID` +- `AZURE_SUBSCRIPTION_ID` + +## Install the package + +As an example, to install the latest Azure VMware Solution module, you would run: + +```sh +npm i @azure/arm-avs@latest +``` + +We also recommend installing the authentication package: + +```sh +npm i @azure/identity +``` + +## Authentication + +Once the environment is setup, all you need to do is to create an authenticated client. The `@azure/identity` module provides facilities for various ways of authenticating with Azure including client/secret, certificate, managed identity, and more. + +Our default option is to use **DefaultAzureCredential** which will make use of the environment variables we have set and take care of the authentication flow for us. + +```typescript +const credential = new DefaultAzureCredential(); +``` + +For more details on how authentication works in `@azure/identity`, please see the documentation for [`@azure/identity`](https://www.npmjs.com/package/@azure/identity). + +## Creating a Resource Management Client + +To begin, determine the target service and create a client to connect to it. In this example, we will use `AzureVMwareSolutionAPI` as the service. To manage a private cloud resource, you can instantiate the client as follows: + +```typescript +const client = new AzureVMwareSolutionAPI(credential, subscriptionId); +``` + +## Interacting with Azure Resources + +Once authenticated and the client is created, you can use it to perform API operations. In resource management scenarios, common operations include creating, updating, reading, and deleting Azure resources. These operations are referred to as "management operations" in Azure. +After identifying the specific operation you want to perform, you can implement it using the management client initialized above. + +We will walk through two examples: + +- **Example 1**: Creating a private cloud resource using the Azure VMware Solution client. +- **Example 2**: Managing a private cloud with the Azure SDK for JavaScript/TypeScript. + +### Example 1: Create a private cloud resource + +**_Import the packages_** +TypeScript + +```typescript +import { AzureVMwareSolutionAPI } from "@azure/arm-avs"; +import { DefaultAzureCredential } from "@azure/identity"; +``` + +JavaScript + +```javascript +const { AzureVMwareSolutionAPI } = require("@azure/arm-avs"); +const { DefaultAzureCredential } = require("@azure/identity"); +``` + +**_Define some global variables_** +TypeScript or JavaScript + +```typescript +const subscriptionId = process.env.AZURE_SUBSCRIPTION_ID; +const credential = new DefaultAzureCredential(); +const client = new AzureVMwareSolutionAPI(credential, subscriptionId); +``` + +**_Create a private cloud_** +TypeScript + +```typescript +async function privateCloudsCreateOrUpdate(resourceGroupName: string, privateCloudName: string): Promise { + const result = await client.privateClouds.createOrUpdate(resourceGroupName, privateCloudName, { + location: "eastus2", + sku: { name: "AV36" }, + identity: { type: "SystemAssigned" }, + properties: { + networkBlock: "192.168.48.0/22", + managementCluster: { clusterSize: 4 }, + }, + tags: {}, + }); + console.log(result); +} +``` + +JavaScript + +```javascript +async function privateCloudsCreateOrUpdate(resourceGroupName, privateCloudName) { + const result = await client.privateClouds.createOrUpdate(resourceGroupName, privateCloudName, { + location: "eastus2", + sku: { name: "AV36" }, + identity: { type: "SystemAssigned" }, + properties: { + networkBlock: "192.168.48.0/22", + managementCluster: { clusterSize: 4 }, + }, + tags: {}, + }); + console.log(result); +} +``` + +### Example 2: Manage a private cloud with the Azure SDK + +**_Import the packages_** +TypeScript + +```typescript +import { AzureVMwareSolutionAPI } from "@azure/arm-avs"; +import { DefaultAzureCredential } from "@azure/identity"; +``` + +JavaScript + +```javascript +const { AzureVMwareSolutionAPI } = require("@azure/arm-avs"); +const { DefaultAzureCredential } = require("@azure/identity"); +``` + +**_Authentication and set up_** +TypeScript or JavaScript + +```typescript +const subscriptionId = process.env.AZURE_SUBSCRIPTION_ID; +const credential = new DefaultAzureCredential(); +const client = new AzureVMwareSolutionAPI(credential, subscriptionId); +``` + + +**_Update a private cloud_** +TypeScript + +```typescript +async function privateCloudsUpdate(resourceGroupName: string, privateCloudName: string): Promise { + const result = await client.privateClouds.update(resourceGroupName, privateCloudName, { + identity: { type: "None" }, + properties: { + managementCluster: { clusterSize: 4 }, + encryption: { + status: "Enabled", + keyVaultProperties: { + keyName: "keyname1", + keyVersion: "ver1.0", + keyVaultUrl: "https://keyvault1-kmip-kvault.vault.azure.net/", + }, + }, + }, + }); + console.log(result); +} +``` + +JavaScript + +```javascript +async function privateCloudsUpdate(resourceGroupName, privateCloudName) { + const result = await client.privateClouds.update(resourceGroupName, privateCloudName, { + identity: { type: "None" }, + properties: { + managementCluster: { clusterSize: 4 }, + encryption: { + status: "Enabled", + keyVaultProperties: { + keyName: "keyname1", + keyVersion: "ver1.0", + keyVaultUrl: "https://keyvault1-kmip-kvault.vault.azure.net/", + }, + }, + }, + }); + console.log(result); +} +``` + +**_List all private clouds_** +TypeScript + +```typescript +async function privateCloudsList(resourceGroupName: string): Promise { + const resArray = new Array(); + for await (const item of client.privateClouds.list(resourceGroupName)) { + resArray.push(item); + } + + console.log(resArray); +} +``` + +JavaScript + +```javascript +async function privateCloudsList(resourceGroupName) { + const resArray = new Array(); + for await (const item of client.privateClouds.list(resourceGroupName)) { + resArray.push(item); + } + + console.log(resArray); +} + +``` + +**_Get a private cloud_** +TypeScript + +```typescript +async function privateCloudsGet(resourceGroupName: string, privateCloudName: string): Promise { + const result = await client.privateClouds.get(resourceGroupName, privateCloudName); + console.log(result); +} +``` + +JavaScript + +```javascript +async function privateCloudsGet(resourceGroupName, privateCloudName) { + const result = await client.privateClouds.get(resourceGroupName, privateCloudName); + console.log(result); +} +``` + +**_Delete a private cloud_** +TypeScript + +```typescript +async function privateCloudsDelete(resourceGroupName: string, privateCloudName: string): Promise { + await client.privateClouds.delete(resourceGroupName, privateCloudName); +} +``` + +JavaScript + +```javascript +async function privateCloudsDelete(resourceGroupName, privateCloudName) { + await client.privateClouds.delete(resourceGroupName, privateCloudName); +} +``` + +**_Manage private clouds_** +TypeScript or JavaScript + +```typescript +async function main() { + const resourceGroupName = "your resource group"; + const privateCloudName = "private cloud name"; + await privateCloudsCreateOrUpdate(resourceGroupName, privateCloudName); + await privateCloudsList(resourceGroupName); + await privateCloudsGet(resourceGroupName, privateCloudName); + await privateCloudsUpdate(resourceGroupName, privateCloudName); + await privateCloudsGet(resourceGroupName, privateCloudName); + await privateCloudsDelete(resourceGroupName, privateCloudName); + await privateCloudsList(resourceGroupName); +} +``` + +## Code Samples + +You can find additional code samples for using the JavaScript/TypeScript SDK in this repo. These samples are located in the samples folder alongside the SDK source code—for example, AVS-related samples are available at https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/avs/arm-avs/samples. + +## Need help? + +- File an issue via [GitHub + Issues](https://github.com/Azure/azure-sdk-for-js/issues) + +## Contributing + +For details on contributing to this repository, see the contributing +guide. + +This project welcomes contributions and suggestions. Most contributions +require you to agree to a Contributor License Agreement (CLA) declaring +that you have the right to, and actually do, grant us the rights to use +your contribution. For details, visit . + +When you submit a pull request, a CLA-bot will automatically determine +whether you need to provide a CLA and decorate the PR appropriately +(e.g., label, comment). Simply follow the instructions provided by the +bot. You will only need to do this once across all repositories using +our CLA. + +This project has adopted the Microsoft Open Source Code of Conduct. For +more information see the Code of Conduct FAQ or contact + with any additional questions or comments.