The API surface of your client library must have the most thought as it is the primary interaction that the consumer has with your service.

Platform Support

DO support all LTS versions of Node and newer versions up to and including the latest release.

DO support the following browsers and versions:

  • Apple Safari: latest two versions
  • Google Chrome: latest two versions
  • Microsoft Edge: all supported versions
  • Mozilla FireFox: latest two versions

Use caniuse.com to determine whether you can use a given platform feature in the runtime versions you support. Syntax support is provided by TypeScript.

⚠️ YOU SHOULD NOT support IE11. If you have a business justification for IE11 support, contact the Architecture Board.

DO compile without errors on all versions of TypeScript greater than 3.1.

While consumers are fast at adopting new versions of TypeScript, version 3.1 is used by Angular 7, which is still commonly used. Supporting older versions of TypeScript can be a challenge. There are two general approaches:

  1. Don’t use new features.
  2. Use typesVersions, which might require manual effort to produce typings compatible with older versions based on the new typings.

DO get approval from the Architecture Board to drop support for any platform (except IE11 and Node 6) even if support isn’t required.

Namespaces, NPM Scopes, and Distribution Tags

DO publish your library to the @azure npm scope.

DO pick a package name that allows the consumer to tie the namespace to the service being used. As a default, use the compressed service name at the end of the namespace. The namespace does NOT change when the branding of the product changes. Avoid the use of marketing names that may change.

DO tag beta packages with the npm distribution tag next. If there is no generally available release of this package, it should also be tagged latest.

DO tag generally available npm packages latest. Generally available packages may also be tagged next if they include the changes from the most recent beta.

DO prefix your data plane package names with the kebab-case version of the appropriate namespace from the following table:

Namespace Group Functional Area
ai Artificial intelligence, including machine learning
analytics Gathering data for metrics or usage
containers Services related to containers
communication Communication services
data Dealing with structured data stores like databases
diagnostics Gathering data for diagnosing issues
digitaltwins Digital Twins, digital representations of physical spaces and IoT devices
identity Authentication and authorization
iot Internet of things
management Control Plane (Azure Resource Manager)
media Audio and video technologies
messaging Messaging services, like push notifications or pub-sub
mixedreality Mixed reality technologies
monitor Services that are offered by Azure Monitor
quantum Quantum computing technologies
search Search technologies
security Security and cryptography
storage Storage of unstructured data

For example, these package names meet the guidelines:

  • @azure/cosmos
  • @azure/storage-blob
  • @azure/digital-twins-core

The following are examples that do not meet the guidelines:

  • @microsoft/cosmos (not in @azure scope).
  • @azure/digitaltwins (not kebab-cased).

☑️ YOU SHOULD you should follow the casing conventions of any existing GA packages released in the @azure npm scope. It’s not worth renaming a package just to align on naming conventions.

The Client API

Your API surface will consist of one or more service clients that the consumer will instantiate to connect to your service, plus a set of supporting types. The basic shape of JavaScript service clients is shown in the following example:

export class ServiceClient {
  // client constructors have overloads for handling different
  // authentication schemes.
  constructor(connectionString: string, options?: ServiceClientOptions);
  constructor(host: string, credential: TokenCredential, options?: ServiceClientOptions);
  constructor(...) { }

  // Service methods. Options take at least an abortSignal.
  async createItem(options?: CreateItemOptions): CreateItemResponse;
  async deleteItem(options?: DeleteItemOptions): DeleteItemResponse;

  // Simple paginated API
  listItems(): PagedAsyncIterableIterator<Item, ItemPage> { }

  // Clients for sub-resources
  getItemClient(itemName: string) { }
}

Client constructors and factories

DO place service client types that the consumer is most likely to interact as a top-level export from your library. That is, the service client type should be something that can be imported directly by the consumer.

DO allow the consumer to construct a service client with the minimal information needed to connect and authenticate to the service.

DO standardize verb prefixes within a set of client libraries for a service (see approved verbs).

The service speaks about specific operations in a cross-language manner within outbound materials (such as documentation, blogs, and public speaking). The service can’t be consistent across languages if the same operation is referred to by different verbs in different languages.

DO support 100% of the features provided by the Azure service the client library represents.

Gaps in functionality cause confusion and frustration among developers. A feature may be omitted if there isn’t support on the platform. For example, a library that depends on local file system access may not work in a browser.

☑️ YOU SHOULD provide overloaded constructors for all client construction scenarios.

Don’t use static methods to construct a client unless an overload would be ambiguous. Prefix the static method with from if you require a static constructor.

☑️ YOU SHOULD prefer overloads over unions when either:

  1. Users want to see documentation (for example, signature help) tailored specifically to the parameters they’re passing in, or
  2. Multiple parameters are correlated.

Unions may be used if neither of these conditions are met. Let’s say we have an API that takes either two numbers or two strings but not both. In this case, the parameters are correlated. If we implemented the types using unions like the following code:

function foo(a: string | number, b: string | number): void {}

We have mistakenly allowed invalid arguments; that is foo(number, string) and foo(string, number). Overloads naturally express this correlation:

function foo(a: string, b: string): void;
function foo(a: number, b: number): void;
function foo(a: string | number, b: string | number): void {}

The overload approach also lets us attach documentation to each overload individually.

// bad example
class ExampleClient {
  constructor (connectionString: string, options: ExampleClientOptions);
  constructor (url: string, options: ExampleClientOptions);
  constructor (urlOrCS: string, options: ExampleClientOptions) {
    // have to dig into the first parameter to see whether its
    // a url or a connection string. Not ideal.
  }
}

// better example
class ExampleClient {
  constructor (url: string, options: ExampleClientOptions) {

  }

  static fromConnectionString(connectionString: string, options: ExampleClientOptions) {

  }
}

Service Versions

DO call the highest supported service API version by default.

DO allow the consumer to explicitly select a supported service API version when instantiating the client if multiple service versions are supported.

DO provide a serviceVersion option in the client constructor’s option bag for providing a service version. The type of this should be a string literal union with supported service versions. You may also provide a string enum with supported service versions.

Options

The guidelines in this section apply to options passed in options bags to clients, whether methods or constructors. When referring to option names, this means the key of the object users must use to specify that option when passing it into a method or constructor.

DO name the type of the options bag as <class name>Options and <method name>Options for constructors and methods respectively.

DO name abort signal options abortSignal.

DO suffix durations with In<Unit>. Unit should be ms for milliseconds, and otherwise the name of the unit. Examples include timeoutInMs and delayInSeconds.

Retry-specific Options

Many services have a notion of retries and have various means to configure them.

DO use the option names specified in the table below

Option Values Usage Other Names (informational)
retryMode ‘fixed’, ‘linear’, ‘exponential’ Used to specify the retry strategy  
maxRetries number >= 0 Number of times to retry. 0 effectively disables retrying.  
retryDelayInMs number > 0 Delay between retries. For linear and exponential strategies, this is the initial retry delay and increases thereafter based on the strategy used.  
maxRetryDelayInMs number > 0 Maximum delay between retries. For linear and exponential strategies, this effectively clamps the maximum amount of time between retries.  
tryTimeoutInMs number > 0 How long to wait for a particular retry to complete before giving up  

TODO: Please add a code sample showing how these fit into a track 2 JS/TS library.

DO support the following retry strategies:

  • fixed: retry after some duration, where the duration never changes.
  • exponential: retry after some duration, where the duration increases exponentially after each attempt.

TODO: Are these implemented by default in Azure Core or does the API designer need to implement these? If there is no action for the API Designer, let’s take this out.

Response formats

Requests to the service fall into two basic groups - methods that make a single logical request, or a deterministic sequence of requests. An example of a single logical request is a request that may be retried inside the operation. An example of a deterministic sequence of requests is a paged operation. The logical entity is a protocol neutral representation of a response. For HTTP, the logical entity may combine data from headers, the body, and the status line. For example, you may add the ETag header as a property on the logical entity to the deserialized content from the body of the response.

DO optimize for returning the logical entity for a given request. The logical entity MUST represent the information needed in the 99%+ case.

TODO: the above guideline is a little vague and I’m not sure how to concretely apply this in JS/TS.

DO make it possible for a developer to get access to the complete response, including the status line, headers, and body.

TODO: a code sample to go with this might be nice, or a direct discussion of the typical response format and how to do this in JS/TS. As an example of this, .NET discusses Response<T> in this section rather than the general guidelines, since Response<T> solves this problem for the API designer and then they don’t have to think of another way to do it.

DO document how to access the raw and streamed response for a request (if exposed by the client library). Include comprehensive samples. We don’t expect all methods to expose a streamed response.

TODO: Should the above guideline go in the Samples section?

For methods that combine multiple requests into a single call:

⛔️ DO NOT return headers and other per-request metadata unless it’s obvious as to which specific HTTP request the methods return value corresponds to.

TODO: It’s unclear regarding the above that this is referring to the model type. (Is it?) An example of how we’ve solved this problem in existing Track 2 JS/TS APIs would be helpful here to make this guideline more actionable.

DO provide enough information in failure cases for an application to take appropriate corrective action.

TODO: Would the above guideline go better in the Exceptions & Errors section? In the training, we say that exceptions should A) describe the problem and B) tell the developer how to solve the problem.

⚠️ YOU SHOULD NOT use the following property names within a logical entity:

  • object
  • value

Using names that are commonly used as reserved words can cause confusion and will cause consistency issues between languages.

An example:

// Service operation method on a service client
  public async getProperties(
    options: ContainerGetPropertiesOptions = {}
  ): Promise<Models.ContainerGetPropertiesResponse> {
    // ...
  }

// Response type, in this case for a service which returns the
// relevant info in headers. Note how the headers are represented
// in first-class properties with intellisense etc.
export type ContainerGetPropertiesResponse = ContainerGetPropertiesHeaders & {
  /**
   * The underlying HTTP response.
   */
  _response: msRest.HttpResponse & {
      /**
       * The parsed HTTP response headers.
       */
      parsedHeaders: ContainerGetPropertiesHeaders;
    };
};

export interface ContainerGetPropertiesHeaders {
  // ...
  /**
   * @member {PublicAccessType} [blobPublicAccess] Indicated whether data in
   * the container may be accessed publicly and the level of access. Possible
   * values include: 'container', 'blob'
   */
  blobPublicAccess?: PublicAccessType;
  /**
   * @member {boolean} [hasImmutabilityPolicy] Indicates whether the container
   * has an immutability policy set on it.
   */
  hasImmutabilityPolicy?: boolean;
}

Client naming conventions

DO name service client types with the Client suffix.

☑️ YOU SHOULD use one of the approved verbs in the below table when referring to service operations.

Verb Parameters Returns Comments
create\<Noun> key, item Created item Create new item. Fails if item already exists.
upsert\<Noun> key, item Updated or created item Create new item, or update existing item. Verb is primarily used in database-like services
set\<Noun> key, item Updated or created item Create new item, or update existing item. Verb is primarily used for dictionary-like properties of a service
update\<Noun> key, partial item Updated item Fails if item doesn’t exist.
replace\<Noun> key, item Replace existing item Completely replaces an existing item. Fails if the item doesn’t exist.
append\<Noun> item Appended item Add item to a collection. Item will be added last.
add\<Noun> index, item Added item Add item to a collection. Item will be added at the given index.
get\<Noun> key Item Will return null if item doesn’t exist
list\<Noun>s   PagedAsyncIterableIterator<TItem, TPage> Return list of items. Returns empty list if no items exist
\<noun>Exists key bool Return true if the item exists.
delete\<Noun> key None Delete an existing item. Will succeed even if item didn’t exist.
remove\<Noun> key None or removed item Remove item from a collection.

⛔️ DO NOT include the Noun when the operation is operating on the resource itself, For example, if you have an ItemClient with a delete method, it should be called delete rather than deleteItem. The noun is implicitly this.

DO prefix methods that create or vend subclients with get and suffix with client. For example, container.getBlobClient().

TODO: Put the above with the discussion of hierarchical clients?

TODO: A code sample here would help illustrate this.

The following are good examples of names for operations in a TypeScript client library:

containerClient.listBlobs();
containerClient.delete();

The following are bad examples:

containerClient.deleteContainer(); // don't include noun for direct manipulation
containerClient.newBlob(); // use create instead of new
containerClient.createOrUpdate(); // use upsert
containerClient.createBlobClient(); // should be `getBlobClient`.

Network requests

When an application makes a network request, the network infrastructure (like routers) and the called service may take a long time to respond. In fact, the network infrastructure may never respond. A well-written application should NEVER give up its control to the network infrastructure or service.

Consider the following examples. An orchestrator needs to stop a service because of a scaling operation, reconfiguration, or upgrading to a new version). The orchestrator typically notifies a running service instance by sending an interrupt signal. The service should stop as quickly as possible when it receives this signal. Similarly, when a web server receives a request, it may set a time limit indicating how much time it’s allowing before giving a response to the user. A UI application may offer the user a cancel button when making a network request.

The best way for consumers to work with cancellation is to think of cancellation objects as forming a tree. For example:

  • Cancelling a parent automatically cancels its children.
  • Children can time out sooner than their parent but can’t extend the total time.
  • Cancellation can happen because of a timeout or an explicit request.

TODO: Regarding the above discussion … is it needed? Could we just say the Azure SDK requires service calls to be cancellable and here are the rules for how to do it in JS/TS? Please consider adding a code sample for this, and if there are implementation specifics for this, it might be nice to have them in the Implementation section (but the latter is technically out of scope for MQ).

DO accept an AbortSignalLike parameter on all asynchronous calls. This type is provided by @azure/abort-controller.

☑️ YOU SHOULD only check cancellation tokens on I/O calls (such as network requests and file loads). Don’t check the cancellation token between I/O calls within the client library (for example, when processing data between I/O calls).

TODO: Does JS/TS use cancellation tokens?

⛔️ DO NOT leak the underlying protocol transport implementation details to the consumer. All types from the protocol transport implementation must be appropriately abstracted.

Authentication

Azure services use different kinds of authentication schemes to allow clients to access the service. Conceptually, there are two entities responsible in this process: a credential and an authentication policy. Credentials provide confidential authentication data. Authentication policies use the data provided by a credential to authenticate requests to the service.

DO support all authentication techniques that the service supports.

DO use credential and authentication policy implementations from the Azure Core library where available.

TODO: Please mention the specific type examples to make this more actionable.

DO provide credential types that can be used to fetch all data needed to authenticate a request to the service. Credential types should be non-blocking and atomic. Use credential types from the @azure/core-auth library where possible.

DO provide service client constructors or factories that accept any supported authentication credentials.

Client libraries may support connection strings ONLY IF the service provides a connection string to users via the portal or other tooling. Connection strings are easily integrated into an application by copy/paste from the portal. However, connection strings don’t allow the credentials to be rotated within a running process.

⛔️ DO NOT support constructing a service client with a connection string unless such connection string is available within tooling (for copy/paste operations).

TODO: Please make this section more actionable with regard to what JS/TS does specifically.

Modern & Idiomatic JavaScript

DO use built-in promises for asynchronous operations. You may provide overloads that take a callback. Don’t import a polyfill or library to implement promises.

Promises were added to JavaScript ES2015. ES2016 and later added async functions to make working with promises easier. Promises are broadly supported in JavaScript runtimes, including all currently supported versions of Node.

☑️ YOU SHOULD use async functions for implementing asynchronous library APIs.

If you need to support ES5 and are concerned with library size, use async when combining asynchronous code with control flow constructs. Use promises for simpler code flows. async adds code bloat (especially when targeting ES5) when transpiled.

DO use Iterators and Async Iterators for sequences and streams of all sorts.

Both iterators and async iterators are built into JavaScript and easy to consume. Other streaming interfaces (such as node streams) may be used where appropriate as long as they’re idiomatic.

☑️ YOU SHOULD prefer interface types to class types. JavaScript is fundamentally a duck-typed language, and so alternative classes that implement the same interface should be allowed. Declare parameters as interface types over class types whenever possible. Overloads with specific class types are fine but there should be an overload present with the generic interface.

// bad synchronous example
function listItems() {
  return {
    nextItem() { /*...*/ }
  }
}

// better synchronous example
function* listItems() {
  /* ... */
}

// bad asynchronous examples
function listItems() {
  return Rx.Observable.of(/* ... */)
}

function listItems(callback) {
  // fetch items
  for (const item of items) {
    callback (item)
    }
}

// better asynchronous example
async function* listItems() {
  for (const item of items) {
    yield item;
  }
}

~

Modern & Idiomatic TypeScript

DO implement your library in TypeScript.

DO include type declarations for your library.

TypeScript static types provide significant benefit for both the library authors and consumers. TypeScript also compiles modern JavaScript language features for use with older runtimes.

tsconfig.json

Your tsconfig.json should look similar to the following example:

{
  "compilerOptions": {
    "declaration": true,
    "module": "es6",
    "moduleResolution": "node",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "outDir": "./dist-esm",
    "target": "es6",
    "sourceMap": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "importHelpers": true
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules"]
}

DO have at least “node_modules” in the exclude array. TypeScript shouldn’t needlessly type check your dependencies.

⛔️ DO NOT use the compilerOptions.lib field. Built in typescript libraries (for example, esnext.asynciterable) should be included via reference directives. See also Microsoft/TypeScript#27416.

DO set compilerOptions.strict to true. The strict flag is a best practice for developers as it provides the best TypeScript experience. The strict flag also ensures that your type definitions are maximally pedantic.

DO set compilerOptions.esModuleInterop to true.

DO set compilerOptions.allowSyntheticDefaultImports to true.

DO set compilerOptions.target, but it can be any valid value so long as the final source distributions are compatible with the runtimes your library targets. See also [#ts-source-distros].

DO set compilerOptions.forceConsistentCasingInFileNames to true. forceConsistentCasingInFileNames forces TypeScript to treat files as case sensitive, and ensures you don’t get surprised by build failures when moving between platforms.

DO set compilerOptions.module to es6. Use a bundler such as Rollup or Webpack to produce the CommonJS and UMD builds.

DO set compilerOptions.moduleResolution to “node” if your library targets Node. Otherwise, it should be absent.

DO set compilerOptions.declaration to true. The --declaration option tells TypeScript to emit a d.ts file that contains the public surface area of your library. TypeScript and editors use this file to provide intellisense and type checking capabilities. Ensure you reference this type declaration file from the types field of your package.json.

⛔️ DO NOT set compilerOptions.experimentalDecorators to true. The experimentalDecorators flag adds support for “v1 decorators” to TypeScript. Unfortunately the standards process has moved on to an incompatible second version that is not yet implemented by TypeScript. Taking a dependency on decorators now means signing up your users for breaking changes later.

DO set compilerOptions.sourceMap and compilerOptions.declarationMap to true. Shipping source maps in your package ensures clients can easily debug into your library code. sourceMap maps your emitted JS source to the declaration file and declarationMap maps the declaration file back to the TypeScript source that generated it. Be sure to include your original TypeScript sources in the package.

DO set compilerOptions.importHelpers to true. Using external helpers keeps your package size down. Without this flag, TypeScript will add a helper block to each file that needs it. The file size savings using this option can be huge when using async functions (as an example) in a number of different files.

TypeScript Coding Guidelines

⚠️ YOU SHOULD NOT use TypeScript namespaces. Namespaces either use the namespace keyword explicitly, or the module keyword with a module name (for example, module Microsoft.ApplicationInsights { ... }). Use top-level imports/exports with ECMAScript modules instead. Namespaces make your code less compatible with standard ECMAScript and create significant friction with the TypeScript community.

⚠️ YOU SHOULD NOT use const enum. Const enum requires global understanding of your program to compile properly. As a result, const enum can’t be used with Babel 7, which otherwise supports TypeScript. Avoiding const enum will make sure your code can be compiled by any tool. Use regular enums instead.

Pagination

Most developers will want to process a list one item at a time. Higher-level APIs (for example, async iterators) are preferred in the majority of use cases. Finer-grained control over handling paginated result sets is sometimes required (for example, to handle over-quota or throttling).

DO provide a list method that returns a PagedAsyncIterableIterator from the module @azure/core-paging.

DO provide page-related settings to the byPage() iterator and not the per-item iterator.

DO take a continuationToken option in the byPage() method. You must rename other parameters that perform a similar function (for example, nextMarker). If your page type has a continuation token, it must be named continuationToken.

DO take a maxPageSize option in the byPage() method.

An example of a paginating client:

// usage
const client = new ServiceClient()
for await (const item of client.listItems()) {
    console.log(item);
}

for await (const page of client.listItems().byPage({ maxPageSize: 50 })) {
    console.log(page);
}

// implementation
interface Item {
    name: string;
}

interface Page {
    continuationToken: string;
    items: Item[];
}

class ServiceClient {
    /* ... */
    listItems(): PagedAsyncIterableIterator<Item, Page> {
        async function* pages () { /* ... */ }
        async function* items () {
            for (const page of pages()) {
                for (const item of page.items) {
                    yield item;
                }
            }
        }

        const itemIter = items();

        return {
            next() {
                return itemIter.next();
                /* ... */
            },
            byPage() {
                return pages();
            },
            [Symbol.asyncIterator]() { return this }
        }
    }
}

DO expose non-paginated list endpoints identically to paginated list endpoints. Users shouldn’t need to appreciate the difference.

DO use different types for entities returned from a list endpoint and a get endpoint if the returned entities have a different shape. If both entities are the same form, use the same type.

⛔️ DO NOT expose an iterator over individual items if it causes additional service requests. Some services charge on a per-request basis. One GET per item is often too expensive when the data isn’t used.

⛔️ DO NOT expose an API to get a paginated collection into an array. Services may return many pages, which can lead to memory exhaustion in the application.

Long Running Operations

Long-running operations are operations which consist of an initial request to start the operation followed by polling to determine when the operation has completed or failed. Long-running operations in Azure tend to follow the REST API guidelines for Long-running Operations, but there are exceptions.

DO represent long-running operations with some object that encapsulates the polling and the operation status. This object, called a poller, must provide APIs for:

  1. querying the current operation state (either asynchronously, which may consult the service, or synchronously which must not)
  2. requesting an asynchronous notification when the operation has completed
  3. cancelling the operation if cancellation is supported by the service
  4. registering disinterest in the operation so polling stops
  5. triggering a poll operation manually (automatic polling must be disabled)
  6. progress reporting (if supported by the service)

DO support the following polling configuration options:

  • pollInterval

Polling configuration may be used only in the absence of relevant retry-after headers from service, and otherwise should be ignored.

DO prefix method names which return a poller with either begin.

DO provide a way to instantiate a poller with the serialized state of another poller to begin where it left off, for example by passing the state as a parameter to the same method which started the operation, or by directly instantiating a poller with that state.

⛔️ DO NOT cancel the long-running operation when cancellation is requested via a cancellation token. The cancellation token is cancelling the polling operation and should not have any effect on the service.

DO log polling status at the Info level (including time to next retry)

DO expose a progress reporting mechanism to the consumer if the service reports progress as part of the polling operation. Language-dependent guidelines will present additional guidance on how to expose progress reporting in this case.

TODO: If this is largely implemented for the API Designer, please include an example of how to use the Azure Core type in the public API. It would be ideal to remove guidelines where the requirement has already been addressed for the API Designer in the type.

Conditional Request Methods

There are two patterns in use depending on whether etag is a member of the model type or not.

DO provide the following options in a method’s options bag when the model type has an etag property:

  • onlyIfChanged - sets the if-match header to the etag.
  • onlyIfUnchanged - sets the if-none-match header to the etag.
  • onlyIfMissing - sets the if-none-match header to *.
  • onlyIfPresent - sets the if-match header to *.

DO provide the following options in a method’s options bag’s conditions property when the model type does not have an etag property:

  • ifMatch - sets the if-match header to the value provided.
  • ifNoneMatch - sets the if-none-match header to the value provided.
  • ifModifiedSince - sets the if-modified-since header to the value provided
  • ifUnmodifiedSince - sets the if-unmodified-since header to the value provided.

DO throw an error if the user provides options from both option sets, for example passing onlyIfChanged: true and ifMatch: "...". In some cases you may want to provide both sets of options, but it is not required or necessarily recommended.

Model Types

Client libraries represent entities transferred to and from Azure services as model types. Certain types are used for round-trips to the service. They can be sent to the service (as an addition or update operation) and retrieved from the service (as a get operation). These must be named according to the type. For example, a ConfigurationSetting in App Configuration, or an Event on Event Grid.

DO follow the above convention for types which round-trip to the service and represent a complete entity.

Data within the model type can generally be split into two parts - data used to support one of the champion scenarios for the service, and less important data. Given a type Foo, the less important details can be gathered in a type called FooDetails and attached to Foo as the details property.

For example:

interface ConfigurationSettingDetails {
    lastModifiedOn: Date;
    receivedOn: Date;
    etag: string;
}

interface ConfigurationSetting {
    key: string;
    value: string;
    details: ConfigurationSettingDetails;
}

✔️ YOU MAY use details to separate commonly needed and less commonly needed properties. If you use this convention, you MUST follow these naming conventions.

In cases where a partial schema is returned, use the following types:

  • <model>Item for each item in an enumeration if the enumeration returns a partial schema for the model. For example, GetBlobs() return an enumeration of BlobItem, which contains the blob name and metadata, but not the content of the blob.
  • <operation>Result for the result of an operation. The <operation> is tied to a specific service operation. If the same result can be used for multiple operations, use a suitable noun-verb phrase instead. For example, use UploadBlobResult for the result from UploadBlob, but ContainerChangeResult for results from the various methods that change a blob container. In cases where a result is just a primitive type, do not create a type alias for it - just use it directly, and do not follow these conventions.

DO follow the above naming conventions when partial schemas are returned.

The following table enumerates the various models you might create:

Type Example Usage
<model> Secret The full data for a resource
<model>Details SecretDetails Less important details about a resource. Attached to <model>.details
<model>Item SecretItem A partial set of data returned for enumeration
<operation>Options AddSecretOptions Optional parameters to a single operation
<operation>Result AddSecretResult A partial or different set of data for a single operation
<model><verb>Result SecretChangeResult A partial or different set of data for multiple operations on a model

Using Azure Core

DO make use of packages in Azure Core to provide behavior consistent across all Azure SDK libraries. This includes, but is not limited to:

  • core-http for http client, pipeline and related functionality
  • logger for logging
  • core-tracing for distributed tracing
  • core-auth for common auth interfaces
  • core-lro for long running operations

See the Azure Core readme for more details.

TODO: Please add a section on extensible enums, if this is relevant to JS/TS.

Support for non-HTTP protocols

Most Azure services expose a RESTful API over HTTPS. However, a few services use other protocols, such as AMQP, MQTT, or WebRTC. In these cases, the operation of the protocol can be split into two phases:

  • Per-connection (surrounding when the connection is initiated and terminated)
  • Per-operation (surrounding when an operation is sent through the open connection)

The policies that are added to a HTTP request/response (authentication, unique request ID, telemetry, distributed tracing, and logging) are still valid on both a per-connection and per-operation basis. However, the methods by which these policies are implemented are protocol dependent.

DO implement as many of the policies as possible on a per-connection and per-operation basis.

For example, MQTT over WebSockets provides the ability to add headers during the initiation of the WebSockets connection, so this is a good place to add authentication, telemetry, and distributed tracing policies. However, MQTT has no metadata (the equivalent of HTTP headers), so per-operation policies are not possible. AMQP, by contract, does have per-operation metadata. Unique request ID, and distributed tracing headers can be provided on a per-operation basis with AMQP.

DO consult the Architecture Board on policy decisions for non-HTTP protocols. Implementation of all policies is expected. If the protocol cannot support a policy, obtain an exception from the Architecture Board.

DO use the global configuration established in the Azure Core library to configure policies for non-HTTP protocols. Consumers don’t necessarily know what protocol is used by the client library. They will expect the client library to honor global configuration that they have established for the entire Azure SDK.