feat(protect): template typecheck + NestJS adapter + rule-sync hardening#85
Merged
Conversation
Templates are excluded from connect's tsconfig (they import framework + JSON files that only exist after scaffolding), so a template type bug ships silently — a broken install has slipped out this way before. scripts/typecheck-templates.mjs assembles each template in a scratch dir with the real @patchstack/connect/protect declarations, a stub rules JSON, and loose framework-type shims, then runs `tsc --noEmit`. Chained into `npm run typecheck` so CI + prepublish cover it. Verified it fails on the exact bug class it targets (a string-typed `mode`) and passes on all 6 current templates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NestJS on the default Express platform accepts `app.use(...)` middleware in the bootstrap, so the guard wires the same way Express/Fastify do — no Nest module/decorators needed. Anchored on `NestFactory.create(...)`, reusing the framework-agnostic guard. Extracted the register-into-app pattern (three adapters now share it) into install/register.ts (wireRegister/verifyRegister + RegisterSpec) and the app-instance finder into install/find-app.ts; Express + Fastify are now thin specs on top. hasDependency() moved to util.ts. No behavior change for the existing adapters (their tests are unchanged and green). Registered most-specific-first (… → astro → nestjs → fastify → express → generic). AGENT-INSTALL lists NestJS. +2 tests, 458 total, typecheck (incl. template check) + build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…able cache
Harden the per-site rule-delivery client:
- Conditional fetch: persist the response ETag alongside the cached bundle (envelope
{ bundle, etag }) and send If-None-Match on the next fetch; a 304 reuses the cached bundle with
no re-download. Fully dormant until the rules API emits an ETag — with no ETag the client behaves
exactly as before (a full fetch each refresh). Defensive against Responses without a headers
object (some test mocks / non-standard fetch shims).
- Jitter: randomly shorten the effective cache TTL by up to 10% so many long-lived clients don't
revalidate in lockstep. Downward-only, so "past TTL ⇒ refetch" still holds.
- Pluggable cache: new `ruleCache` option ({ read, write }, sync or async) for runtimes without a
filesystem (Workers/Deno) to persist last-known-good in their own store; disk stays the default.
The disk read accepts a legacy bare-bundle file for backward compatibility.
Applied to both the Pulse (siteUuid) and token clients; resolveRules threads the prior etag and
handles 304. +4 tests (rule-sync), 462 total, typecheck (incl. template check) + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Well-factored abstractions streamline adapter wiring and improve rules client caching. 🎯 Quality: 73% Good · 📦 Size: Extra Large — strongly consider breaking this down 🛡️ Standards: no pre-flight fit check ran for this change — wire 📈 This month: Your 37th PR — above team average · Averaging Good |
Contributor
Author
|
/review |
mariojgt
approved these changes
Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three independent hardening items, one clean commit each (DNS-rebinding resistance is a separate follow-up PR).
1. Template-typecheck harness (
test(protect))Scaffolded templates are excluded from connect's tsconfig, so a template type bug ships silently — a broken install has slipped out this way before.
scripts/typecheck-templates.mjsassembles each template in a scratch dir with the real@patchstack/connect/protectdeclarations, a stub rules JSON, and loose framework-type shims, then runstsc --noEmit. Chained intonpm run typecheck(so CI + prepublish cover it). Verified it fails on the exact bug class it targets (astring-typedmode) and passes on all 6 templates.2. NestJS adapter (
feat(protect))NestJS on the default Express platform accepts
app.use(...)in the bootstrap, so it wires the same way Express/Fastify do — anchored onNestFactory.create(...), reusing the generic guard (no Nest module/decorators). Extracted the shared register-into-app pattern (three adapters now use it) intoinstall/register.ts+install/find-app.ts; Express/Fastify are now thin specs (behavior unchanged, their tests untouched and green).3. Rule-sync hardening (
feat(protect)){ bundle, etag }), sendIf-None-Match, reuse the bundle on304. Dormant until the rules API emits an ETag — with no ETag it behaves exactly as today (full fetch). Defensive against Responses without aheadersobject.ruleCacheoption for runtimes without a filesystem (Workers/Deno); disk stays default; legacy bare-bundle cache files still read.+10 tests. 462 pass, typecheck (incl. the new template check) + build clean.