Repository navigation
chore: use Response.json in edge functions, drop two dead declarations - #36
Conversation
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (5)
💤 Files with no reviewable changes (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe changes standardize JSON response construction in three Supabase Edge Functions, remove an unused AI type, and remove the shared ChangesEdge Function cleanup
Estimated code review effort: 2 (Simple) | ~10 minutes Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
1dfbacb to
9b78279
Compare
|
Interim independent review while CodeRabbit is rate limited (Free plan allows 1 review/hour). Reviewer: Claude Opus 4.5, run locally against CodeRabbit will still review this PR when its hourly window resets; this does not replace it. |
9b78279 to
91353eb
Compare
|
Independent review, second pass, model upgraded to Claude Opus 5 (the earlier comment on this PR used Opus 4.5). Run locally against Every should-fix finding below has been addressed in the current head. Full output: |
Response.json sets the JSON content type itself, so three hand-rolled wrappers that stringified a body and spread in a Content-Type header can delegate to it. Call sites are unchanged. Two behavioural differences exist in principle, and neither is reachable here: - Response.json(undefined) throws a TypeError, where the old form produced an empty body. Both remaining wrappers are typed `unknown`, so this was worth checking: every one of the 12 call sites in import-source and the 3 in announcements passes an object literal, never a possibly-undefined value. - Response.json defers to a caller-supplied content-type, where the old form always overrode it. No getCorsHeaders in this repo returns one. Verified under Deno 2.9.6 that the content type is exactly "application/json" with no charset parameter, matching the old form byte for byte. Also deletes two declarations with no references: - _shared/cors.ts, a wildcard Access-Control-Allow-Origin header set with no importer, no import-map alias, and no config.toml entry. Every function defines its own getCorsHeaders origin allowlist instead, so this file was a misleading leftover. _shared/native-cors.test.ts is unaffected: it iterates a hardcoded list of function names and reads ../<name>/index.ts as text, so it never enumerates _shared or asserts on this file. - the local Source type in ai/index.ts, never exported and never used. Verified on main fe679fd, from a clean npm ci: docs:check, lint, typecheck, 956 unit tests, 121 edge tests, deno check on the 4 touched function entrypoints, build, and 126 Chromium e2e tests pass.
91353eb to
4836ada
Compare
Third PR from the over-engineering audit. Scoped to
supabase/functions/. Independent of #34, #35, #37, and #38: no file overlap, based directly on currentmain.Response.json
Three functions hand-rolled the same wrapper: stringify a body, spread CORS headers, add
Content-Type: application/json.Response.jsondoes the content type itself, so each wrapper now delegates. Call sites are untouched.ai-stream/index.ts—jsonResponseimport-source/index.ts—jsonResponseannouncements/index.ts—jsonWhere this is not a drop-in, and why it is safe here
Response.jsondiffers from the old form in two ways. Both were checked rather than assumed.1.
Response.json(undefined)throws.The old form returned an empty body; the new one would be an unhandled 500. Both remaining wrappers are typed
unknown, so this matters. Every call site passes an object literal:No call site can pass
undefined.ai-stream's wrapper is typedRecord<string, unknown>and cannot.2. Header precedence. The old wrappers spread CORS headers first and then forced
Content-Type, overriding any caller value.Response.jsondefers to a caller-suppliedcontent-type. Unreachable here: nogetCorsHeadersin this repo returns one.3. Content type is identical. Verified rather than assumed:
An earlier revision of this description claimed
Response.jsonappends;charset=UTF-8and blamed Node/undici. That was simply wrong: the WHATWG spec sets exactlyapplication/json, and Node 22 agrees (node -e 'console.log(Response.json({}).headers.get("content-type"))'→application/json). There is no charset difference on any platform.Dead declarations removed
_shared/cors.ts— acorsHeadersobject withAccess-Control-Allow-Origin: "*". No importer, no string-built specifier, no alias insupabase/functions/deno.json, no entry insupabase/config.toml. Every function defines its owngetCorsHeaders(origin)with an explicit allowlist, so this file read like the project's CORS policy while being wired to nothing. Deleting it removes the trap; it changes no policy._shared/native-cors.test.tsis unaffected, and the mechanism matters: it iterates a hardcoded list of 10 function names and reads../<name>/index.tsas text, asserting each contains"capacitor://localhost". It performs no directory walk oversupabase/functions/**, has no file-count assertion, and makes no "no wildcard anywhere" claim. Deleting a file in_sharedcannot change what it enumerates.type Sourceinai/index.ts— never exported, never referenced.deno checkon that entrypoint proves it.Rejected during this pass
Two findings I tried and backed out, both because the "simpler" form was wrong:
callbackOrigin(url)→url.originincreate-checkout/checkout-security.ts. Looks like a textbook stdlib swap. It is not: for the iOS app'scapacitor://localhostscheme,url.originreturns the string"null"(opaque origin), while${url.protocol}//${url.host}returnscapacitor://localhost. The hand-rolled version is deliberately correct for non-special schemes.src/test/checkout-security.test.tscaught this immediately.narrowSparringDatabase(database: object)inai/index.ts. Reads like a pointless one-line cast wrapper. Taking the parameter asobjectis load-bearing: it widens the type and breaks a deep instantiation chain. CastingsparringSupabase as SparringDatabasedirectly failsdeno checkwith TS2589, "Type instantiation is excessively deep and possibly infinite."Reported, not applied
getCorsHeadersis duplicated across 11 edge functions, 6 of them byte-identical. Consolidating it into_sharedwould be a real cut, but it edits the CORS trust boundary of every deployed function and would require redeploying all 11. That belongs in its own reviewed change, not a cleanup PR.Verification
npm run lintnpm run typechecknpm test -- --coveragenpm run test:edgedeno checkon the 4 touched function entrypointsnpm run buildnpm run test:e2e(The diff touches 4 function entrypoints plus the deleted
_shared/cors.ts. An earlier revision of this table said "all 5 touched functions", which double-counted the deleted file as a function.)Not run: the staging suite, which needs protected credentials, and the DB gate, since there are no migrations here. These functions are not deployed by this PR.
Summary by CodeRabbit