Render cached package facts in initial HTML - #1007
Conversation
690c6af to
43873b7
Compare
|
|
||
| context.res.setHeader( | ||
| 'Cache-Control', | ||
| 'public, s-maxage=300, stale-while-revalidate=86400' |
There was a problem hiding this comment.
Seems odd that this is here - is this not duplication with existing headers and code?
There was a problem hiding this comment.
This header is for the rendered Next.js document, not the size API response. The package page is handled by the catch-all Koa route, so the API cache-control middleware never runs for it; without setting this in getServerSideProps, Next serves the SSR document with its default non-shared cache behavior. I added an inline comment making that boundary explicit. The 5-minute fresh / 1-day stale window only caches cache-derived public package HTML.
| // run for Next.js page responses handled by the catch-all Koa route. | ||
| context.res.setHeader( | ||
| 'Cache-Control', | ||
| 'public, s-maxage=300, stale-while-revalidate=86400' |
There was a problem hiding this comment.
Can we keep this cache in some existing config / new config
There was a problem hiding this comment.
Moved both SSR document cache durations into the existing config.CACHE object as PACKAGE_PAGE_SHARED and PACKAGE_PAGE_STALE. getServerSideProps now only constructs the header from those configured values.
There was a problem hiding this comment.
file too small can be inlined?
There was a problem hiding this comment.
Removed this standalone file. The request-to-package transforms now live in packageResolution.service.ts alongside the normalized resolved-package transform used by both size and exports workflows. This deletes the tiny module without duplicating the parsing logic in SSR and Koa.
| expect(build).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it('returns an explicit cache miss without invoking the builder', async () => { |
There was a problem hiding this comment.
Seems ike implementation detail. remove very coupled test cases
There was a problem hiding this comment.
Addressed in 3934389. I removed the interaction-heavy miss, builder call-count, and cache-write assertions. The remaining tests exercise public outcomes: an exact cached result remains available when npm fails, refresh returns a miss instead of cached data, build output preserves nullable metadata, and abort propagates to cancellation.
| scoped: requested.scoped, | ||
| packageString: `${requested.name}@${version}`, | ||
| description: '', | ||
| repository: '', |
There was a problem hiding this comment.
Why empty? Is there a better way to represent nullability or union?
There was a problem hiding this comment.
Addressed in 3934389. Error logging now uses a requested-package or resolved-package union. Requested context no longer fabricates description, repository, or a latest version; resolved metadata is explicitly nullable.
There was a problem hiding this comment.
Follow-up in 5815af2 removes the remaining empty-string sentinels across this workflow, not only the original error context. Error messages are now required, optional details/log fields are omitted, package metadata and UI category/route state use explicit nullability, renderers omit absent content, and exact-version detection returns string | null instead of calling semver with version ?? "". Whitespace-only npm metadata is normalized to null and covered by a focused test. I also audited every file changed by this PR; no ?? "", || "", empty default, or empty-return sentinel remains.
| ctx.state.packageRequestPolicy = { | ||
| forceBuild: ctx.query.force !== undefined, | ||
| cacheOnly: ctx.query.peep !== undefined, | ||
| peekOnly: ctx.query.peek !== undefined, |
There was a problem hiding this comment.
peep and peek sound similar. are they different enough? can we normalize names to reduce terminology fragmentation throughout the pr and rest of the code.
There was a problem hiding this comment.
Addressed in 3934389 without compatibility aliases. The HTTP contract is now one validated cache parameter with prefer, refresh, or only. All internal layers carry the same cacheMode, cache-only misses consistently return 404, and repository callers now use cache=refresh.
There was a problem hiding this comment.
Follow-up in 79c83cb makes the values outcome-oriented: cache-first, force-rebuild, and cache-only. The same constant tuple now drives validation and typing, so these names cannot diverge again.
| const startedAt = now() | ||
| const cachePolicy = ctx.state.packageRequestPolicy.forceBuild | ||
| ? 'bypass' | ||
| : 'read' |
There was a problem hiding this comment.
Terminology fragmentation again.
There was a problem hiding this comment.
Addressed in 3934389. The forceBuild to cachePolicy to bypass/read translation is gone. PackageRequest owns one cacheMode, the service reads it directly, PackageSizeLookup became PackageSizeCacheResult, and the middleware is now packageSizeCacheMiddleware.
There was a problem hiding this comment.
Second pass in 79c83cb also consolidated callers. Browser API methods, MCP proxy calls, historical jobs, bulk population, rebuild comparison, and integration tests now use createPackageApiPath for endpoint names, package encoding, cache mode, record, and limit parameters. The only literal package URLs left are the shell stress fixture using default behavior.
| import { debug, logger } from '../../init' | ||
| import { packageSizeService } from '../../services/packageSize.service' | ||
|
|
||
| const packageSizeLookupMiddleware: Middleware = async (ctx, next) => { |
There was a problem hiding this comment.
Add 2 line description for all middlewares
There was a problem hiding this comment.
Addressed in 3934389. Every affected middleware now has a two-line contract describing what it consumes and what it publishes or controls: request, resolution, size cache, blacklist, cached response, build, errors, exports, and export sizes.
| typeof repository === 'string' ? repository : repository.url ?? '' | ||
| return gitURLParse(rawRepository).toString('https') | ||
| } catch { | ||
| return '' |
There was a problem hiding this comment.
See if using better nullablikity helps
There was a problem hiding this comment.
Addressed in 3934389. Repository normalization and description truncation now return string or null. PackageMetadata uses the same nullable shape across server and client, cache/build results preserve null, and UI components choose their own display fallback only when rendering.
|
|
||
| if (force === 'true') { | ||
| void cache.setPackageSize({ name, version }, body) | ||
| } |
There was a problem hiding this comment.
Are we sure this removal is fine and handled in new api?
There was a problem hiding this comment.
Handled in a6a9037. The removed force-only cache write is now superseded by the single awaited cache write in PackageSizeService.buildPackageSize for every successful composed result. That path is used by both PackageCacheMode.ForceRebuild and the legacy force query adapter, so force rebuilds still replace the persistent package result without duplicating cache ownership in middleware.
There was a problem hiding this comment.
we've added a bunch of new middlewares along with older middlewares. is it possible to combine some?
There was a problem hiding this comment.
Handled in a6a9037. packageSizeCacheMiddleware is deleted. The size-specific lookup, package policy check, successful/failure cache response, cache-only stop, and miss continuation now live in one packageSizeMiddleware, reducing the /api/size chain to request -> error -> package size -> build rate limit -> build. The older generic cachedResponse middleware was narrowed and renamed to jsonCacheResponseMiddleware because it now exists only for the koa-cash export-size path.
| }) | ||
| } | ||
|
|
||
| async function getReadme( |
There was a problem hiding this comment.
Is this dead code? Or did we delete by mistake?
There was a problem hiding this comment.
Confirmed dead rather than accidentally deleted. getPackageDetails previously fetched or constructed readme and returned it, but getCategory destructured only description and keywords, so README content never entered tokenString or getScore. I kept the removal and added an explanatory comment at the fetch boundary; scoring behavior is unchanged while the unused GitHub, GitLab, or Bitbucket request is gone.
| ctx.cacheControl = { | ||
| maxAge: | ||
| force != null | ||
| cacheMode === 'force-rebuild' |
There was a problem hiding this comment.
Use enums for this everywhere instead of strings.
There was a problem hiding this comment.
Handled in a6a9037. PackageCacheMode is now a string enum and every internal comparison, service default, script caller, rebuild caller, and test uses enum members. Object.values(PackageCacheMode) drives boundary validation, leaving the wire values unchanged.
| `BUILD EXPORTS SIZES: ${packageString} built in ${time.toFixed()}s` | ||
| ) | ||
|
|
||
| if (force === 'true') { |
There was a problem hiding this comment.
As far as main service is concerned. please maintain backward compat for just force param for all apis
There was a problem hiding this comment.
Handled in a6a9037. packageRequestMiddleware now retains force as the one legacy query adapter for /api/size, /api/exports, and /api/exports-sizes, immediately mapping its presence to PackageCacheMode.ForceRebuild. New callers continue to use cache=force-rebuild. Supplying force and cache together returns 400 to avoid ambiguous precedence. Live checks returned 200 and Cache-Control max-age=0 for all three force endpoints.
|
|
||
| private async getPackageSizeBuilder(): Promise<PackageSizeBuilder> { | ||
| if (!this.packageSizeBuilder) { | ||
| const { buildService } = await import('../api/BuildService') |
There was a problem hiding this comment.
Handled in a6a9037. The inline dynamic import and mutable builder field are removed. build.middleware.ts statically imports BuildService, composes the PackageSizeBuilder adapter at the HTTP build boundary, and passes it into buildPackageSize. ResultPage SSR imports only PackageSizeService, so its cache-only import graph still cannot initialize BuildService.
There was a problem hiding this comment.
add some explainatory comments in this file (in code) when logic is getting multi-stage
There was a problem hiding this comment.
Handled in a6a9037. PackageSizeService now explains the three non-obvious stages inline: exact-version cache lookup before npm resolution, tag or range resolution followed by the immutable-version cache lookup, and the single post-build persistence path that replaces the old middleware force write.
| priority | ||
| ) | ||
| body = await packageSizeService.buildPackageSize(resolvedPackage, { | ||
| builder: packageSizeBuilder, |
There was a problem hiding this comment.
This reads like overengineering. rename or simplify.
Replace the ad-hoc per-route middleware wiring for /api/size, /api/exports, and /api/exports-sizes with a single linear pipeline: parse -> error boundary -> blocklist guard -> resolve+serve cache -> (miss only) rate limit -> build+store Each endpoint is now a declarative descriptor (which cache, what to build, whether misses are rate limited) rather than its own chain of middleware, so resolution, caching, and response no longer have their logic spread across several files. New server/pipeline/ module owns the flow; the old resolve / cache-serve / build / exports / exports-sizes / koa-cash middlewares and the package-size service are removed. exports-sizes moves onto the same domain cache as size (dropping koa-cash's 304 support), and the blocklist guard now runs before resolution. Also fold in the earlier cleanup pass: shared cache-mode policy and TTL helpers, a single ResolvedPackage constructor, shared formatSentence, and a cache-only SSR lookup that never blocks on npm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1b2696f to
c65fbdc
Compare
The descriptor + factory + type-parameterized pipeline was more machinery
than three endpoints warrant. Spell the three routes out explicitly in
index.ts and give each stage a concrete middleware that reads top to bottom:
size: request -> error -> blocklist -> resolve+serve cache
-> rate limit -> build
exports: request -> error -> blocklist -> resolve -> build
exports-sizes: request -> error -> blocklist -> resolve -> serve cache
-> rate limit -> build
Only small leaf helpers stay shared (cachePolicy, sizeCacheMaxAge, logCache,
serveFailureCache) in results/packageCache.ts. Drops the generic
PackageResultCache, resolveCachedPackage, packageApiPipeline, PackageEndpoint
descriptors, buildAndStore/resolveAndServeCached factories, and the
runCancellableBuild helper (client-disconnect cancellation is inlined). The
size fast path (serve an exact cached version before hitting npm) lives in a
concrete, testable lookupPackageSize; SSR uses readCachedPackageSize.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
PackageRequestfrom HTTP/page parsing through package resolution and size-cache lookupPackageSizeServicefocused on cache lookup, build-result composition, cancellation, and cache writescache-hit | cache-missresult; SSR cannot enqueue buildsPackageCacheModeenum everywhere:cache-first,force-rebuild, orcache-onlyforcequery compatibility across all package APIs while normalizing it immediately toPackageCacheMode.ForceRebuildInformation flow
Koa middleware owns HTTP behavior: blacklist checks, failure caching, rate limits, request priority, error formatting, response headers, and recent-search recording. Services own reusable package resolution and package-size operations.
/api/sizenow has one package-size middleware for resolution plus pre-build cache behavior;/api/exportsand/api/exports-sizesconsume the samePackageRequestandResolvedPackagerather than reparsing query state.Before / after examples
For
/package/react@18.2.0:/api/sizeon every visitFor package request and optional-data code:
requestedPackage,packageRequestPolicy,resolved, andpackageSizeLookupmoved independently through Koa statePackageRequestand oneresolvedPackage; the typed cache result remains local topackageSizeMiddlewareforceBuild -> cachePolicy -> bypass/readtranslationsPackageCacheModeenum passed unchanged through every layerpeepandpeekhad endpoint-specific meaningscache=cache-only; every cache miss is HTTP 404force=truewas interpreted independently by each endpointforceremains supported on size, exports, and export-size APIs, but the request boundary maps it once toPackageCacheMode.ForceRebuild; new callers usecache=force-rebuildcreatePackageApiPath, which owns endpoint names, encoding, cache modes, recording, and limits""nullonce, then remains nullable across shared server/client domain typesversion ?? ''was passed to semver in three middlewaresgetExactRequestedVersionreturns `stringpackageSizeMiddlewareowns the complete pre-build size flow; genericjsonCacheResponseMiddlewareremains only for koa-cash export-size entriesPackageSizeServicedynamically imported and retained its build adapterBuildServiceCache behavior
cache=cache-first(default): return a cache hit, otherwise continue to the guarded build flow.cache=force-rebuild: bypass cache reads, rebuild, returnCache-Control: max-age=0, and replace the persistent result.cache=cache-only: return a cache hit or HTTP 404; never build.BuildServiceis statically imported only at the build middleware composition boundary, so SSR cache lookup cannot initialize build infrastructure.server/config.ts, beside API cache durations.Verification
tsc --noEmitplaywright-clilive page flow:/package/react@18.2.0settles to the package-specific title and renders package description, sizes, dependency count, repository, composition, and exports statecache=refreshvalue returns HTTP 400 with the accepted contractcache=cache-firstreturns HTTP 200 withCache-Control: max-age=86400cache=cache-firstreturns HTTP 200 withCache-Control: max-age=30cache=force-rebuildreturns HTTP 200 withCache-Control: max-age=0cache=cache-onlyreturns HTTP 404 on a local persistent-cache missforce=truereturns HTTP 200 withCache-Control: max-age=0on size, exports, and export-size APIsforceandcachereturns HTTP 400 instead of applying ambiguous precedence