Loading
feat(npm): implement deprecate handler (S11 Step 17, part 2/2)
Part 2 of 2 of the npm hosted plan — Step 17: deprecate handler.
🗂️ Stacked MRs
Split into 2 stacked MRs to keep each within the review size limit (~600 ideal reviewable LoC). Each part targets the previous one (part 1 → main); review and merge bottom-up.
- feat(npm): batch deprecate datastore queries (S... (!773 - merged) • Dzmitry (Dima) Meshcharakou • 19.2
- feat(npm): implement deprecate handler (S11 Ste... (!774 - merged) • Dzmitry (Dima) Meshcharakou • 19.2
👈
📦 What this part adds
NpmVersionDeprecateWriter— the READ COMMITTED transaction envelope that batch-updates the targeted versions'package_jsonand force-expires the packument cache atomically, returningErrNotFound(→ 404version_not_found) when the batch touches fewer rows than requested (a version soft-deleted between resolve and write).DeprecateHandlerforPUT /{slug}/npm/{repo}/{package}(no_attachments, non-emptyversions["{ver}"].deprecated): resolves every targeted version in one query, patches each stored document (empty string clearsdeprecated), returns 200{"ok": true}, and enqueues one package-level cache rebuild off the request path (TODO(s27-cache-rebuild)).- Client input maps to 4xx, never 500: empty / unknown version key → 404
version_not_found; oversized deprecation message → 422package_json_too_large; malformed body → 400bad_request; invalid package name → 422package_name_invalid. A request that targets no version short-circuits to 200 without a transaction or a rebuild.
ℹ️ The route stays on the 501 placeholder until Step 23 conformance; mounting the real handler is a deliberate later step, the same pattern used for the other npm handlers.
✅ Spec coverage
| Acceptance criterion / case | Test |
|---|---|
| AC 24 — deprecate sets the field, fires one package rebuild | TestDeprecateIntegration_Success |
| AC 27 — cache force-expired in the same transaction | TestDeprecateIntegration_Success |
AC 34 — missing / soft-deleted package → 404 package_not_found |
TestDeprecateIntegration_PackageNotFound |
AC 35 — missing / soft-deleted version → 404 version_not_found |
TestDeprecateIntegration_VersionNotFound |
| AC 58 — no resolution → 500 (auth-gate stand-in) | TestDeprecateIntegration_MissingResolution |
Undeprecate — empty string clears deprecated, siblings preserved |
TestDeprecateIntegration_Undeprecate |
| Non-targets skipped; a no-target request short-circuits to 200 | TestDeprecateIntegration_SkipsNonTargets |
| Empty version key → 404; oversized message → 422 | TestDeprecateIntegration_EmptyVersionKey, TestDeprecateIntegration_OversizedMessage |
| Malformed body → 400; invalid package name → 422 | TestDeprecateIntegration_MalformedBody, TestDeprecateIntegration_InvalidPackageName |
| Writer rolls back when a co-batched target is stale | TestDeprecateIntegration_WriterRollsBackStaleVersion |
📚 References
- Plan: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/plans/2026-05-11-npm-hosted.md
- Spec: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/specs/S11-npm-hosted.md
Related to #135 (closed)
Edited by Dzmitry (Dima) Meshcharakou