Skip to content

Introduce @experimental decorator to tag client-side features #8086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

joheredi
Copy link
Member

@joheredi joheredi commented Aug 1, 2025

Introduce @experimental decorator to tag client-side features

Status Draft
Author(s) Jose Heredia (joheredi)
Sponsor
Updated 2025-08-18

Objective

Define a decorator, @experimental, that lets TypeSpec authors mark client-side features as experimental. This enables shipping preview helpers, gathering feedback, and promoting features without affecting the service contract version.

Note: @experimental is solely about client-side feature maturity. It does not affect or replace @typespec/versioning, which remains the source of truth for service/API versioning.

Goals

  • Introduce @experimental with minimal syntax.
  • Preserve backward compatibility: if the decorator is not handled by emitters, they keep current behavior.

Non-Goals

  • Modifying @typespec/versioning.
  • Mandating specific emitter behavior; emitters may opt into using this metadata.

Motivation

@typespec/versioning tracks service API versions only. SDK teams commonly add higher-level helpers (batching, pagination convenience, analytics wrappers) after the REST surface is GA. Without a standard way to tag those as “experimental,” teams patch generated code manually—costly and inconsistent. A formal decorator:

  • Co-locates lifecycle metadata with declarations.
  • Gives emitters a single source for warnings, conditional generation, or package splitting.

User Benefits

  • Spec authors: one standard way to mark experimental parts of the client API.
  • Emitter authors: a unified API to query experimental status.
  • Client users: clear documentation/IDE signals about experimental vs. stable features.

Design Proposal

Decorator

  • Name: @experimental

  • Signature

    @experimental(options?: { emitterScope?: string })
    • emitterScope (optional): opaque string used by emitters as a lookup key (e.g., "typescript", "java"). If omitted, it applies to all emitters.
  • Examples

    @experimental
    op listFooExperimental(): Foo[];
    @experimental(#{ emitterScope: "myEmitter" })
    op listFooExperimental(): Foo[];

Lifecycle model & semantics

  • FeatureLifecycle: 'stable' | 'experimental'.

  • Default: if @experimental is absent'stable'.

  • Scope rules:

    • If @experimental has no emitterScope, the target is experimental for all emitters.
    • If emitterScope is present, the target is experimental only when the consuming emitter’s scope matches that string. Otherwise it is treated as stable for that emitter.
    • Multiple applications of @experimental with different scopes may be used on the same target; a match on any one marks it experimental for that scope.
  • No inheritance: there is no implicit propagation. Applying @experimental to a namespace/interface does not make children experimental. Emitters may choose to layer their own inheritance logic, but it is not part of this spec.

  • No wire-protocol impact: purely metadata; does not change HTTP behavior.

Discovery API

A small helper will be provided for emitters/tooling:

function getFeatureLifecycle(
  program: Program,
  target: Type,
  options?: { emitterName?: string }
): string | undefined;
  • Location: exposed alongside the decorator (e.g., in @typespec/http-client) and re-exported from the client generator core (TCGC) for convenience.
  • Behavior: implements the rules above and returns 'experimental' if any matching @experimental applies for the provided scope; otherwise 'undefined'.

Guidance for emitters (non-normative)

Emitters can adopt one or more patterns:

  1. Package splitting
    Generate experimental items into a preview package or sub-namespace (e.g., foo-client-preview).

  2. Constructor opt-in
    Provide a client option such as enablePreviewFeatures: string[] and surface experimental operations only when opted in.

  3. Conditional compilation
    Use language-specific flags (e.g., #if PREVIEW_FEATURES in C#, cfg(feature = "preview") in Rust).

  4. Documentation signals
    Add doc comments/annotations understood by the ecosystem (e.g., TypeDoc tags, Kotlin opt-in annotations, .NET RequiresPreviewFeaturesAttribute) to clearly label experimental APIs.

Alternatives Considered

  • Reuse @previewVersion: conflates client maturity with service versioning.
  • @featureLifecycle (broader enum): more general but no concrete need today; adds complexity vs. a targeted @experimental.
  • External config files: loses co-location with code and is harder to maintain.

Performance Implications

  • None for the TypeSpec compiler.
  • No measurable runtime impact; emitters may ignore this without change in behavior.

Dependencies

  • No new runtime dependencies.
  • Lives in @typespec/http-client (and re-exported by TCGC for emitter use).

Engineering Impact

  • TCGC: register @experimental and expose getFeatureLifecycle.
  • Emitters: optional adoption. If ignored, behavior remains unchanged.

Best Practices

  • Tag new preview helpers with @experimental early.
  • Remove the decorator promptly once the feature is considered stable.

Compatibility

  • Fully backward-compatible: existing specs/emitters unaffected if they ignore the decorator.
  • Omitted decorator preserves current “everything is stable” behavior.

User Impact

  • No breaking changes for spec authors or consumers.
  • Users of updated emitters may see preview packages, gated features, or clearer docs/IDE hints.

FAQ

Q: Why mark a client helper as experimental if the service is already GA?

A: Client helpers (batching, pagination, analytics, etc.) often evolve after the REST API is GA. Marking them experimental lets you ship safely alongside stable features while you validate design and gather feedback.

Q: Why not mark the service/API as preview instead?

A: Service/API versioning (@typespec/versioning) governs the HTTP contract. @experimental is strictly for client SDK features, so you can ship preview helpers without bumping the service version or impacting existing clients.

Q: Can I release experimental client APIs without bumping my package version?

A: Yes. Emitters can gate experimental items behind flags or generate them into preview sub-packages/namespaces, while keeping the main package version unchanged.

Copy link
Contributor

github-actions bot commented Aug 1, 2025

All changed packages have been documented.

  • @typespec/http-client
Show changes

@typespec/http-client - feature ✏️

Introduces @experimental decorator for annotating generated code as experimental.

@azure-sdk
Copy link
Collaborator

azure-sdk commented Aug 1, 2025

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@joheredi joheredi force-pushed the joheredi/feature-lifecycle branch from 434ccbd to 15e40ba Compare August 18, 2025 21:54
@joheredi joheredi marked this pull request as ready for review August 18, 2025 21:55
@joheredi joheredi force-pushed the joheredi/feature-lifecycle branch from eadebd9 to ecc281b Compare August 18, 2025 22:28
@joheredi joheredi changed the title Joheredi/feature lifecycle Introduce @experimental decorator to tag client-side features Aug 18, 2025
@joheredi joheredi force-pushed the joheredi/feature-lifecycle branch from 9e2a4aa to 5061a17 Compare August 20, 2025 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants