-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Migrating to TypeSpec
This guide helps service teams migrate their JavaScript SDK generation from OpenAPI specifications and AutoRest to TypeSpec. The migration process involves updating how your SDK is generated, but should not result in breaking changes to the public API surface of your SDK.
This guide is written for service teams who are migrating a high-level client (i.e. a client library with a hand-authored convenience layer) from OpenAPI to TypeSpec.
TypeSpec is Microsoft's new API specification language that provides better tooling, type safety, and developer experience compared to OpenAPI specifications. When you migrate from AutoRest to TypeSpec:
- Your public SDK API should remain the same - this is not a breaking change for your customers
- The generation process changes - you'll use TypeSpec definitions instead of OpenAPI/Swagger files
- Build scripts and configuration update - new tooling replaces AutoRest
- Internal generated code structure changes - but your hand-written client code adapts with minimal changes
-
Customization workflow - generated code is placed in
generated/
and then copied tosrc/
with merge capabilities
Before starting the migration, ensure you have:
- TypeSpec definitions ready: Your service's TypeSpec definitions should be complete and merged into the main branch of the Azure REST API specs repository
-
Local development environment:
- Node.js LTS version
- Local clone of your fork of azure-sdk-for-js
- Local clone of your fork of azure-rest-api-specs
- Understanding of your current SDK: Know which packages in azure-sdk-for-js belong to your service
Install the TypeSpec client generator CLI globally:
npm install -g @azure-tools/[email protected]
For more information on tsp-client, see the TypeSpec Client Generator CLI documentation
In your package directory (e.g., sdk/your-service/your-package
), add the tsp-location.yaml
file that points to your TypeSpec definitions in the azure-rest-api-specs repository.
An example tsp-location.yaml
file looks like this:
directory: specification/ai/Azure.AI.Projects
commit: a720ec94da68a0d77a691ddd563a4528883638ee
repo: Azure/azure-rest-api-specs
additionalDirectories:
- specification/common-types/resource-management
After migration, your package will have this structure:
sdk/your-service/your-package/
├── generated/ # Generated TypeScript code (temporary)
│ ├── api/
│ ├── models/
│ ├── static-helpers/
│ ├── index.ts
│ └── restorePollerHelpers.ts (if LRO is used)
├── src/ # Your source code (generated + customizations)
│ ├── generated/ # ⚠️ Will be removed during migration
│ ├── index.ts # Your public exports
│ ├── yourClient.ts # Your hand-written client code
│ └── ... # Other hand-written files
├── tsp-location.yaml # TypeSpec configuration
└── package.json
Key differences from AutoRest:
- Generated code initially goes to
generated/
(package root) - The customization tool copies files from
generated/
tosrc/
-
tsp-location.yaml
replaces AutoRest configuration - Generated models and client interfaces follow modern TypeScript conventions
Replace your AutoRest generation script with TypeSpec generation and customization scripts:
Before (AutoRest):
{
"scripts": {
"generate": "autorest --typescript swagger/README.md"
}
}
After (TypeSpec):
{
"scripts": {
"generate:client": "tsp-client update -d && npm run format && dev-tool run customization apply-v2 --skip index.ts",
"build": "npm run clean && dev-tool run build-package && dev-tool run extract-api",
"test": "npm run test:node && npm run test:browser",
"test:node": "dev-tool run build-test --no-browser-test && dev-tool run test:vitest"
}
}
Update your package.json
dependencies to use the new core packages:
Key dependency changes:
{
"dependencies": {
"@azure-rest/core-client": "^2.3.2",
"@azure/core-lro": "^3.1.0",
"@azure/core-paging": "^1.6.2",
"@azure/core-rest-pipeline": "^1.19.0",
"@azure/core-util": "^1.11.0"
},
"engines": {
"node": ">=20.0.0"
}
}
Replace:
-
@azure/core-client
→@azure-rest/core-client
-
@azure/[email protected]
→@azure/[email protected]
Run the TypeSpec client generation:
cd sdk/your-service/your-package
npm run generate:client
Use the dev-tool customization command to copy generated files to src/
and merge with existing customizations:
npx dev-tool customization apply-v2 --skip index.ts
This command:
- Copies all files from
generated/
tosrc/
- Performs 3-way merges with any existing files in
src/
- Preserves your customizations from previous versions
Add a mapPagedAsyncIterable
function to handle pagination mapping:
// src/mappings.ts
export function mapPagedAsyncIterable<T, U>(
iterable: PagedAsyncIterableIterator<T>,
mapper: (item: T) => U
): PagedAsyncIterableIterator<U> {
return {
next() {
return iterable.next().then(({ done, value }) => ({
done,
value: done ? (value as any) : mapper(value),
}));
},
[Symbol.asyncIterator]() {
return this;
},
byPage: (settings) => {
return mapPagedAsyncIterable(iterable.byPage(settings), (page) => ({
...page,
value: page.value.map(mapper),
}));
},
};
}
Update your eslint.config.mjs
to handle the new structure:
import azsdkEslint from "@azure/eslint-plugin-azure-sdk";
export default azsdkEslint.config([
{
rules: {
"@typescript-eslint/no-empty-object-type": "warn",
"@azure/azure-sdk/ts-naming-options": "warn",
},
},
{
files: [
"src/api/**/*.ts",
"src/classic/**/*.ts",
"src/models/**/*.ts",
"src/static-helpers/**/*.ts",
"src/restorePollerHelpers.ts", // if LRO is used
],
rules: {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"tsdoc/syntax": "off",
"spaced-comment": "off",
"no-useless-escape": "off",
"no-unused-expressions": "off",
},
},
]);
Delete the following files that are no longer needed:
-
swagger/README.md
(or similar AutoRest configuration) - Any custom AutoRest configuration files
- The old
src/generated/
directory
-
Generate and apply customizations:
npm run generate:client
-
Build the package:
npm run build
-
Run tests:
npm run test
-
Validate the API surface: Use API Extractor to ensure your public API hasn't changed unexpectedly.
-
Always generate to
generated/
first - never modify files in this directory directly -
Use the customization tool to copy files to
src/
and preserve customizations - Keep customizations minimal - prefer TypeSpec definition changes over post-generation modifications
- Test the customization merge - the 3-way merge can help but may require manual conflict resolution
After migration, your development workflow becomes:
- Update TypeSpec definitions in azure-rest-api-specs
-
Generate new code:
npm run generate:client
-
Build and test:
npm run build && npm run test
- Update your package version according to Azure SDK versioning guidelines
- Add changelog entries describing the migration (usually marked as internal changes)
- Coordinate with the Azure SDK team for any breaking changes
Problem: TypeScript compilation errors after migration. Solution:
- Run
pnpm install
to ensure dependencies are installed - Check that the customization tool completed successfully
- Verify all imports are pointing to the correct files in
src/
Problem: The customization tool reports merge conflicts. Solution:
- Examine the conflicted files in
src/
- Resolve conflict markers manually
- Test that the resolution maintains your customizations
- Consider updating TypeSpec definitions to reduce future conflicts
Problem: Generated API doesn't match expectations. Solution:
- Review your TypeSpec definitions and ensure they're complete
- Check emitter options in the generation command
- Use the customization workflow to add necessary adaptations
If you encounter issues during migration:
-
Check existing documentation:
-
Consult with the team:
- Post in the TypeSpec Discussion Teams channel
- Tag
@DPG TypeScript
for JavaScript/TypeScript-specific questions
-
File issues:
For complete examples of migrated packages, see:
sdk/keyvault/keyvault-admin/
sdk/keyvault/keyvault-keys/
sdk/keyvault/keyvault-certificates/
These packages demonstrate the full migration including the customization workflow, helper functions, and updated configurations.