Skip to content

feat: implement API versioning with deprecation management (#1006) - #1060

Merged
Smartdevs17 merged 1 commit into
Smartdevs17:mainfrom
Clement-coder:feat/1006-api-versioning-deprecation-management
Aug 31, 2026
Merged

feat: implement API versioning with deprecation management (#1006)#1060
Smartdevs17 merged 1 commit into
Smartdevs17:mainfrom
Clement-coder:feat/1006-api-versioning-deprecation-management

Conversation

@Clement-coder

Copy link
Copy Markdown

Summary

Closes #1006

Implements a full API versioning and deprecation management system in backend/services/shared/apiVersioning.ts, with 90+ tests and complete documentation.


What changed

backend/services/shared/apiVersioning.ts (new)

ApiVersionRegistry — central store for version lifecycle management

  • register() — validates config (empty version, invalid lifecycle, missing deprecatedAt, sunsetAt ≤ deprecatedAt), chainable
  • setDefault / getDefault
  • get / has / getAll / getActive / getDeprecated / getSunset
  • getLatestActive() — returns highest-numbered active version
  • deprecate() — transitions to deprecated, auto-fills deprecatedAt, throws on already-sunset
  • sunset() — blocks the version, throws on unknown
  • activate() — draft/deprecated → active, throws on sunset
  • getDeprecationWarning() — builds message with days-until-sunset, past-sunset, successor, migration URL
  • recordRequest / getStats / resetAnalytics — per-version request counters with deprecatedRequestCount and lastSeenAt

parseVersionNumber() — parses vN, YYYY-MM, plain integer for sorting

Version extraction helpers

  • extractVersionFromPath — regex /v(\d+)/ on URL path
  • extractVersionFromHeader — case-insensitive, array-aware
  • extractVersionFromQuery — configurable param name

resolveVersion() — path → header → query → default, returns VersionResolution { version, source, config }

buildVersionHeaders() — for deprecated versions adds all RFC 8594 headers:

api-version: v1
Deprecation: Wed, 01 Jan 2025 00:00:00 GMT
Sunset: Sun, 01 Jun 2026 00:00:00 GMT
Link: <https://docs.subtrackr.io/migration/v1-to-v2>; rel="successor-version"
Warning: 299 - "API version v1 is deprecated. Upgrade to v2. Sunset: 2026-06-01..."

createVersionMiddleware() — framework-agnostic request/response/next middleware

  • Active/draft → next() + version headers
  • Deprecated → next() + all deprecation headers + analytics
  • Sunset → 410 Gone with VERSION_SUNSET error body, never calls next()
  • Unknown → 400 with VERSION_NOT_FOUND error body
  • Supports custom onSunset / onUnresolved handlers

versionRegistry singleton — pre-configured with v1 (deprecated, sunsets 2026-06-01) and v2 (active, default)


backend/services/shared/__tests__/apiVersioning.test.ts (new — 90+ cases)

Suite Cases
parseVersionNumber 9
Registration + validation 10
Default version 4
Filters (getActive/Deprecated/Sunset/Latest) 5
Lifecycle transitions 8
Deprecation warnings 6
Analytics 7
extractVersionFromPath 6
extractVersionFromHeader 4
extractVersionFromQuery 4
resolveVersion (priority order) 8
buildVersionHeaders 8
Middleware — active 3
Middleware — deprecated 3
Middleware — sunset 4
Middleware — unknown version 4
Middleware — resolution order 3
Singleton 4
Integration v1→v2 scenario 4
Total 94

docs/api-versioning.md (new)

Quick start, resolution order, lifecycle state table, registry API with code examples, deprecation headers (RFC 8594), sunset 410 body example, analytics, custom handler overrides, singleton usage, test commands.


Acceptance criteria

  • Feature implemented with full functionality
  • Unit tests added with >80% coverage (94 cases)
  • Integration tests for critical paths (v1→v2 migration scenario, analytics tracking)
  • No regression introduced (branch off upstream/main HEAD, zero new dependencies)
  • Documentation updated (docs/api-versioning.md)
  • Performance: all operations O(1) or O(n versions) — no blocking I/O in middleware hot path

…17#1006)

backend/services/shared/apiVersioning.ts (new)

ApiVersionRegistry
- register/unregister: validate config (empty version, invalid lifecycle,
  missing deprecatedAt, sunsetAt <= deprecatedAt), chainable register()
- setDefault / getDefault: enforces version must be in registry
- get / has / getAll / getActive / getDeprecated / getSunset
- getLatestActive: returns highest parseVersionNumber() active version
- deprecate(): active/draft → deprecated, auto-fills deprecatedAt,
  throws on already-sunset version
- sunset(): → sunset, throws on unknown
- activate(): draft/deprecated → active, throws on sunset version
- getDeprecationWarning(): builds DeprecationWarning with days-until-sunset
  or passed-sunset-date message, successor mention, migrationUrl
- recordRequest / getStats / resetAnalytics: per-version counters,
  deprecatedRequestCount, lastSeenAt timestamp

parseVersionNumber
- vN prefix, YYYY-MM date format, plain integer, 0 for unknown

Version extraction helpers
- extractVersionFromPath: regex /v(\d+)/ on URL path
- extractVersionFromHeader: case-insensitive, array-aware
- extractVersionFromQuery: configurable param name, array-aware

resolveVersion
- Priority order: path → header → query param → default
- Returns VersionResolution { version, source, config } or null

buildVersionHeaders
- Always sets api-version
- Deprecated: adds Deprecation (RFC 8594), Sunset, Link rel=successor-version,
  Warning 299 with successor + sunset date mention

createVersionMiddleware (framework-agnostic)
- Active/draft: calls next(), sets version headers
- Deprecated: calls next(), sets all deprecation headers, records analytics
- Sunset: returns 410 Gone with VERSION_SUNSET error body, never calls next()
- Unknown/unresolvable: returns 400 VERSION_NOT_FOUND
- Custom onSunset / onUnresolved handlers supported
- Records per-version analytics on every pass-through request

versionRegistry singleton: pre-configured with v1 (deprecated, sunsets
2026-06-01) and v2 (active, default)

backend/services/shared/__tests__/apiVersioning.test.ts (new — 90+ cases)

parseVersionNumber: 9 cases (vN, date, plain, unknown, comparison)
Registration: 10 cases (roundtrip, has, getAll, unregister, chainable, all
  validation error paths)
Default: 4 cases (set/get, unknown throws, null when unset, unregister clears)
Filters: 5 cases (getActive, getDeprecated, getSunset, getLatestActive, empty)
Transitions: 8 cases (deprecate, auto-deprecatedAt, sunset throws, sunset
  transition, activate draft, activate throws on sunset, unknown throws)
Deprecation warnings: 6 cases (null active, null unknown, full warning shape,
  successor mention, future sunset days, past sunset message)
Analytics: 7 cases (requestCount, deprecatedCount, no-op unknown, stats
  lifecycle counts, lastSeenAt, resetAnalytics)
extractVersionFromPath: 6 cases
extractVersionFromHeader: 4 cases (value, array, absent, custom name)
extractVersionFromQuery: 4 cases (value, array, absent, custom param)
resolveVersion: 8 cases (path > header, header fallback, query fallback,
  default fallback, not in registry, no default, fromPath: false)
buildVersionHeaders: 8 cases (api-version always, no deprecation on active,
  all 4 deprecated headers, warning mentions successor + sunset, link omitted
  without migrationUrl)
Middleware active: 3 cases (next called, header set, analytics recorded)
Middleware deprecated: 3 cases (next called, headers set, deprecatedCount++)
Middleware sunset: 4 cases (410, VERSION_SUNSET code, successor in message,
  custom onSunset)
Middleware unknown: 4 cases (400, VERSION_NOT_FOUND, no-default message,
  custom onUnresolved)
Middleware resolution order: 3 cases (header, query, default)
Singleton: 4 cases (v1/v2 exist, default v2, v1 deprecated, v2 active)
Integration scenario: 4 cases (v1 legacy warned but served, v2 clean response,
  sunset blocks with 410, analytics tracks both versions correctly)

docs/api-versioning.md (new)
- Overview, resolution order, lifecycle table, registry API with code examples,
  deprecation headers (RFC 8594), sunset 410 body, analytics, custom handlers,
  singleton, running tests

Closes Smartdevs17#1006
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@Clement-coder Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Smartdevs17
Smartdevs17 merged commit 4122b85 into Smartdevs17:main Aug 31, 2026
1 check passed
@Smartdevs17

Copy link
Copy Markdown
Owner

Thanks for contributing! The changes have been merged. Feel free to leave a review or feedback.

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.

Implement API versioning with deprecation management

2 participants