arcjet-js icon indicating copy to clipboard operation
arcjet-js copied to clipboard

feat(arcjet): Segment cache entries by rule

Open blaine-arcjet opened this issue 6 months ago • 3 comments

This changes the way caching is done in the SDK. Instead of a single top-level block cache for a decision, we will now cache each rule result with a DENY conclusion. These are cached on a combination of ruleId and (per rule) fingerprint. If a decision is made by the Decide service, it will return the ruleId and fingerprint to store in the cache.

This means that all rules will now have a local component, even if it is just looking up a value in the cache.

Depends upon #4209

blaine-arcjet avatar May 21 '25 00:05 blaine-arcjet

😎 Merged successfully - details.

trunk-io[bot] avatar May 21 '25 00:05 trunk-io[bot]

This will get rebased on top of #4204

blaine-arcjet avatar May 22 '25 23:05 blaine-arcjet

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​arcjet/​cache@​1.0.0-beta.7100100100100100

View full report

socket-security[bot] avatar May 23 '25 23:05 socket-security[bot]

Been waiting on this pr to be merged. Any updates??

developedbynick avatar May 25 '25 16:05 developedbynick

We’re almost there @developedbynick ! We’re aiming for the next week or so.

davidmytton avatar May 25 '25 16:05 davidmytton

using the cache would need to put any calls that could throw in try / catch blocks

We already try/catch around rule execution, which is where my explanation of "Errored" results comes from. We need to do everything in our power to not produce errors for users (see below).

prefer to have invariants enforced by clear signals that the contract has been violated and that some part of the runtime may be in an error or undefined state.

I think I'd rather just accept empty strings than calling these invariants and throwing. This is a footgun for users but I really don't want to throw in this code.

In this case, that could also be achieved by defining a type guard on the get / set methods to ensure that the namespaces and other values that must be set are so marked in the types.

As I've documented in internal documentation, we need to treat usage of TypeScript as raw JavaScript so we can't rely exclusively on a TypeScript type to ensure anything here.

blaine-arcjet avatar May 27 '25 20:05 blaine-arcjet

In this case, that could also be achieved by defining a type guard on the get / set methods to ensure that the namespaces and other values that must be set are so marked in the types.

As I've documented in internal documentation, we need to treat usage of TypeScript as raw JavaScript so we can't rely exclusively on a TypeScript type to ensure anything here.

This is a good requirement for code that is user-facing, but users do not access the MemoryCache instances directly themselves, correct?

The distinction I'm drawing here is between actual exceptions and runtime errors. For runtime errors, as embedded code writers, we should be following the Robustness Principle (which is more or less what the existing documents stipulate). I'm arguing that the the "must nots" in the code comments should be treated as preconditions / invariants in the design-by-contract sense, and that violations of those invariants should be treated as actual exceptions, which is to say situations in which the system is in an inconsistent state. I want to ensure that we catch all possible instances of this during development, using whatever means are available to us to enforce our API contracts. I don't want this to bubble up to end users. As long as we're confident in the strategies we're using to achieve that goal, I am agnostic as to what strategies we choose to use.

To me, putting type guards on components that are used internally is one way to do that, because, as per the guidelines you've written, end users are running transpiled "raw" JS.

arcjet-rei avatar May 27 '25 20:05 arcjet-rei

This is a good requirement for code that is user-facing, but users do not access the MemoryCache instances directly themselves, correct?

They can, if they Write a Custom Rule.

The distinction I'm drawing here is between actual exceptions and runtime errors.

This is a good distinction. With the implementation I ended up using for the namespaced MemoryCache, there's nothing actually stopping users from using empty namespaces or keys (it's just another value in a Map) so I've removed that restriction and instead just throw if non-strings are provided, which are also encoded in TypeScript.

blaine-arcjet avatar May 27 '25 21:05 blaine-arcjet

Thanks Guys. I screamed out "yes" when I saw the merge😂😂

developedbynick avatar May 27 '25 22:05 developedbynick

@developedbynick expect an SDK release by the end of the week 🚀

davidmytton avatar May 28 '25 06:05 davidmytton

Thanks David!

developedbynick avatar May 29 '25 03:05 developedbynick