feat: implement API versioning with deprecation management (#1006) - #1060
Merged
Smartdevs17 merged 1 commit intoAug 31, 2026
Conversation
…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
|
@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! 🚀 |
Owner
|
Thanks for contributing! The changes have been merged. Feel free to leave a review or feedback. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 managementregister()— validates config (empty version, invalid lifecycle, missingdeprecatedAt,sunsetAt ≤ deprecatedAt), chainablesetDefault/getDefaultget/has/getAll/getActive/getDeprecated/getSunsetgetLatestActive()— returns highest-numbered active versiondeprecate()— transitions to deprecated, auto-fillsdeprecatedAt, throws on already-sunsetsunset()— blocks the version, throws on unknownactivate()— draft/deprecated → active, throws on sunsetgetDeprecationWarning()— builds message with days-until-sunset, past-sunset, successor, migration URLrecordRequest/getStats/resetAnalytics— per-version request counters withdeprecatedRequestCountandlastSeenAtparseVersionNumber()— parsesvN,YYYY-MM, plain integer for sortingVersion extraction helpers
extractVersionFromPath— regex/v(\d+)/on URL pathextractVersionFromHeader— case-insensitive, array-awareextractVersionFromQuery— configurable param nameresolveVersion()— path → header → query → default, returnsVersionResolution { version, source, config }buildVersionHeaders()— for deprecated versions adds all RFC 8594 headers:createVersionMiddleware()— framework-agnostic request/response/next middlewarenext()+ version headersnext()+ all deprecation headers + analytics410 GonewithVERSION_SUNSETerror body, never callsnext()400withVERSION_NOT_FOUNDerror bodyonSunset/onUnresolvedhandlersversionRegistrysingleton — pre-configured with v1 (deprecated, sunsets 2026-06-01) and v2 (active, default)backend/services/shared/__tests__/apiVersioning.test.ts(new — 90+ cases)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