Skip to content

refactor: migrate to ocache#74

Merged
9romise merged 3 commits intomainfrom
ocache
Mar 11, 2026
Merged

refactor: migrate to ocache#74
9romise merged 3 commits intomainfrom
ocache

Conversation

@9romise
Copy link
Member

@9romise 9romise commented Mar 10, 2026

move away from manual memoize function, migrate to ocache

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Replaces the custom memoize utility with ocache's defineCachedFunction across the codebase, removes src/utils/memoize.ts and its tests, and updates caching usage sites (package, replacement, vulnerability APIs and other helpers) to use ocache with CACHE_MAX_AGE_ONE_DAY. Renames CACHE_TTL_ONE_DAY → CACHE_MAX_AGE_ONE_DAY and changes units from milliseconds to seconds. Moves API modules from src/utils/api/* to src/api/* and updates imports. Refactors workspace caching to per-instance invalidation (invalidateDependencyInfo / invalidatedFolder) instead of direct cache deletion.

Possibly related PRs

  • npmx-dev/vscode-npmx PR 68: Modifies workspace context and per-instance cache invalidation logic, directly related to the workspace invalidation changes in this PR.
  • npmx-dev/vscode-npmx PR 45: Touches memoize utility/tests; related to the removal of src/utils/memoize.ts and deletion of its test suite.
  • npmx-dev/vscode-npmx PR 49: Changes vulnerability diagnostic usage and getVulnerability signature, matching import/signature updates for vulnerability handling in this PR.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description accurately describes the changeset: migrating away from a manual memoize function to use ocache, which is reflected throughout the modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ocache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/api/vulnerability.ts (1)

92-104: ⚠️ Potential issue | 🟠 Major

Map 404 responses to no-result instead of throwing.

The ofetch library throws on non-2xx status codes by default. A 404 response from the vulnerabilities endpoint will currently abort with an error instead of allowing a no-result path, which is why the test "should not flag when API returns 404" is failing. Handle 404 responses explicitly before the error propagates, returning null or a suitable no-result value to let checkVulnerability handle the absence of data gracefully. Rethrow only unexpected error responses.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4c9e422a-fb8e-4ff5-b18e-86d58ab05600

📥 Commits

Reviewing files that changed from the base of the PR and between e8eb38d and d76cb94.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • package.json
  • pnpm-workspace.yaml
  • src/composables/workspace-context.ts
  • src/constants.ts
  • src/core/workspace.ts
  • src/providers/diagnostics/rules/vulnerability.ts
  • src/utils/api/package.ts
  • src/utils/api/replacement.ts
  • src/utils/api/vulnerability.ts
  • src/utils/memoize.ts
  • tests/utils/memoize.test.ts
  • tsdown.config.ts
💤 Files with no reviewable changes (2)
  • tests/utils/memoize.test.ts
  • src/utils/memoize.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/workspace.ts (1)

39-54: ⚠️ Potential issue | 🟠 Major

Invalidate manifest caches when workspace catalogue data changes.

loadWorkspace() refreshes #catalogs, but the new invalidation path only marks the exact URI passed to invalidateDependencyInfo(). When src/composables/workspace-context.ts:17-30 invalidates a workspace file and then reloads the workspace, any cached loadPackageManifestInfo(uri) entries still keep the old catalogue-based resolutions until each manifest is invalidated separately.

Possible fix
 class WorkspaceContext {
+  `#workspaceGeneration` = 0
   folder: WorkspaceFolder
   packageManager: PackageManager = 'npm'
   `#catalogs`?: PromiseWithResolvers<CatalogsInfo | undefined>
   `#invalidatedPaths` = new Set<string>()

   async loadWorkspace() {
+    this.#workspaceGeneration++
     this.#catalogs = undefined
     this.packageManager = await detectPackageManager(this.folder)
     // ...
   }

   `#cacheOptions`: CacheOptions<any, [Uri]> = {
-    getKey: (uri) => uri.path,
+    getKey: (uri) => `${this.#workspaceGeneration}:${uri.path}`,
     maxAge: 0,
   }
 }

Also applies to: 57-60, 62-65, 120-123, 148-150


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ceab782a-0e95-4b18-b7e0-84cc8479583c

📥 Commits

Reviewing files that changed from the base of the PR and between d76cb94 and 118b863.

📒 Files selected for processing (13)
  • src/api/package.ts
  • src/api/replacement.ts
  • src/api/vulnerability.ts
  • src/core/workspace.ts
  • src/providers/diagnostics/rules/replacement.ts
  • src/providers/diagnostics/rules/upgrade.ts
  • src/providers/diagnostics/rules/vulnerability.ts
  • src/types/context.ts
  • src/utils/package.ts
  • tests/diagnostics/context.ts
  • tests/diagnostics/upgrade.test.ts
  • tests/utils/package.test.ts
  • tsconfig.json
✅ Files skipped from review due to trivial changes (1)
  • tests/utils/package.test.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/api/vulnerability.ts (1)

96-98: ⚠️ Potential issue | 🟠 Major

Do not cache non-2xx responses as vulnerability data.

ignoreResponseError: true removes the status gate here, and ?? null only handles null/undefined. A 404/5xx payload can therefore be cached as if it were a valid VulnerabilityTreeResult, which keeps bad data around for the full TTL. Only convert an expected 404 to null; let the other failures bypass the cache.

Proposed fix
-import { ofetch } from 'ofetch'
+import { FetchError, ofetch } from 'ofetch'
...
-  const result = await ofetch(`${NPMX_DEV_API}/registry/vulnerabilities/${encodedName}/v/${version}`, {
-    ignoreResponseError: true,
-  }) ?? null
-  logger.info(`[vulnerability] fetched for ${name}`)
-
-  return result
+  try {
+    const result = await ofetch<VulnerabilityTreeResult>(`${NPMX_DEV_API}/registry/vulnerabilities/${encodedName}/v/${version}`)
+    logger.info(`[vulnerability] fetched for ${name}`)
+    return result
+  }
+  catch (error) {
+    if (error instanceof FetchError && error.statusCode === 404)
+      return null
+    throw error
+  }
In unjs/ofetch, what does `ignoreResponseError: true` do for HTTP 404/500 responses returned by `ofetch`? Does it resolve with the parsed response body rather than throwing?

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 32dbdc64-485a-4e27-aeae-52fa5c6fb988

📥 Commits

Reviewing files that changed from the base of the PR and between 118b863 and cdd76d3.

📒 Files selected for processing (3)
  • src/api/replacement.ts
  • src/api/vulnerability.ts
  • src/core/workspace.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/api/replacement.ts

Comment on lines +57 to 68
#cacheOptions: CacheOptions<any, [Uri]> = {
getKey: (uri) => uri.path,
ttl: false,
maxSize: Number.POSITIVE_INFINITY,
fallbackToCachedOnError: false,
maxAge: 0,
swr: false,
staleMaxAge: 0,
shouldInvalidateCache: (uri) => this.#invalidatedPaths.delete(uri.path),
}

invalidateDependencyInfo(uri: Uri) {
const path = uri.path
this.#invalidatedPaths.add(path)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Invalidate manifest caches when the workspace catalogue changes.

loadPackageManifestInfo() bakes this.#catalogs!.promise into each cached manifest result, but invalidateDependencyInfo() only expires the exact URI that changed. If the workspace file changes, the catalogue is reloaded while existing manifest entries still serve dependency resolutions computed from the old catalogue.

Possible fix
 class WorkspaceContext {
   folder: WorkspaceFolder
   packageManager: PackageManager = 'npm'
   `#catalogs`?: PromiseWithResolvers<CatalogsInfo | undefined>
   `#invalidatedPaths` = new Set<string>()
+  `#manifestCacheVersion` = 0
...
   invalidateDependencyInfo(uri: Uri) {
     const path = uri.path
     this.#invalidatedPaths.add(path)
+    if (isWorkspaceFilePath(path))
+      this.#manifestCacheVersion++
   }
...
   loadPackageManifestInfo = defineCachedFunction<
     WithResolvedDependencyInfo<PackageManifestInfo> | undefined,
     [Uri]
   >(async (uri) => {
     const path = uri.path
     if (!isPackageManifestPath(path))
       return
...
     return {
       ...info,
       dependencies: info.dependencies.map((dep) => this.#createResolvedDependencyInfo(dep, catalogs)),
     }
-  }, this.#cacheOptions)
+  }, {
+    ...this.#cacheOptions,
+    getKey: (uri) => `${this.#manifestCacheVersion}:${uri.path}`,
+  })

Also applies to: 97-123

@9romise 9romise merged commit 09c0eac into main Mar 11, 2026
11 checks passed
@9romise 9romise deleted the ocache branch March 11, 2026 09:36
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.

1 participant