Option
HeimHeim Skill Entwicklertools azure-cosmos-ts

azure-cosmos-ts

microsoft/skills microsoft/skills

Führen Sie CRUD-Vorgänge, Abfragen, Massenvorgänge und Containerverwaltung für die Azure Cosmos DB NoSQL-API mit dem @azure/cosmos TypeScript/JavaScript-SDK durch.

...Alle erweitern
4
Zeit aktualisiert 13. September 2026

@azure/cosmos (TypeScript/JavaScript)

Data Plane SDK für Azure Cosmos DB NoSQL API-Operationen — CRUD für Dokumente, Abfragen und Massenvorgänge.

⚠️ Daten- vs. Verwaltungsebene

  • Dieses SDK (@azure/cosmos): CRUD-Operationen für Dokumente, Abfragen, gespeicherte Prozeduren
  • Management SDK (@azure/arm-cosmosdb): Erstellen von Konten, Datenbanken und Containern über ARM

Installation

npm install @azure/cosmos @azure/identity

Aktuelle Version: 4.9.0
Node.js: >= 20.0.0

Umgebungsvariablen

COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/
COSMOS_DATABASE=<database-name>
COSMOS_CONTAINER=<container-name>
# Nur für Schlüsselbasierte Authentifizierung (AAD wird bevorzugt)
COSMOS_KEY=<account-key>
AZURE_TOKEN_CREDENTIALS=prod # Nur erforderlich, wenn DefaultAzureCredential in der Produktion verwendet wird
</account-key></container-name></database-name></account>

Authentifizierung

Microsoft Entra-Tokenanmeldeinformationen (Empfohlen)

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

// Lokale Entwicklung: DefaultAzureCredential. Produktion: AZURE_TOKEN_CREDENTIALS=prod oder AZURE_TOKEN_CREDENTIALS=<specific_credential> festlegen
const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
// Oder in der Produktion direkt ein spezifisches Anmeldeobjekt verwenden:
// Siehe 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,
});
</specific_credential>

Schlüsselbasierte Authentifizierung

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

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

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

Ressourcenhierarchie

CosmosClient
└── Database
    └── Container
        ├── Items (Dokumente)
        ├── Scripts (gespeicherte Prozeduren, Trigger, UDFs)
        └── Conflicts

Kernoperationen

Datenbank- und Container-Einrichtung

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

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

Dokument erstellen

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);
</product>

Dokument lesen

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

if (resource) {
  console.log(resource.name);
}
</product>

Dokument aktualisieren (Ersetzen)

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);
}
</product></product>

Dokument hochschreiben (Upsert)

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

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

Dokument löschen

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

Dokument patchen (Teilaktualisierung)

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);
</product>

Abfragen

Einfache Abfrage

const { resources } = await container.items
  .query<product>("SELECT * FROM c WHERE c.price </product>

Parametrisierte Abfrage (Empfohlen)

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

const querySpec: SqlQuerySpec = {
  query: "SELECT * FROM c WHERE c.partitionKey = @category AND c.price (querySpec)
  .fetchAll();

Abfrage mit Paginierung

const queryIterator = container.items.query<product>(querySpec, {
  maxItemCount: 10, // Elemente pro Seite
});

while (queryIterator.hasMoreResults()) {
  const { resources, continuationToken } = await queryIterator.fetchNext();
  console.log(`Seite mit ${resources?.length} Elementen`);
  // Verwenden Sie continuationToken für die nächste Seite, falls erforderlich
}
</product>

Partitionübergreifende Abfrage

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

Massenvorgänge

Massenvorgänge ausführen

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 

Partitionschlüssel

Einfacher Partitionschlüssel

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

Hierarchischer Partitionschlüssel (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,
  },
});

// Vorgänge erfordern ein Array von Partitionschlüsselwerten
const { resource } = await container.items.create({
  id: "order-1",
  tenantId: "tenant-a",
  userId: "user-123",
  sessionId: "session-xyz",
  total: 99.99,
});

// Lesen mit hierarchischem Partitionschlüssel
const { resource: order } = await container
  .item("order-1", ["tenant-a", "user-123", "session-xyz"])
  .read();

Fehlerbehandlung

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("Dokument nicht gefunden");
        break;
      case 409:
        console.log("Konflikt - Dokument existiert bereits");
        break;
      case 412:
        console.log("Vorbedingung fehlgeschlagen (ETag-Abweichung)");
        break;
      case 429:
        console.log("Rate begrenzt - Wiederholung nach:", error.retryAfterInMs);
        break;
      default:
        console.error(`Cosmos-Fehler ${error.code}: ${error.message}`);
    }
  }
  throw error;
}

Optimistische Parallelität (ETags)

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

if (resource && etag) {
  resource.price = 899.99;

  try {
    // Nur ersetzen, wenn ETag übereinstimmt
    await container.item("product-1", "electronics").replace(resource, {
      accessCondition: { type: "IfMatch", condition: etag },
    });
  } catch (error) {
    if (error instanceof ErrorResponse && error.code === 412) {
      console.log("Dokument wurde von einem anderen Prozess geändert");
    }
  }
}
</product>

TypeScript-Typenreferenz

import {
  // Client & Ressourcen
  CosmosClient,
  Database,
  Container,
  Item,
  Items,

  // Operationen
  OperationInput,
  BulkOperationType,
  PatchOperation,

  // Abfragen
  SqlQuerySpec,
  SqlParameter,
  FeedOptions,

  // Partitionschlüssel
  PartitionKeyDefinition,
  PartitionKeyDefinitionVersion,
  PartitionKeyKind,

  // Antworten
  ItemResponse,
  FeedResponse,
  ResourceResponse,

  // Fehler
  ErrorResponse,
} from "@azure/cosmos";

Best Practices

  1. Microsoft Entra-Tokenanmeldeinformationen verwenden — Verwenden Sie DefaultAzureCredential für die lokale Entwicklung; verwenden Sie ManagedIdentityCredential oder WorkloadIdentityCredential für die Produktion
  2. Immer parametrisierte Abfragen verwenden — Verhindert Injektion, verbessert die Planzwischenspeicherung
  3. Partitionschlüssel angeben — Vermeiden Sie partitionübergreifende Abfragen, wenn möglich
  4. Massenvorgänge verwenden — Für mehrere Schreibvorgänge verwenden Sie executeBulkOperations
  5. 429-Fehler behandeln — Implementieren Sie Wiederholungslogik mit exponentiellem Backoff
  6. ETags für Parallelität verwenden — Verhindern Sie verlorene Aktualisierungen in parallelen Szenarien
  7. Client beim Herunterfahren schließen — Rufen Sie client.dispose() im Bereinigungscode auf

Häufige Muster

Dienstschichtenmuster

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> {
    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>): 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;
  }
}
</product></product></product></product></product></product></product>

Verwandte SDKs

SDKZweckInstallation
`@azure/cosmos`Datenebene (dieses SDK)`npm install @azure/cosmos`
`@azure/arm-cosmosdb`Verwaltungsebene (ARM)`npm install @azure/arm-cosmosdb`
`@azure/identity`Authentifizierung`npm install @azure/identity`
Auf GitHub ansehen
---
name: azure-cosmos-ts
description: Perform CRUD operations, queries, bulk operations, and container management on Azure Cosmos DB NoSQL API using the @azure/cosmos TypeScript/JavaScript SDK.
license: MIT
---

# @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` |

Alle Dateien

0 Dateien

azure-cosmos-ts installieren

Laden Sie die Skill-Dateien herunter und extrahieren Sie diese in Ihr .claude/skills/-Verzeichnis.

ZIP herunterladen

Klonen Sie das Repository und kopieren Sie die Skill-Dateien in Ihr Projekt.

git clone https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-typescript/skills/azure-cosmos-ts # Copy SKILL.md to your .claude/skills/ directory

Kopieren Kopieren
Schnelle Einrichtung: Kopieren Sie den Ordner „skill“ nach .claude/skills/. Claude erkennt und verwendet die Fähigkeit automatisch.
Repository microsoft/skills

Ähnliche Skills

algorithmic-art
Zeit aktualisiert 27. August 2026
tech-debt-tracker
Zeit aktualisiert 29. August 2026
receiving-code-review
Zeit aktualisiert 3. September 2026
deprecation-and-migration
Zeit aktualisiert 3. September 2026
OR