fix: S3Client singleton and R2 error logging#108
Conversation
…Handler → handleEditResolution) Closes willchen96#94
bmersereau
left a comment
There was a problem hiding this comment.
PR Review
Summary
Introduces an S3Client singleton (lazy-initialised on first use) to avoid allocating a new client on every R2 operation, and adds console.error logging to the silent catch blocks in downloadFile and getSignedUrl. Both changes are correct and operationally valuable. editResolutionLogging.test.ts is not present. documents.ts is unchanged from origin/main.
Findings
- [severity:low] The
_clientsingleton is module-level, so it is initialised with whateverR2_*env vars are present at first use. If env vars are set after module load (e.g., via a secrets loader), the client will be created with empty credentials. This is an inherent trade-off of singletons and is acceptable here since the credentials are typically set at process start, but it should be documented. - [severity:low]
vitest.config.tsdoes not setisolate: true(unlike PR #106/#107). The storage tests usevi.resetModules()inline before each dynamicimport, which achieves per-test isolation manually. This works (tests pass), but explicitly settingisolate: truewould be more defensive and consistent with the rest of the PR series. - [severity:nit] The
.gitignoreaddsCLAUDE.md,.current-issue.md,.implementation-plan.md, and.implement-config. SinceCLAUDE.mdis not tracked inorigin/mainthese entries are appropriate. Confirm intentional inclusion — ifCLAUDE.mdshould eventually be committed as project documentation (as it is in the local working tree ofmikeoss), removing it from.gitignorelater would require a separate PR. - [severity:nit]
storage.test.tsdoes not have abeforeEachorafterEachfor env var cleanup. Eachitcallsvi.resetModules()inline, which is functional but inconsistent. A sharedbeforeEachwould be cleaner.
Verdict
Approve with nits
What I verified
- Tests: pass (3/3)
- vitest.config.ts has
includefilter: pass ("src/**/__tests__/**/*.test.ts") - package.json has
"test": "vitest run": pass editResolutionLogging.test.tsNOT present: passdocuments.tsunchanged fromorigin/main: pass_clientsingleton and error logging in catch blocks: pass
bmersereau
left a comment
There was a problem hiding this comment.
PR Review
Summary
Hoists S3Client to a module-level lazy singleton and adds console.error logging to the catch blocks in downloadFile and getSignedUrl. Three storage tests verify the module behaves correctly when R2 env vars are absent. editResolutionLogging.test.ts is absent and documents.ts is unchanged from origin/main (issues #115 and #116 resolved).
Findings
- [severity:praise] Singleton pattern is correct — lazy init on first call, guarded by null check
- [severity:praise] Error logging includes both
keyand the error message for actionable diagnostics - [severity:minor] The singleton is module-scoped, so if R2 credentials are rotated without a server restart, the old client persists. This is expected behavior for a singleton — acceptable trade-off. A comment noting this would help operators
- [severity:nit]
storageEnabledtest restores env vars but the cleanup is synchronous; if the import throws, cleanup is skipped. Not a real risk here since the delete/restore is fast, but atry/finallywould be cleaner
Specific checks
-
"test": "vitest run"in package.json ✓ -
editResolutionLogging.test.tsabsent ✓ (issue #116) -
documents.tsunchanged from origin/main ✓ (issue #115) - Tests pass: 3/3 ✓
Verdict
Approve with nits — ship it.
Summary
S3Clientto a module-level singleton (lazy-initialised on first use) — prevents a new client being allocated on every R2 operation and enables HTTP keep-alive connection reuseconsole.errorlogging to thecatchblocks indownloadFileandgetSignedUrlso R2 outages appear in server logs rather than silently returning nulleditResolutionLogging.test.tsthat was copied from an unrelated branch and caused test failures on this branchCloses #101
Closes #102
Closes #115
Closes #116
Closes #125
Changes
backend/src/lib/storage.ts— singleton_client+ error logging in catch blocksbackend/src/lib/__tests__/storage.test.ts— basic storage module testsbackend/vitest.config.ts—isolate: trueadded for consistent module isolationbackend/package.json—"test": "vitest run"script addedTest plan