feat(typespec-client-generator-core): support per-service api-version map for multi-service packages#4650
Conversation
|
All changed packages have been documented.
Show changes
|
⚡ Benchmark Results
Full details – comparing
|
| Metric | Baseline | Current | Change |
|---|---|---|---|
| total | 🔴 579.5ms | 🔴 713.4ms | +23.1% 🔴 |
| loader | 🟢 158.9ms | 🟡 251.8ms | +58.5% 🔴 |
| resolver | 🟢 20.8ms | 🟢 21.4ms | +2.5% |
| checker | 🟢 185.6ms | 🟡 213.5ms | +15.0% 🔴 |
| validation | 🟢 41.6ms | 🟢 45.3ms | +8.9% 🔴 |
| ↳ validation/@azure-tools/typespec-azure-core | 🟢 6.6ms | 🟢 7.3ms | +10.4% |
| ↳ validation/@typespec/http | 🟢 5.1ms | 🟢 6.3ms | +24.0% 🔴 |
| ↳ validation/@typespec/rest | 🟢 0.6ms | 🟢 0.7ms | +16.5% |
| ↳ validation/@typespec/versioning | 🔴 27.4ms | 🔴 28.6ms | +4.5% |
| ↳ validation/compiler | 🟢 1.6ms | 🟢 1.6ms | +1.2% |
| linter | 🟢 131.4ms | 🟢 149.2ms | +13.6% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/auth-required | 🟢 0.0ms | 🟢 0.0ms | -0.6% |
| ↳ linter/@azure-tools/typespec-azure-core/bad-record-type | 🟢 0.2ms | 🟢 0.2ms | +1.3% |
| ↳ linter/@azure-tools/typespec-azure-core/byos | 🟢 5.5ms | 🟢 7.1ms | +28.8% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/casing-style | 🟢 0.6ms | 🟢 0.7ms | +9.6% |
| ↳ linter/@azure-tools/typespec-azure-core/composition-over-inheritance | 🟢 0.1ms | 🟢 0.1ms | +8.7% |
| ↳ linter/@azure-tools/typespec-azure-core/documentation-required | 🟢 0.9ms | 🟢 0.9ms | +5.2% |
| ↳ linter/@azure-tools/typespec-azure-core/friendly-name | 🟢 0.6ms | 🟢 0.6ms | +6.0% |
| ↳ linter/@azure-tools/typespec-azure-core/key-visibility-required | 🟢 0.2ms | 🟢 0.3ms | +102.7% |
| ↳ linter/@azure-tools/typespec-azure-core/known-encoding | 🟢 0.3ms | 🟢 0.3ms | +20.9% |
| ↳ linter/@azure-tools/typespec-azure-core/long-running-polling-operation-required | 🟢 0.3ms | 🟢 0.3ms | +16.0% |
| ↳ linter/@azure-tools/typespec-azure-core/no-case-mismatch | 🟢 0.2ms | 🟢 0.3ms | +13.3% |
| ↳ linter/@azure-tools/typespec-azure-core/no-closed-literal-union | 🟢 0.3ms | 🟢 0.3ms | +6.3% |
| ↳ linter/@azure-tools/typespec-azure-core/no-enum | 🟢 0.0ms | 🟢 0.0ms | +8.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-error-status-codes | 🟢 0.1ms | 🟢 0.1ms | +10.4% |
| ↳ linter/@azure-tools/typespec-azure-core/no-explicit-routes-resource-ops | 🟢 0.1ms | 🟢 0.1ms | +10.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-format | 🟢 0.6ms | 🟢 0.6ms | +6.9% |
| ↳ linter/@azure-tools/typespec-azure-core/no-generic-numeric | 🟢 0.4ms | 🟢 0.5ms | +8.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-header-explode | 🟡 18.3ms | 🔴 21.0ms | +14.6% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/no-legacy-usage | 🟢 1.1ms | 🟢 1.2ms | +8.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-multiple-discriminator | 🟢 0.1ms | 🟢 0.1ms | +14.2% |
| ↳ linter/@azure-tools/typespec-azure-core/no-nullable | 🟢 0.2ms | 🟢 0.3ms | +15.9% |
| ↳ linter/@azure-tools/typespec-azure-core/no-offsetdatetime | 🟢 1.3ms | 🟢 1.2ms | -5.4% |
| ↳ linter/@azure-tools/typespec-azure-core/no-openapi | 🟢 2.1ms | 🟢 2.4ms | +16.7% |
| ↳ linter/@azure-tools/typespec-azure-core/no-private-usage | 🟢 2.0ms | 🟢 2.1ms | +5.4% |
| ↳ linter/@azure-tools/typespec-azure-core/no-query-explode | 🟡 19.5ms | 🔴 22.0ms | +12.5% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/no-response-body | 🔴 21.8ms | 🔴 25.6ms | +17.4% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/no-rest-library-interfaces | 🟢 0.0ms | 🟢 0.0ms | -1.4% |
| ↳ linter/@azure-tools/typespec-azure-core/no-route-parameter-name-mismatch | 🟢 5.1ms | 🟢 5.8ms | +12.1% |
| ↳ linter/@azure-tools/typespec-azure-core/no-rpc-path-params | 🟢 0.2ms | 🟢 0.2ms | +21.0% |
| ↳ linter/@azure-tools/typespec-azure-core/no-string-discriminator | 🟢 0.0ms | 🟢 0.0ms | +10.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-unknown | 🟢 0.2ms | 🟢 0.2ms | +14.1% |
| ↳ linter/@azure-tools/typespec-azure-core/no-unnamed-union | 🟢 0.4ms | 🟢 0.4ms | +8.2% |
| ↳ linter/@azure-tools/typespec-azure-core/operation-missing-api-version | 🟢 0.2ms | 🟢 0.2ms | +3.6% |
| ↳ linter/@azure-tools/typespec-azure-core/request-body-problem | 🟢 0.3ms | 🟢 0.3ms | +4.6% |
| ↳ linter/@azure-tools/typespec-azure-core/require-versioned | 🟢 0.0ms | 🟢 0.0ms | +7.2% |
| ↳ linter/@azure-tools/typespec-azure-core/response-schema-problem | 🔴 22.4ms | 🔴 25.1ms | +12.1% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/rpc-operation-request-body | 🟢 0.3ms | 🟢 0.4ms | +19.4% |
| ↳ linter/@azure-tools/typespec-azure-core/spread-discriminated-model | 🟢 0.3ms | 🟢 0.3ms | +6.6% |
| ↳ linter/@azure-tools/typespec-azure-core/use-standard-names | 🟢 5.3ms | 🟢 5.9ms | +11.2% |
| ↳ linter/@azure-tools/typespec-azure-core/use-standard-operations | 🟢 0.1ms | 🟢 0.1ms | +9.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-agent-base-type-child-resources | 🟢 4.1ms | 🟢 4.9ms | +19.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-agent-base-type-lifecycle-operations | 🟢 0.0ms | 🟢 0.0ms | -3.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-common-types-version | 🟢 3.7ms | 🟢 4.3ms | +17.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-no-key | 🟢 0.1ms | 🟢 0.1ms | +25.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-usage-discourage | 🟢 0.1ms | 🟢 0.1ms | +10.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-delete-operation-response-codes | 🟢 1.1ms | 🟢 1.3ms | +16.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-path-casing-conflicts | 🟢 3.9ms | 🟢 4.7ms | +20.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-record | 🟢 0.3ms | 🟢 0.4ms | +14.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes | 🟢 0.4ms | 🟢 0.5ms | +16.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-put-operation-response-codes | 🟢 0.0ms | 🟢 0.0ms | +3.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-action-no-segment | 🟢 0.2ms | 🟢 0.2ms | +17.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property | 🟢 0.1ms | 🟢 0.1ms | +22.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-interface-requires-decorator | 🟢 0.0ms | 🟢 0.0ms | +21.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-action-verb | 🟢 0.1ms | 🟢 0.1ms | +24.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property | 🟢 0.1ms | 🟢 0.1ms | +16.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-version-format | 🟢 0.0ms | 🟢 0.0ms | +15.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-key-invalid-chars | 🟢 0.2ms | 🟢 0.3ms | +20.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern | 🟢 0.0ms | 🟢 0.0ms | +15.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation | 🟢 0.2ms | 🟢 0.2ms | +13.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation-response | 🟢 4.1ms | 🟢 4.8ms | +17.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-patch | 🟢 0.3ms | 🟢 0.3ms | +30.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-path-segment-invalid-chars | 🟢 0.2ms | 🟢 0.2ms | +11.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state | 🟢 0.1ms | 🟢 0.1ms | +15.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/beyond-nesting-levels | 🟢 0.1ms | 🟢 0.1ms | +30.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/empty-updateable-properties | 🟢 0.1ms | 🟢 0.2ms | +19.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/improper-subscription-list-operation | 🟢 0.0ms | 🟢 0.0ms | +5.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/lro-location-header | 🟡 11.8ms | 🟡 15.0ms | +26.5% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint | 🟢 0.0ms | 🟢 0.0ms | +13.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers | 🟢 0.3ms | 🟢 0.3ms | +11.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-empty-model | 🟢 0.1ms | 🟢 0.2ms | +26.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-override-props | 🟢 0.1ms | 🟢 0.1ms | +12.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-resource-delete-operation | 🟢 0.2ms | 🟢 0.2ms | +23.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-response-body | 🟡 19.8ms | 🔴 22.7ms | +14.6% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/patch-envelope | 🟢 0.1ms | 🟢 0.2ms | +17.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/resource-name | 🟢 0.1ms | 🟢 0.2ms | +17.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/secret-prop | 🟢 2.3ms | 🟢 2.6ms | +15.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/unsupported-type | 🟢 0.4ms | 🟢 0.4ms | +19.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/version-progression | 🟢 0.0ms | 🟢 0.0ms | -1.0% |
| ↳ linter/@azure-tools/typespec-client-generator-core/property-name-conflict | 🟢 1.1ms | 🟢 1.2ms | +15.0% |
| ↳ linter/@azure-tools/typespec-client-generator-core/require-client-suffix | 🟢 0.2ms | 🟢 0.2ms | -3.9% |
| emit | 🔴 5.81s | 🔴 6.26s | +7.8% 🔴 |
| ↳ emit/@azure-tools/typespec-autorest | 🟢 161.8ms | 🟢 179.1ms | +10.7% 🔴 |
| ↳ emit/@azure-tools/typespec-python | 🔴 4.20s | 🔴 4.60s | +9.4% 🔴 |
| ↳ emit/@typespec/http-client-js | 🔴 1.20s | 🔴 1.35s | +12.7% 🔴 |
| ↳ emit/@typespec/openapi3 | 🟢 152.5ms | 🟢 159.8ms | +4.8% |
| ↳ emit/@typespec/openapi3/compute | 🟢 129.0ms | 🟢 142.5ms | +10.5% 🔴 |
| ↳ emit/@typespec/openapi3/write | 🟢 21.1ms | 🟢 17.2ms | -18.6% 🟢 |
Averaged across 3 specs (azure-arm-resource-manager, azure-core-dataplane, azure-full).
Threshold: changes > ±5% are highlighted.
🟢 Fast · 🟡 Moderate (stages >200ms, rules >10ms) · 🔴 Slow (stages >400ms, rules >20ms)
commit: |
weidongxu-microsoft
left a comment
There was a problem hiding this comment.
agent:
1. Docs are now inconsistent with the feature and behavior.
packages/typespec-client-generator-core/README.md*#COLON|*63-67 and website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/emitter.md*#COLON|*57-61 still say api-version is Type: string, but this PR changes the option schema to also accept a per-service map (packages/typespec-client-generator-core/src/lib.ts*#COLON|*12-27, src/interfaces.ts*#COLON|*44). The docs also say all is accepted without clarifying that multi-service packages intentionally do not support all and fall back to latest. As written, users will configure the option incorrectly.
|
You can try these changes here
|
There was a problem hiding this comment.
another one from agent that is too complex for me to understand :-)
therefore just throw it out for expert
1. Per-service version resolution ignores the wrapper, so shared/common types can get the wrong apiVersions.
getAvailableApiVersions still accepts a wrapper specifically so version filtering can be contextual (src/internal-utils.ts:409-426), but filterApiVersionsWithDecorators now resolves the selected service only from type (src/internal-utils.ts:362-370). That works when the type lives under a service namespace, but it breaks for shared models/enums/unions that are outside the service namespace tree. In those cases getServiceNamespaceForType(context, type) returns undefined, the per-service map is ignored, and the type can keep a broader apiVersions set than the selected service version should allow. You can already see affected call sites that pass type.namespace instead of an operation/service wrapper, e.g. model and enum creation in src/types.ts:876 and src/types.ts:1059.
Suggested fix: carry the wrapper through into filterApiVersionsWithDecorators, and resolve the service from the wrapper first, then fall back to the type.
1b7f254 to
93e6bd2
Compare
… map for multi-service packages The `api-version` emitter option now accepts either a string or a map from each service namespace's full name to its desired version. Services not listed in the map default to their latest version. Multi-service packages do not support the `all` value (in either form); it is ignored and treated as latest. Resolves #4009 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
93e6bd2 to
180d7aa
Compare
Hotfix patch release for `@azure-tools/typespec-client-generator-core` → **0.69.1**. Cherry-picks #4650 (per-service `api-version` map for multi-service packages) onto `release/june-2026` and bumps the patch version. ## Changes - Cherry-pick of the fix commit from #4650 (changeKind set to `fix` for a patch bump) - Version bump: tcgc `0.69.0` → `0.69.1` ## Notes - Targets `release/june-2026` (not `main`) - Only `@azure-tools/typespec-client-generator-core` is affected - `core` submodule pointer unchanged --------- Co-authored-by: tadelesh <chenjieshi@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: iscai-msft <isabellavcai@gmail.com>
Fixes #4009
Summary
Extends the TCGC
api-versionemitter option to accept either astringor aRecord<string, string>(a map from each service namespace's full name to its desired version), enabling per-service version selection for multi-service packages.Behavior
string(e.g."2024-10-01"): applies to single-service packages only. Ignored for multi-service."latest": global keyword, applies regardless of service count."all": single-service only. Multi-service packages do not supportall(in either string or map form) — it is ignored and each service falls back to its latest version.Record<string, string>(e.g.{ "ServiceA": "av1", "ServiceB": "bv2" }): maps each service namespace full name to a version. Services not listed default to their latest version.Implementation notes
resolveApiVersionForServiceas the single source of truth for resolving the version that applies to a given service.getServiceNamespacesis memoized on the context (__serviceNamespaces) since it is consulted on the per-type hot path viagetAvailableApiVersions.handleVersioningMutationForGlobalNamespacenow mutates each service independently using its resolved version.Validation
allignored).