feat(npm): NpmPackageByID resolver (npm hosted step 12, part 1/5)
This change is split into 5 stacked MRs to keep each within review size. Each part targets the previous one (part 1 targets main); review and merge bottom-up, part 1 first.
Part 1 of 5 of docs/plans/2026-05-11-npm-hosted.md — Step 12: packument cache rebuilder (in-process).
Adds the NpmPackageByID datastore resolver: it resolves a package's name, scope, and repository identifiers by surrogate id, joining through npm_repositories to the foundation repositories row. The async rebuild job carries only (namespace_id, npm_package_id), so the rebuilder uses this to resolve the rest before rebuilding the cache.
- Plan: docs/plans/2026-05-11-npm-hosted.md
- Spec: S11-npm-hosted
Related to #130 (closed)
Stacked MRs (review/merge bottom-up)
- feat(npm): NpmPackageByID resolver (npm hosted ... (!712 - merged) • David Fernandez • 19.2
👈 - feat(npm): metadata-file write methods (npm hos... (!713 - merged) • David Fernandez, Dzmitry (Dima) Meshcharakou • 19.2
- feat(npm): transactional cache upsert (npm host... (!714 - merged) • David Fernandez, Dzmitry (Dima) Meshcharakou • 19.2
- feat(npm): packument cache rebuilder core (npm ... (!715 - merged) • David Fernandez, Dzmitry (Dima) Meshcharakou • 19.2
- feat(npm): wire rebuild enqueue + tests (npm ho... (!716 - merged) • David Fernandez, Dzmitry (Dima) Meshcharakou • 19.2
Database Review Evidence
Queries
Note
Plans are from EXPLAIN (ANALYZE, BUFFERS) against an ephemeral
PostgreSQL 17 container (matching GL_PG_CURR_VERSION from
.gitlab-ci-other-versions.yml), with synthesized seed data rolled
back per query and the container torn down at the end of the run.
Numbers reflect moderate cardinality and do not capture
production-scale effects. See
Database review evidence
for seed sizing, methodology, and the anomalies the skill flags.
Expand each row's details for the seed shape, rendered SQL, bound args,
and raw plan.
| Method | Plan node | Index | Rows (plan / actual) | Cost | Time | Buffers (hit / read) | Partitions |
|---|---|---|---|---|---|---|---|
datastore.NpmPackageByID |
Limit | npm_packages_p32_pkey |
1 / 1 | 10.35 | 0.021ms | 5 / 0 | 1 per table (pruned from 64) |
datastore.NpmPackageByID
Summary: Plan matches the resolver's intent. The namespace_id literal prunes each of the three hash-partitioned tables to a single partition (_p32, one of 64), and the driving row is fetched by primary-key Index Scan on npm_packages_p32_pkey (Index Cond: id = $2 AND namespace_id = $1), with soft_deleted_at IS NULL applied as a residual filter on that single PK-located row. The two joins resolve their pruned single-partition npm_repositories and repositories rows via single-row Seq Scans; the repository soft-delete gate appears as the expected residual Filter: soft_deleted_at IS NULL on the repositories partition scan (no extra index needed for a one-row partition). Planner and actual rows match (1 / 1) and execution stays at ~0.04ms over 5000 seeded packages. No anomalies.
Seed shape: namespaces=1, repositories=1, npm_repositories=1, npm_packages=5000
Rendered SQL:
SELECT npm_packages.name AS "npm_packages.name",
npm_packages.scope AS "npm_packages.scope",
npm_repositories.id AS "npm_repositories.id",
npm_repositories.repository_id AS "npm_repositories.repository_id",
repositories.name AS "repositories.name"
FROM public.npm_packages
INNER JOIN public.npm_repositories ON ((npm_repositories.id = npm_packages.npm_repository_id) AND (npm_repositories.namespace_id = npm_packages.namespace_id))
INNER JOIN public.repositories ON (((repositories.id = npm_repositories.repository_id) AND (repositories.namespace_id = npm_packages.namespace_id)) AND (repositories.soft_deleted_at IS NULL))
WHERE ((npm_packages.namespace_id = $1::uuid) AND (npm_packages.id = $2)) AND (npm_packages.soft_deleted_at IS NULL)
LIMIT $3;Bound args: [$1 = namespace_id cc90c01f-8e36-4f08-8650-b4cfb17e8530, $2 = npm_packages.id 2501, $3 = LIMIT 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..10.35 rows=1 width=88) (actual time=0.021..0.021 rows=1 loops=1)
Buffers: shared hit=5
-> Nested Loop (cost=0.28..10.35 rows=1 width=88) (actual time=0.020..0.021 rows=1 loops=1)
Join Filter: (npm_repositories.repository_id = repositories.id)
Buffers: shared hit=5
-> Nested Loop (cost=0.28..9.33 rows=1 width=87) (actual time=0.016..0.017 rows=1 loops=1)
Join Filter: (npm_repositories.id = npm_packages.npm_repository_id)
Buffers: shared hit=4
-> Index Scan using npm_packages_p32_pkey on npm_packages_p32 npm_packages (cost=0.28..8.30 rows=1 width=79) (actual time=0.013..0.013 rows=1 loops=1)
Index Cond: ((id = '2501'::bigint) AND (namespace_id = 'cc90c01f-8e36-4f08-8650-b4cfb17e8530'::uuid))
Filter: (soft_deleted_at IS NULL)
Buffers: shared hit=3
-> Seq Scan on npm_repositories_p32 npm_repositories (cost=0.00..1.01 rows=1 width=32) (actual time=0.003..0.003 rows=1 loops=1)
Filter: (namespace_id = 'cc90c01f-8e36-4f08-8650-b4cfb17e8530'::uuid)
Buffers: shared hit=1
-> Seq Scan on repositories_p32 repositories (cost=0.00..1.01 rows=1 width=41) (actual time=0.003..0.003 rows=1 loops=1)
Filter: ((soft_deleted_at IS NULL) AND (namespace_id = 'cc90c01f-8e36-4f08-8650-b4cfb17e8530'::uuid))
Buffers: shared hit=1
Planning:
Buffers: shared hit=911
Planning Time: 1.935 ms
Execution Time: 0.035 ms
(22 rows)Timings: planning 1.935ms, execution 0.035ms, total 1.970ms.