GitHub Copilot ChatGPT Claude Codex CLI Cursor opencode Skill Text

azure-cosmos-ts

Azure Cosmos DB JavaScript/TypeScript SDK (@azure/cosmos) for data plane operations. Use for CRUD operations on documents, queries, bulk operations, and container management. Triggers: "Cosmos DB", "@azure/cosmos", "CosmosClient", "document CRUD", "NoSQL queries", "bulk operation

Ciza · 0 points · 22 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download microsoft-skills-.github_plugins_azure-sdk-typescript_skills_azure-cosmos-ts-e58528d.zip · 9 KB
Part of microsoft/skills — 195 skills

Install

skills CLI npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-typescript/skills/azure-cosmos-ts
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
Git git clone https://github.com/microsoft/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

@azure/cosmos (TypeScript/JavaScript)

Data plane SDK for Azure Cosmos DB NoSQL API operations — CRUD on documents, queries, bulk operations.

⚠️ Data vs Management Plane

  • This SDK (@azure/cosmos): CRUD operations on documents, queries, stored procedures
  • Management SDK (@azure/arm-cosmosdb): Create accounts, databases, containers via ARM

Installation

npm install @azure/cosmos @azure/identity

Current Version: 4.9.0
Node.js: >= 20.0.0

Environment Variables

COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/
COSMOS_DATABASE=<database-name>
COSMOS_CONTAINER=<container-name>
# For key-based auth only (prefer AAD)
COSMOS_KEY=<account-key>
AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production

Authentication

Microsoft Entra Token Credential (Recommended)

import { CosmosClient } from "@azure/cosmos";
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";

// Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
// Or use a specific credential directly in production:
// See https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes
// const credential = new ManagedIdentityCredential();

const client = new CosmosClient({
  endpoint: process.env.COSMOS_ENDPOINT!,
  aadCredentials: credential,
});

Key-Based Authentication

import { CosmosClient } from "@azure/cosmos";

// Option 1: Endpoint + Key
const client = new CosmosClient({
  endpoint: process.env.COSMOS_ENDPOINT!,
  key: process.env.COSMOS_KEY!,
});

// Option 2: Connection String
const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING!);

Resource Hierarchy

CosmosClient
└── Database
    └── Container
        ├── Items (documents)
        ├── Scripts (stored procedures, triggers, UDFs)
        └── Conflicts

Core Operations

Database & Container Setup

const { database } = await client.databases.createIfNotExists({
  id: "my-database",
});

const { container } = await database.containers.createIfNotExists({
  id: "my-container",
  partitionKey: { paths: ["/partitionKey"] },
});

Create Document

interface Product {
  id: string;
  partitionKey: string;
  name: string;
  price: number;
}

const item: Product = {
  id: "product-1",
  partitionKey: "electronics",
  name: "Laptop",
  price: 999.99,
};

const { resource } = await container.items.create<Product>(item);

Read Document

const { resource } = await container
  .item("product-1", "electronics") // id, partitionKey
  .read<Product>();

if (resource) {
  console.log(resource.name);
}

Update Document (Replace)

const { resource: existing } = await container
  .item("product-1", "electronics")
  .read<Product>();

if (existing) {
  existing.price = 899.99;
  const { resource: updated } = await container
    .item("product-1", "electronics")
    .replace<Product>(existing);
}

Upsert Document

const item: Product = {
  id: "product-1",
  partitionKey: "electronics",
  name: "Laptop Pro",
  price: 1299.99,
};

const { resource } = await container.items.upsert<Product>(item);

Delete Document

await container.item("product-1", "electronics").delete();

Patch Document (Partial Update)

import { PatchOperation } from "@azure/cosmos";

const operations: PatchOperation[] = [
  { op: "replace", path: "/price", value: 799.99 },
  { op: "add", path: "/discount", value: true },
  { op: "remove", path: "/oldField" },
];

const { resource } = await container
  .item("product-1", "electronics")
  .patch<Product>(operations);

Queries

Simple Query

const { resources } = await container.items
  .query<Product>("SELECT * FROM c WHERE c.price < 1000")
  .fetchAll();

Parameterized Query (Recommended)

import { SqlQuerySpec } from "@azure/cosmos";

const querySpec: SqlQuerySpec = {
  query: "SELECT * FROM c WHERE c.partitionKey = @category AND c.price < @maxPrice",
  parameters: [
    { name: "@category", value: "electronics" },
    { name: "@maxPrice", value: 1000 },
  ],
};

const { resources } = await container.items
  .query<Product>(querySpec)
  .fetchAll();

Query with Pagination

const queryIterator = container.items.query<Product>(querySpec, {
  maxItemCount: 10, // Items per page
});

while (queryIterator.hasMoreResults()) {
  const { resources, continuationToken } = await queryIterator.fetchNext();
  console.log(`Page with ${resources?.length} items`);
  // Use continuationToken for next page if needed
}

Cross-Partition Query

const { resources } = await container.items
  .query<Product>(
    "SELECT * FROM c WHERE c.price > 500",
    { enableCrossPartitionQuery: true }
  )
  .fetchAll();

Bulk Operations

Execute Bulk Operations

import { BulkOperationType, OperationInput } from "@azure/cosmos";

const operations: OperationInput[] = [
  {
    operationType: BulkOperationType.Create,
    resourceBody: { id: "1", partitionKey: "cat-a", name: "Item 1" },
  },
  {
    operationType: BulkOperationType.Upsert,
    resourceBody: { id: "2", partitionKey: "cat-a", name: "Item 2" },
  },
  {
    operationType: BulkOperationType.Read,
    id: "3",
    partitionKey: "cat-b",
  },
  {
    operationType: BulkOperationType.Replace,
    id: "4",
    partitionKey: "cat-b",
    resourceBody: { id: "4", partitionKey: "cat-b", name: "Updated" },
  },
  {
    operationType: BulkOperationType.Delete,
    id: "5",
    partitionKey: "cat-c",
  },
  {
    operationType: BulkOperationType.Patch,
    id: "6",
    partitionKey: "cat-c",
    resourceBody: {
      operations: [{ op: "replace", path: "/name", value: "Patched" }],
    },
  },
];

const response = await container.items.executeBulkOperations(operations);

response.forEach((result, index) => {
  if (result.statusCode >= 200 && result.statusCode < 300) {
    console.log(`Operation ${index} succeeded`);
  } else {
    console.error(`Operation ${index} failed: ${result.statusCode}`);
  }
});

Partition Keys

Simple Partition Key

const { container } = await database.containers.createIfNotExists({
  id: "products",
  partitionKey: { paths: ["/category"] },
});

Hierarchical Partition Key (MultiHash)

import { PartitionKeyDefinitionVersion, PartitionKeyKind } from "@azure/cosmos";

const { container } = await database.containers.createIfNotExists({
  id: "orders",
  partitionKey: {
    paths: ["/tenantId", "/userId", "/sessionId"],
    version: PartitionKeyDefinitionVersion.V2,
    kind: PartitionKeyKind.MultiHash,
  },
});

// Operations require array of partition key values
const { resource } = await container.items.create({
  id: "order-1",
  tenantId: "tenant-a",
  userId: "user-123",
  sessionId: "session-xyz",
  total: 99.99,
});

// Read with hierarchical partition key
const { resource: order } = await container
  .item("order-1", ["tenant-a", "user-123", "session-xyz"])
  .read();

Error Handling

import { ErrorResponse } from "@azure/cosmos";

try {
  const { resource } = await container.item("missing", "pk").read();
} catch (error) {
  if (error instanceof ErrorResponse) {
    switch (error.code) {
      case 404:
        console.log("Document not found");
        break;
      case 409:
        console.log("Conflict - document already exists");
        break;
      case 412:
        console.log("Precondition failed (ETag mismatch)");
        break;
      case 429:
        console.log("Rate limited - retry after:", error.retryAfterInMs);
        break;
      default:
        console.error(`Cosmos error ${error.code}: ${error.message}`);
    }
  }
  throw error;
}

Optimistic Concurrency (ETags)

// Read with ETag
const { resource, etag } = await container
  .item("product-1", "electronics")
  .read<Product>();

if (resource && etag) {
  resource.price = 899.99;
  
  try {
    // Replace only if ETag matches
    await container.item("product-1", "electronics").replace(resource, {
      accessCondition: { type: "IfMatch", condition: etag },
    });
  } catch (error) {
    if (error instanceof ErrorResponse && error.code === 412) {
      console.log("Document was modified by another process");
    }
  }
}

TypeScript Types Reference

import {
  // Client & Resources
  CosmosClient,
  Database,
  Container,
  Item,
  Items,
  
  // Operations
  OperationInput,
  BulkOperationType,
  PatchOperation,
  
  // Queries
  SqlQuerySpec,
  SqlParameter,
  FeedOptions,
  
  // Partition Keys
  PartitionKeyDefinition,
  PartitionKeyDefinitionVersion,
  PartitionKeyKind,
  
  // Responses
  ItemResponse,
  FeedResponse,
  ResourceResponse,
  
  // Errors
  ErrorResponse,
} from "@azure/cosmos";

Best Practices

  1. Use Microsoft Entra Token Credential — Use DefaultAzureCredential for local development; use ManagedIdentityCredential or WorkloadIdentityCredential for production
  2. Always use parameterized queries — Prevents injection, improves plan caching
  3. Specify partition key — Avoid cross-partition queries when possible
  4. Use bulk operations — For multiple writes, use executeBulkOperations
  5. Handle 429 errors — Implement retry logic with exponential backoff
  6. Use ETags for concurrency — Prevent lost updates in concurrent scenarios
  7. Close client on shutdown — Call client.dispose() in cleanup

Common Patterns

Service Layer Pattern

export class ProductService {
  private container: Container;

  constructor(client: CosmosClient) {
    this.container = client
      .database(process.env.COSMOS_DATABASE!)
      .container(process.env.COSMOS_CONTAINER!);
  }

  async getById(id: string, category: string): Promise<Product | null> {
    try {
      const { resource } = await this.container
        .item(id, category)
        .read<Product>();
      return resource ?? null;
    } catch (error) {
      if (error instanceof ErrorResponse && error.code === 404) {
        return null;
      }
      throw error;
    }
  }

  async create(product: Omit<Product, "id">): Promise<Product> {
    const item = { ...product, id: crypto.randomUUID() };
    const { resource } = await this.container.items.create<Product>(item);
    return resource!;
  }

  async findByCategory(category: string): Promise<Product[]> {
    const querySpec: SqlQuerySpec = {
      query: "SELECT * FROM c WHERE c.partitionKey = @category",
      parameters: [{ name: "@category", value: category }],
    };
    const { resources } = await this.container.items
      .query<Product>(querySpec)
      .fetchAll();
    return resources;
  }
}

Related SDKs

SDK Purpose Install
@azure/cosmos Data plane (this SDK) npm install @azure/cosmos
@azure/arm-cosmosdb Management plane (ARM) npm install @azure/arm-cosmosdb
@azure/identity Authentication npm install @azure/identity
Files (skills)
  • references
    • bulk-operations.md 11.2 KB
      # Bulk Operations Reference
      
      High-throughput bulk operations for Azure Cosmos DB using the @azure/cosmos TypeScript SDK.
      
      ## Overview
      
      Bulk operations enable efficient batch processing of Create, Upsert, Read, Replace, Delete, and Patch operations with automatic batching and retry handling.
      
      ## BulkOperationType Enum
      
      ```typescript
      import { BulkOperationType } from "@azure/cosmos";
      
      enum BulkOperationType {
        Create = "Create",
        Upsert = "Upsert",
        Read = "Read",
        Replace = "Replace",
        Delete = "Delete",
        Patch = "Patch"
      }
      ```
      
      ## OperationInput Interface
      
      ```typescript
      import { OperationInput, PatchOperation } from "@azure/cosmos";
      
      // Base operation types
      interface CreateOperationInput {
        operationType: BulkOperationType.Create;
        resourceBody: Record<string, unknown>;
        partitionKey?: PartitionKey;
      }
      
      interface UpsertOperationInput {
        operationType: BulkOperationType.Upsert;
        resourceBody: Record<string, unknown>;
        partitionKey?: PartitionKey;
      }
      
      interface ReadOperationInput {
        operationType: BulkOperationType.Read;
        id: string;
        partitionKey: PartitionKey;
      }
      
      interface ReplaceOperationInput {
        operationType: BulkOperationType.Replace;
        id: string;
        resourceBody: Record<string, unknown>;
        partitionKey?: PartitionKey;
      }
      
      interface DeleteOperationInput {
        operationType: BulkOperationType.Delete;
        id: string;
        partitionKey: PartitionKey;
      }
      
      interface PatchOperationInput {
        operationType: BulkOperationType.Patch;
        id: string;
        partitionKey: PartitionKey;
        resourceBody: {
          operations: PatchOperation[];
          condition?: string;
        };
      }
      
      type OperationInput = 
        | CreateOperationInput
        | UpsertOperationInput
        | ReadOperationInput
        | ReplaceOperationInput
        | DeleteOperationInput
        | PatchOperationInput;
      ```
      
      ## Basic Bulk Operations
      
      ```typescript
      import { 
        CosmosClient, 
        BulkOperationType, 
        OperationInput,
        BulkOperationResponse 
      } from "@azure/cosmos";
      
      const client = new CosmosClient({ endpoint, key });
      const container = client.database("mydb").container("mycontainer");
      
      // Mixed bulk operations
      const operations: OperationInput[] = [
        // Create
        {
          operationType: BulkOperationType.Create,
          resourceBody: { 
            id: "item-1", 
            partitionKey: "category-a", 
            name: "Item 1",
            price: 10.99
          }
        },
        // Upsert
        {
          operationType: BulkOperationType.Upsert,
          resourceBody: { 
            id: "item-2", 
            partitionKey: "category-a", 
            name: "Item 2",
            price: 20.99
          }
        },
        // Read
        {
          operationType: BulkOperationType.Read,
          id: "item-3",
          partitionKey: "category-b"
        },
        // Replace
        {
          operationType: BulkOperationType.Replace,
          id: "item-4",
          partitionKey: "category-b",
          resourceBody: { 
            id: "item-4", 
            partitionKey: "category-b", 
            name: "Updated Item 4",
            price: 15.99
          }
        },
        // Delete
        {
          operationType: BulkOperationType.Delete,
          id: "item-5",
          partitionKey: "category-c"
        }
      ];
      
      const response: BulkOperationResponse = await container.items.bulk(operations);
      
      // Process results
      response.forEach((result, index) => {
        if (result.statusCode >= 200 && result.statusCode < 300) {
          console.log(`Operation ${index} succeeded: ${result.statusCode}`);
          if (result.resourceBody) {
            console.log(`  Resource: ${JSON.stringify(result.resourceBody)}`);
          }
        } else {
          console.error(`Operation ${index} failed: ${result.statusCode}`);
        }
      });
      ```
      
      ## Bulk Create Pattern
      
      ```typescript
      interface Product {
        id: string;
        partitionKey: string;
        name: string;
        price: number;
      }
      
      async function bulkCreate(
        container: Container,
        items: Product[]
      ): Promise<{ succeeded: number; failed: number }> {
        const operations: OperationInput[] = items.map(item => ({
          operationType: BulkOperationType.Create,
          resourceBody: item
        }));
      
        const response = await container.items.bulk(operations);
      
        let succeeded = 0;
        let failed = 0;
      
        response.forEach((result, index) => {
          if (result.statusCode === 201) {
            succeeded++;
          } else {
            failed++;
            console.error(`Failed to create item ${items[index].id}: ${result.statusCode}`);
          }
        });
      
        return { succeeded, failed };
      }
      
      // Usage
      const products: Product[] = [
        { id: "p1", partitionKey: "electronics", name: "Laptop", price: 999 },
        { id: "p2", partitionKey: "electronics", name: "Phone", price: 599 },
        { id: "p3", partitionKey: "clothing", name: "Shirt", price: 29 },
      ];
      
      const result = await bulkCreate(container, products);
      console.log(`Created: ${result.succeeded}, Failed: ${result.failed}`);
      ```
      
      ## Bulk Upsert Pattern
      
      ```typescript
      async function bulkUpsert<T extends { id: string }>(
        container: Container,
        items: T[]
      ): Promise<BulkOperationResponse> {
        const operations: OperationInput[] = items.map(item => ({
          operationType: BulkOperationType.Upsert,
          resourceBody: item as Record<string, unknown>
        }));
      
        return container.items.bulk(operations);
      }
      
      // Usage - creates if not exists, updates if exists
      const updates = [
        { id: "p1", partitionKey: "electronics", name: "Laptop Pro", price: 1299 },
        { id: "p4", partitionKey: "electronics", name: "Tablet", price: 399 },
      ];
      
      await bulkUpsert(container, updates);
      ```
      
      ## Bulk Delete Pattern
      
      ```typescript
      interface DeleteItem {
        id: string;
        partitionKey: string;
      }
      
      async function bulkDelete(
        container: Container,
        items: DeleteItem[]
      ): Promise<{ deleted: number; notFound: number; errors: number }> {
        const operations: OperationInput[] = items.map(item => ({
          operationType: BulkOperationType.Delete,
          id: item.id,
          partitionKey: item.partitionKey
        }));
      
        const response = await container.items.bulk(operations);
      
        let deleted = 0;
        let notFound = 0;
        let errors = 0;
      
        response.forEach((result) => {
          if (result.statusCode === 204) {
            deleted++;
          } else if (result.statusCode === 404) {
            notFound++;
          } else {
            errors++;
          }
        });
      
        return { deleted, notFound, errors };
      }
      
      // Usage
      const toDelete: DeleteItem[] = [
        { id: "p1", partitionKey: "electronics" },
        { id: "p2", partitionKey: "electronics" },
      ];
      
      const deleteResult = await bulkDelete(container, toDelete);
      console.log(`Deleted: ${deleteResult.deleted}, Not found: ${deleteResult.notFound}`);
      ```
      
      ## Bulk Patch Pattern
      
      ```typescript
      import { PatchOperation } from "@azure/cosmos";
      
      interface PatchItem {
        id: string;
        partitionKey: string;
        operations: PatchOperation[];
      }
      
      async function bulkPatch(
        container: Container,
        items: PatchItem[]
      ): Promise<BulkOperationResponse> {
        const operations: OperationInput[] = items.map(item => ({
          operationType: BulkOperationType.Patch,
          id: item.id,
          partitionKey: item.partitionKey,
          resourceBody: {
            operations: item.operations
          }
        }));
      
        return container.items.bulk(operations);
      }
      
      // Usage - partial updates
      const patches: PatchItem[] = [
        {
          id: "p1",
          partitionKey: "electronics",
          operations: [
            { op: "replace", path: "/price", value: 899 },
            { op: "add", path: "/onSale", value: true }
          ]
        },
        {
          id: "p2",
          partitionKey: "electronics",
          operations: [
            { op: "incr", path: "/viewCount", value: 1 }
          ]
        }
      ];
      
      await bulkPatch(container, patches);
      ```
      
      ## Chunked Bulk Operations
      
      For very large datasets, process in chunks to manage memory and handle rate limiting:
      
      ```typescript
      async function bulkOperationsChunked<T extends Record<string, unknown>>(
        container: Container,
        items: T[],
        operationType: BulkOperationType.Create | BulkOperationType.Upsert,
        chunkSize: number = 100
      ): Promise<{ succeeded: number; failed: number }> {
        let succeeded = 0;
        let failed = 0;
      
        // Process in chunks
        for (let i = 0; i < items.length; i += chunkSize) {
          const chunk = items.slice(i, i + chunkSize);
          
          const operations: OperationInput[] = chunk.map(item => ({
            operationType,
            resourceBody: item
          }));
      
          const response = await container.items.bulk(operations);
      
          response.forEach(result => {
            if (result.statusCode >= 200 && result.statusCode < 300) {
              succeeded++;
            } else {
              failed++;
            }
          });
      
          console.log(`Processed ${Math.min(i + chunkSize, items.length)}/${items.length}`);
        }
      
        return { succeeded, failed };
      }
      
      // Usage
      const largeDataset = generateItems(10000);
      const result = await bulkOperationsChunked(container, largeDataset, BulkOperationType.Create, 100);
      ```
      
      ## Bulk Operation Response
      
      ```typescript
      interface BulkOperationResponse extends Array<OperationResponse> {}
      
      interface OperationResponse {
        /** HTTP status code */
        statusCode: number;
        
        /** Request charge (RUs) for this operation */
        requestCharge: number;
        
        /** ETag of the resource (for successful operations) */
        eTag?: string;
        
        /** Resource body (for read/create/upsert/replace) */
        resourceBody?: Record<string, unknown>;
      }
      
      // Common status codes
      // 200 - Read successful
      // 201 - Create successful
      // 204 - Delete successful
      // 400 - Bad request
      // 404 - Not found
      // 409 - Conflict (duplicate id on create)
      // 412 - Precondition failed
      // 429 - Rate limited
      ```
      
      ## Error Handling
      
      ```typescript
      async function bulkWithRetry(
        container: Container,
        operations: OperationInput[],
        maxRetries: number = 3
      ): Promise<BulkOperationResponse> {
        let response = await container.items.bulk(operations);
        let retryCount = 0;
      
        while (retryCount < maxRetries) {
          const failedOps: OperationInput[] = [];
          const failedIndices: number[] = [];
      
          response.forEach((result, index) => {
            if (result.statusCode === 429) {
              // Rate limited - retry these
              failedOps.push(operations[index]);
              failedIndices.push(index);
            }
          });
      
          if (failedOps.length === 0) {
            break; // All succeeded
          }
      
          console.log(`Retrying ${failedOps.length} rate-limited operations...`);
          
          // Exponential backoff
          await new Promise(resolve => 
            setTimeout(resolve, Math.pow(2, retryCount) * 1000)
          );
      
          const retryResponse = await container.items.bulk(failedOps);
          
          // Update original response with retry results
          retryResponse.forEach((result, i) => {
            response[failedIndices[i]] = result;
          });
      
          retryCount++;
        }
      
        return response;
      }
      ```
      
      ## Best Practices
      
      1. **Batch by partition key** — Operations in the same partition are more efficient
      2. **Use appropriate chunk sizes** — 100-500 items per batch is typical
      3. **Handle 429 errors** — Implement retry with exponential backoff
      4. **Monitor RU consumption** — Check `requestCharge` in responses
      5. **Use Upsert for idempotency** — Safer than Create for retry scenarios
      6. **Validate before bulk** — Check data before submitting large batches
      7. **Process results** — Always check individual operation status codes
      
      ## Performance Considerations
      
      | Factor | Recommendation |
      |--------|----------------|
      | Chunk size | 100-500 items per batch |
      | Partition distribution | Spread across partitions for parallelism |
      | Operation type | Upsert is safer than Create for retries |
      | Error handling | Implement retry logic for 429s |
      | Memory | Stream large datasets, don't load all into memory |
      
      ## See Also
      
      - [Query Patterns Reference](./query-patterns.md)
      - [Cosmos DB Bulk Execution](https://learn.microsoft.com/azure/cosmos-db/nosql/tutorial-dotnet-bulk-import)
      - [Request Units (RUs)](https://learn.microsoft.com/azure/cosmos-db/request-units)
      
    • query-patterns.md 8.7 KB
      # Query Patterns Reference
      
      Advanced query patterns for Azure Cosmos DB using the @azure/cosmos TypeScript SDK.
      
      ## Overview
      
      Cosmos DB supports SQL-like queries with support for JSON documents. This reference covers parameterized queries, pagination, cross-partition queries, and advanced query patterns.
      
      ## SqlQuerySpec Interface
      
      ```typescript
      import { SqlQuerySpec, SqlParameter } from "@azure/cosmos";
      
      interface SqlQuerySpec {
        /** SQL query text */
        query: string;
        /** Array of parameters */
        parameters?: SqlParameter[];
      }
      
      interface SqlParameter {
        /** Parameter name (including @) */
        name: string;
        /** Parameter value */
        value: unknown;
      }
      ```
      
      ## Parameterized Queries (Recommended)
      
      Always use parameterized queries to prevent injection and improve plan caching.
      
      ```typescript
      import { SqlQuerySpec, Container } from "@azure/cosmos";
      
      interface Product {
        id: string;
        category: string;
        name: string;
        price: number;
        inStock: boolean;
      }
      
      // Single parameter
      const querySpec: SqlQuerySpec = {
        query: "SELECT * FROM c WHERE c.category = @category",
        parameters: [
          { name: "@category", value: "electronics" }
        ]
      };
      
      const { resources } = await container.items
        .query<Product>(querySpec)
        .fetchAll();
      
      // Multiple parameters
      const rangeQuery: SqlQuerySpec = {
        query: `
          SELECT * FROM c 
          WHERE c.category = @category 
            AND c.price >= @minPrice 
            AND c.price <= @maxPrice
            AND c.inStock = @inStock
        `,
        parameters: [
          { name: "@category", value: "electronics" },
          { name: "@minPrice", value: 100 },
          { name: "@maxPrice", value: 1000 },
          { name: "@inStock", value: true }
        ]
      };
      
      const { resources: filtered } = await container.items
        .query<Product>(rangeQuery)
        .fetchAll();
      ```
      
      ## Pagination with Continuation Tokens
      
      ```typescript
      import { FeedOptions } from "@azure/cosmos";
      
      interface PagedResult<T> {
        items: T[];
        continuationToken?: string;
        hasMore: boolean;
      }
      
      async function queryWithPagination<T>(
        container: Container,
        querySpec: SqlQuerySpec,
        pageSize: number,
        continuationToken?: string
      ): Promise<PagedResult<T>> {
        const options: FeedOptions = {
          maxItemCount: pageSize,
          continuationToken
        };
      
        const queryIterator = container.items.query<T>(querySpec, options);
        const { resources, continuationToken: nextToken } = await queryIterator.fetchNext();
      
        return {
          items: resources || [],
          continuationToken: nextToken,
          hasMore: !!nextToken
        };
      }
      
      // Usage
      let page = await queryWithPagination<Product>(
        container,
        { query: "SELECT * FROM c ORDER BY c.createdAt DESC" },
        10
      );
      
      console.log(`Page 1: ${page.items.length} items`);
      
      while (page.hasMore) {
        page = await queryWithPagination<Product>(
          container,
          { query: "SELECT * FROM c ORDER BY c.createdAt DESC" },
          10,
          page.continuationToken
        );
        console.log(`Next page: ${page.items.length} items`);
      }
      ```
      
      ## Async Iterator Pattern
      
      ```typescript
      async function* queryAll<T>(
        container: Container,
        querySpec: SqlQuerySpec
      ): AsyncGenerator<T> {
        const queryIterator = container.items.query<T>(querySpec);
        
        while (queryIterator.hasMoreResults()) {
          const { resources } = await queryIterator.fetchNext();
          if (resources) {
            for (const item of resources) {
              yield item;
            }
          }
        }
      }
      
      // Usage
      for await (const product of queryAll<Product>(container, querySpec)) {
        console.log(product.name);
      }
      ```
      
      ## Cross-Partition Queries
      
      ```typescript
      // Enable cross-partition query when partition key is not specified
      const crossPartitionQuery: SqlQuerySpec = {
        query: "SELECT * FROM c WHERE c.price > @minPrice",
        parameters: [{ name: "@minPrice", value: 500 }]
      };
      
      const { resources } = await container.items
        .query<Product>(crossPartitionQuery, {
          enableCrossPartitionQuery: true
        })
        .fetchAll();
      
      // Aggregate across partitions
      const aggregateQuery: SqlQuerySpec = {
        query: "SELECT VALUE COUNT(1) FROM c WHERE c.category = @category",
        parameters: [{ name: "@category", value: "electronics" }]
      };
      
      const { resources: countResult } = await container.items
        .query<number>(aggregateQuery, { enableCrossPartitionQuery: true })
        .fetchAll();
      
      console.log(`Total count: ${countResult[0]}`);
      ```
      
      ## Projection Queries
      
      ```typescript
      // Select specific fields
      const projectionQuery: SqlQuerySpec = {
        query: `
          SELECT c.id, c.name, c.price, c.category
          FROM c
          WHERE c.inStock = true
        `
      };
      
      interface ProductSummary {
        id: string;
        name: string;
        price: number;
        category: string;
      }
      
      const { resources } = await container.items
        .query<ProductSummary>(projectionQuery)
        .fetchAll();
      
      // Computed properties
      const computedQuery: SqlQuerySpec = {
        query: `
          SELECT 
            c.id,
            c.name,
            c.price,
            c.price * 0.9 AS discountedPrice,
            CONCAT(c.category, "-", c.id) AS sku
          FROM c
        `
      };
      ```
      
      ## Array Queries (JOIN)
      
      ```typescript
      interface Order {
        id: string;
        customerId: string;
        items: OrderItem[];
      }
      
      interface OrderItem {
        productId: string;
        quantity: number;
        price: number;
      }
      
      // Query items within arrays
      const arrayQuery: SqlQuerySpec = {
        query: `
          SELECT 
            o.id AS orderId,
            o.customerId,
            i.productId,
            i.quantity,
            i.price
          FROM orders o
          JOIN i IN o.items
          WHERE i.quantity > @minQuantity
        `,
        parameters: [{ name: "@minQuantity", value: 5 }]
      };
      
      // Check if array contains value
      const containsQuery: SqlQuerySpec = {
        query: `
          SELECT * FROM c 
          WHERE ARRAY_CONTAINS(c.tags, @tag)
        `,
        parameters: [{ name: "@tag", value: "featured" }]
      };
      ```
      
      ## Aggregate Functions
      
      ```typescript
      // COUNT
      const countQuery = { query: "SELECT VALUE COUNT(1) FROM c" };
      
      // SUM, AVG, MIN, MAX
      const statsQuery: SqlQuerySpec = {
        query: `
          SELECT 
            COUNT(1) AS totalProducts,
            SUM(c.price) AS totalValue,
            AVG(c.price) AS averagePrice,
            MIN(c.price) AS minPrice,
            MAX(c.price) AS maxPrice
          FROM c
          WHERE c.category = @category
        `,
        parameters: [{ name: "@category", value: "electronics" }]
      };
      
      interface ProductStats {
        totalProducts: number;
        totalValue: number;
        averagePrice: number;
        minPrice: number;
        maxPrice: number;
      }
      
      const { resources } = await container.items
        .query<ProductStats>(statsQuery, { enableCrossPartitionQuery: true })
        .fetchAll();
      ```
      
      ## ORDER BY and TOP
      
      ```typescript
      // Order by with TOP
      const topQuery: SqlQuerySpec = {
        query: `
          SELECT TOP 10 *
          FROM c
          WHERE c.category = @category
          ORDER BY c.price DESC
        `,
        parameters: [{ name: "@category", value: "electronics" }]
      };
      
      // Multiple ORDER BY
      const multiOrderQuery: SqlQuerySpec = {
        query: `
          SELECT * FROM c
          ORDER BY c.category ASC, c.price DESC
        `
      };
      
      // OFFSET and LIMIT (alternative to continuation tokens)
      const offsetQuery: SqlQuerySpec = {
        query: `
          SELECT * FROM c
          ORDER BY c.createdAt DESC
          OFFSET @offset LIMIT @limit
        `,
        parameters: [
          { name: "@offset", value: 20 },
          { name: "@limit", value: 10 }
        ]
      };
      ```
      
      ## String Functions
      
      ```typescript
      const stringQuery: SqlQuerySpec = {
        query: `
          SELECT * FROM c
          WHERE CONTAINS(c.name, @searchTerm, true)
            OR STARTSWITH(c.name, @prefix)
        `,
        parameters: [
          { name: "@searchTerm", value: "phone" },
          { name: "@prefix", value: "Smart" }
        ]
      };
      
      // Case-insensitive search with LOWER
      const caseInsensitiveQuery: SqlQuerySpec = {
        query: `
          SELECT * FROM c
          WHERE LOWER(c.name) LIKE @pattern
        `,
        parameters: [{ name: "@pattern", value: "%laptop%" }]
      };
      ```
      
      ## FeedOptions Reference
      
      ```typescript
      interface FeedOptions {
        /** Max items per page */
        maxItemCount?: number;
        
        /** Continuation token from previous page */
        continuationToken?: string;
        
        /** Enable cross-partition queries */
        enableCrossPartitionQuery?: boolean;
        
        /** Max parallelism for cross-partition queries */
        maxDegreeOfParallelism?: number;
        
        /** Partition key for scoped queries */
        partitionKey?: PartitionKey;
        
        /** Enable scan in queries (avoid if possible) */
        enableScanInQuery?: boolean;
        
        /** Populate index metrics in response */
        populateIndexMetrics?: boolean;
      }
      ```
      
      ## Query Performance Tips
      
      1. **Always specify partition key** — Avoids expensive cross-partition queries
      2. **Use parameterized queries** — Enables query plan caching
      3. **Project only needed fields** — Reduces response size and RU consumption
      4. **Avoid cross-partition aggregates** — Very expensive; consider materialized views
      5. **Use continuation tokens** — More efficient than OFFSET/LIMIT
      6. **Check index metrics** — Use `populateIndexMetrics: true` to diagnose slow queries
      
      ## See Also
      
      - [Bulk Operations Reference](./bulk-operations.md)
      - [Azure Cosmos DB SQL Reference](https://learn.microsoft.com/azure/cosmos-db/nosql/query/)
      - [Query Performance Tips](https://learn.microsoft.com/azure/cosmos-db/nosql/query-metrics)
      
  • SKILL.md 11.6 KB
    ---
    name: azure-cosmos-ts
    description: |
      Azure Cosmos DB JavaScript/TypeScript SDK (@azure/cosmos) for data plane operations. Use for CRUD operations on documents, queries, bulk operations, and container management. Triggers: "Cosmos DB", "@azure/cosmos", "CosmosClient", "document CRUD", "NoSQL queries", "bulk operations", "partition key", "container.items".
    license: MIT
    metadata:
      author: Microsoft
      version: "1.0.0"
      package: '@azure/cosmos'
    ---
    
    # @azure/cosmos (TypeScript/JavaScript)
    
    Data plane SDK for Azure Cosmos DB NoSQL API operations — CRUD on documents, queries, bulk operations.
    
    > **⚠️ Data vs Management Plane**
    > - **This SDK (@azure/cosmos)**: CRUD operations on documents, queries, stored procedures
    > - **Management SDK (@azure/arm-cosmosdb)**: Create accounts, databases, containers via ARM
    
    ## Installation
    
    ```bash
    npm install @azure/cosmos @azure/identity
    ```
    
    **Current Version**: 4.9.0  
    **Node.js**: >= 20.0.0
    
    ## Environment Variables
    
    ```bash
    COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/
    COSMOS_DATABASE=<database-name>
    COSMOS_CONTAINER=<container-name>
    # For key-based auth only (prefer AAD)
    COSMOS_KEY=<account-key>
    AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production
    ```
    
    ## Authentication
    
    ### Microsoft Entra Token Credential (Recommended)
    
    ```typescript
    import { CosmosClient } from "@azure/cosmos";
    import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
    
    // Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
    const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
    // Or use a specific credential directly in production:
    // See https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes
    // const credential = new ManagedIdentityCredential();
    
    const client = new CosmosClient({
      endpoint: process.env.COSMOS_ENDPOINT!,
      aadCredentials: credential,
    });
    ```
    
    ### Key-Based Authentication
    
    ```typescript
    import { CosmosClient } from "@azure/cosmos";
    
    // Option 1: Endpoint + Key
    const client = new CosmosClient({
      endpoint: process.env.COSMOS_ENDPOINT!,
      key: process.env.COSMOS_KEY!,
    });
    
    // Option 2: Connection String
    const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING!);
    ```
    
    ## Resource Hierarchy
    
    ```
    CosmosClient
    └── Database
        └── Container
            ├── Items (documents)
            ├── Scripts (stored procedures, triggers, UDFs)
            └── Conflicts
    ```
    
    ## Core Operations
    
    ### Database & Container Setup
    
    ```typescript
    const { database } = await client.databases.createIfNotExists({
      id: "my-database",
    });
    
    const { container } = await database.containers.createIfNotExists({
      id: "my-container",
      partitionKey: { paths: ["/partitionKey"] },
    });
    ```
    
    ### Create Document
    
    ```typescript
    interface Product {
      id: string;
      partitionKey: string;
      name: string;
      price: number;
    }
    
    const item: Product = {
      id: "product-1",
      partitionKey: "electronics",
      name: "Laptop",
      price: 999.99,
    };
    
    const { resource } = await container.items.create<Product>(item);
    ```
    
    ### Read Document
    
    ```typescript
    const { resource } = await container
      .item("product-1", "electronics") // id, partitionKey
      .read<Product>();
    
    if (resource) {
      console.log(resource.name);
    }
    ```
    
    ### Update Document (Replace)
    
    ```typescript
    const { resource: existing } = await container
      .item("product-1", "electronics")
      .read<Product>();
    
    if (existing) {
      existing.price = 899.99;
      const { resource: updated } = await container
        .item("product-1", "electronics")
        .replace<Product>(existing);
    }
    ```
    
    ### Upsert Document
    
    ```typescript
    const item: Product = {
      id: "product-1",
      partitionKey: "electronics",
      name: "Laptop Pro",
      price: 1299.99,
    };
    
    const { resource } = await container.items.upsert<Product>(item);
    ```
    
    ### Delete Document
    
    ```typescript
    await container.item("product-1", "electronics").delete();
    ```
    
    ### Patch Document (Partial Update)
    
    ```typescript
    import { PatchOperation } from "@azure/cosmos";
    
    const operations: PatchOperation[] = [
      { op: "replace", path: "/price", value: 799.99 },
      { op: "add", path: "/discount", value: true },
      { op: "remove", path: "/oldField" },
    ];
    
    const { resource } = await container
      .item("product-1", "electronics")
      .patch<Product>(operations);
    ```
    
    ## Queries
    
    ### Simple Query
    
    ```typescript
    const { resources } = await container.items
      .query<Product>("SELECT * FROM c WHERE c.price < 1000")
      .fetchAll();
    ```
    
    ### Parameterized Query (Recommended)
    
    ```typescript
    import { SqlQuerySpec } from "@azure/cosmos";
    
    const querySpec: SqlQuerySpec = {
      query: "SELECT * FROM c WHERE c.partitionKey = @category AND c.price < @maxPrice",
      parameters: [
        { name: "@category", value: "electronics" },
        { name: "@maxPrice", value: 1000 },
      ],
    };
    
    const { resources } = await container.items
      .query<Product>(querySpec)
      .fetchAll();
    ```
    
    ### Query with Pagination
    
    ```typescript
    const queryIterator = container.items.query<Product>(querySpec, {
      maxItemCount: 10, // Items per page
    });
    
    while (queryIterator.hasMoreResults()) {
      const { resources, continuationToken } = await queryIterator.fetchNext();
      console.log(`Page with ${resources?.length} items`);
      // Use continuationToken for next page if needed
    }
    ```
    
    ### Cross-Partition Query
    
    ```typescript
    const { resources } = await container.items
      .query<Product>(
        "SELECT * FROM c WHERE c.price > 500",
        { enableCrossPartitionQuery: true }
      )
      .fetchAll();
    ```
    
    ## Bulk Operations
    
    ### Execute Bulk Operations
    
    ```typescript
    import { BulkOperationType, OperationInput } from "@azure/cosmos";
    
    const operations: OperationInput[] = [
      {
        operationType: BulkOperationType.Create,
        resourceBody: { id: "1", partitionKey: "cat-a", name: "Item 1" },
      },
      {
        operationType: BulkOperationType.Upsert,
        resourceBody: { id: "2", partitionKey: "cat-a", name: "Item 2" },
      },
      {
        operationType: BulkOperationType.Read,
        id: "3",
        partitionKey: "cat-b",
      },
      {
        operationType: BulkOperationType.Replace,
        id: "4",
        partitionKey: "cat-b",
        resourceBody: { id: "4", partitionKey: "cat-b", name: "Updated" },
      },
      {
        operationType: BulkOperationType.Delete,
        id: "5",
        partitionKey: "cat-c",
      },
      {
        operationType: BulkOperationType.Patch,
        id: "6",
        partitionKey: "cat-c",
        resourceBody: {
          operations: [{ op: "replace", path: "/name", value: "Patched" }],
        },
      },
    ];
    
    const response = await container.items.executeBulkOperations(operations);
    
    response.forEach((result, index) => {
      if (result.statusCode >= 200 && result.statusCode < 300) {
        console.log(`Operation ${index} succeeded`);
      } else {
        console.error(`Operation ${index} failed: ${result.statusCode}`);
      }
    });
    ```
    
    ## Partition Keys
    
    ### Simple Partition Key
    
    ```typescript
    const { container } = await database.containers.createIfNotExists({
      id: "products",
      partitionKey: { paths: ["/category"] },
    });
    ```
    
    ### Hierarchical Partition Key (MultiHash)
    
    ```typescript
    import { PartitionKeyDefinitionVersion, PartitionKeyKind } from "@azure/cosmos";
    
    const { container } = await database.containers.createIfNotExists({
      id: "orders",
      partitionKey: {
        paths: ["/tenantId", "/userId", "/sessionId"],
        version: PartitionKeyDefinitionVersion.V2,
        kind: PartitionKeyKind.MultiHash,
      },
    });
    
    // Operations require array of partition key values
    const { resource } = await container.items.create({
      id: "order-1",
      tenantId: "tenant-a",
      userId: "user-123",
      sessionId: "session-xyz",
      total: 99.99,
    });
    
    // Read with hierarchical partition key
    const { resource: order } = await container
      .item("order-1", ["tenant-a", "user-123", "session-xyz"])
      .read();
    ```
    
    ## Error Handling
    
    ```typescript
    import { ErrorResponse } from "@azure/cosmos";
    
    try {
      const { resource } = await container.item("missing", "pk").read();
    } catch (error) {
      if (error instanceof ErrorResponse) {
        switch (error.code) {
          case 404:
            console.log("Document not found");
            break;
          case 409:
            console.log("Conflict - document already exists");
            break;
          case 412:
            console.log("Precondition failed (ETag mismatch)");
            break;
          case 429:
            console.log("Rate limited - retry after:", error.retryAfterInMs);
            break;
          default:
            console.error(`Cosmos error ${error.code}: ${error.message}`);
        }
      }
      throw error;
    }
    ```
    
    ## Optimistic Concurrency (ETags)
    
    ```typescript
    // Read with ETag
    const { resource, etag } = await container
      .item("product-1", "electronics")
      .read<Product>();
    
    if (resource && etag) {
      resource.price = 899.99;
      
      try {
        // Replace only if ETag matches
        await container.item("product-1", "electronics").replace(resource, {
          accessCondition: { type: "IfMatch", condition: etag },
        });
      } catch (error) {
        if (error instanceof ErrorResponse && error.code === 412) {
          console.log("Document was modified by another process");
        }
      }
    }
    ```
    
    ## TypeScript Types Reference
    
    ```typescript
    import {
      // Client & Resources
      CosmosClient,
      Database,
      Container,
      Item,
      Items,
      
      // Operations
      OperationInput,
      BulkOperationType,
      PatchOperation,
      
      // Queries
      SqlQuerySpec,
      SqlParameter,
      FeedOptions,
      
      // Partition Keys
      PartitionKeyDefinition,
      PartitionKeyDefinitionVersion,
      PartitionKeyKind,
      
      // Responses
      ItemResponse,
      FeedResponse,
      ResourceResponse,
      
      // Errors
      ErrorResponse,
    } from "@azure/cosmos";
    ```
    
    ## Best Practices
    
    1. **Use Microsoft Entra Token Credential** — Use `DefaultAzureCredential` for local development; use `ManagedIdentityCredential` or `WorkloadIdentityCredential` for production
    2. **Always use parameterized queries** — Prevents injection, improves plan caching
    3. **Specify partition key** — Avoid cross-partition queries when possible
    4. **Use bulk operations** — For multiple writes, use `executeBulkOperations`
    5. **Handle 429 errors** — Implement retry logic with exponential backoff
    6. **Use ETags for concurrency** — Prevent lost updates in concurrent scenarios
    7. **Close client on shutdown** — Call `client.dispose()` in cleanup
    
    ## Common Patterns
    
    ### Service Layer Pattern
    
    ```typescript
    export class ProductService {
      private container: Container;
    
      constructor(client: CosmosClient) {
        this.container = client
          .database(process.env.COSMOS_DATABASE!)
          .container(process.env.COSMOS_CONTAINER!);
      }
    
      async getById(id: string, category: string): Promise<Product | null> {
        try {
          const { resource } = await this.container
            .item(id, category)
            .read<Product>();
          return resource ?? null;
        } catch (error) {
          if (error instanceof ErrorResponse && error.code === 404) {
            return null;
          }
          throw error;
        }
      }
    
      async create(product: Omit<Product, "id">): Promise<Product> {
        const item = { ...product, id: crypto.randomUUID() };
        const { resource } = await this.container.items.create<Product>(item);
        return resource!;
      }
    
      async findByCategory(category: string): Promise<Product[]> {
        const querySpec: SqlQuerySpec = {
          query: "SELECT * FROM c WHERE c.partitionKey = @category",
          parameters: [{ name: "@category", value: category }],
        };
        const { resources } = await this.container.items
          .query<Product>(querySpec)
          .fetchAll();
        return resources;
      }
    }
    ```
    
    ## Related SDKs
    
    | SDK | Purpose | Install |
    |-----|---------|---------|
    | `@azure/cosmos` | Data plane (this SDK) | `npm install @azure/cosmos` |
    | `@azure/arm-cosmosdb` | Management plane (ARM) | `npm install @azure/arm-cosmosdb` |
    | `@azure/identity` | Authentication | `npm install @azure/identity` |
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related