react-native-mmkv icon indicating copy to clipboard operation
react-native-mmkv copied to clipboard

feat: typed storages support

Open ipakhomov opened this issue 8 months ago • 12 comments

Changes for https://github.com/mrousavy/react-native-mmkv/issues/815

Changes

  • Added ability to pass an optional generic type when declaring MMKV instance to describe storage keys and values for better type-safety
  • Updated hooks to support typed storage instances
  • Added tests for types using tsd
  • Updated Contributing guide a bit

Usage

  1. Define Your Storage Schema
type StorageValues = {
  userToken: string;
  sessionTTL: number;
  isPremiumUser: boolean;
  cachedData: ArrayBuffer;
};
  1. Create a Typed MMKV Instance:
const storage = new MMKV<StorageValues>();
  1. Store & Retrieve Values with Type Safety
// ✅ Correct Usage
storage.set('userToken', 'abc123');
const token = storage.getString('userToken'); // string | undefined

storage.set('sessionTTL', 3600);
const ttl = storage.getNumber('sessionTTL'); // number | undefined

storage.set('isPremiumUser', true);
const isPremium = storage.getBoolean('isPremiumUser'); // boolean | undefined
  1. Type Errors Prevent Mistakes:
// ❌ TypeScript will prevent incorrect operations:
storage.set('userToken', 123); // Error: Expected string value
storage.getBoolean('sessionTTL'); // Error: Expected boolean key
  1. Hooks were also updated:
const [userToken, setUserToken] = useMMKVString('userToken', storage);
setUserToken('newToken123'); // ✅ Type-safe
setUserToken(false) //  ❌ TypeScript will report this error

More examples of correct and incorrect use-cases can be found in types tests file package/src/__types_tests__/mmkv.test-d.ts

ipakhomov avatar Mar 31 '25 16:03 ipakhomov

@mrousavy I look forward to your feedback! Please let me know if I can update the Readme.

ipakhomov avatar Mar 31 '25 16:03 ipakhomov

I appreciate the PR, but you added a package-lock.json which clearly can't be right. MMKV uses Bun or Yarn.

Also, I don't think this should be patched into MMKV.ts, this should be an added interface ontop.

mrousavy avatar Apr 01 '25 07:04 mrousavy

@mrousavy good catch about package-lock.json - removed, thanks! I accidentally added it because of mention of npm in Contributing guide (fixed in this PR as well).

As for this:

I don't think this should be patched into MMKV.ts, this should be an added interface ontop.

I'm not quite sure how the same functionality can be achieved without touching package/src/MMKV.ts file. Could you please elaborate?

ipakhomov avatar Apr 01 '25 10:04 ipakhomov

You just provide a thin wrapper that only does types. Your implementation doesn't do anything else, no JS logic as far as I can see.

So users would call new TypedMMKV<...> instead of new MMKV then

mrousavy avatar Apr 01 '25 10:04 mrousavy

@mrousavy yes, my implementation does not change JS logic. But it looked to me that introducing a new class is a bit redundant comparing to adding an optional generic type to the existing one (MMKV). Usage isn't changed for the existing users, and the class became more type-safe. Thoughts?

PS: Also, I moved types tests to a separate directory and added them to the code-checks pipeline. Could you please check?

ipakhomov avatar Apr 01 '25 12:04 ipakhomov

Looks reasonable to me, existing users do not need to change anything new MMKV()

Using more specific types is opt-in via generics new MMKV<T>()

MarkusWendorf avatar Apr 15 '25 09:04 MarkusWendorf

@mrousavy just a friendly ping here 🙂

ipakhomov avatar Apr 28 '25 16:04 ipakhomov

@mrousavy Looks good to me too. Please merge ASAP, need support for TS. Thanks

devuco avatar Apr 29 '25 17:04 devuco

@mrousavy just a kindly reminder about this PR :)

ipakhomov avatar Jun 04 '25 20:06 ipakhomov

I gotta be honest I'm still pretty torn between either merging this or having the user be responsible for this. There's a few cases where the type safety just isn't guaranteed. After all, it's a storage - it can be anything if written to from elsewhere (e.g. from an untyped MMKV storage, from a previous version where the user specified different types, or from native).

I feel like this just increases the maintenance layer for me - people will find bugs in the type system here and complain in issues.

mrousavy avatar Jun 05 '25 08:06 mrousavy

If it helps, I implemented something pretty similar for storage in the apps I've been working on, but it uses zod validation to enforce the storage values at runtime + a primitive migrations system, to address the whole type 'assumption' problem.

  • There is the performance tradeoff of doing runtime validation of course
  • The migration system is a direct result of the issue that mrousavy was talking about where the stored value shape changed across different app versions

Probably not able to share what we're using as it's a bit tightly coupled with our stuff, but I'll maybe look into throwing together a thin wrapping library (since this seems to be beyond the scope of this project) using https://github.com/standard-schema/standard-schema given the interest is there.

NathanP-7West avatar Jun 06 '25 07:06 NathanP-7West

@mrousavy Thank you for taking a look! Indeed, the changes in this PR make supporting the library a bit more complex due to the tightened types. However, I believe it's worth it, as the library becomes more type-safe. We could consider releasing this as a beta, and I will be happy to help you resolve any TypeScript-related issues.

Thoughts?

@NathanP-7West Thank you for your comment! The approach you suggested can work, but I'm trying to avoid runtime validation and instead provide built-in type checks within the library itself. Please let me know if I misunderstood your idea.

ipakhomov avatar Jun 09 '25 15:06 ipakhomov

@mrousavy In the last two commits, I slightly tightened some type checks for default (non-typed) storage and added a few more tests to demonstrate that non-typed storage types aren't affected and continue to work correctly. So all comments seems to be resolved.

Looking forward to your feedback!

ipakhomov avatar Jun 22 '25 10:06 ipakhomov

@mrousavy just a kind reminder that this PR waits for your attention

ipakhomov avatar Jul 30 '25 04:07 ipakhomov

Like everyone here, I value a well-designed type system. But for this one, I have to agree with @mrousavy that the TypeScript interface proposed here, while interesting and well thought out, isn't a reliable guarantee of type safety:

There's a few cases where the type safety just isn't guaranteed. After all, it's a storage - it can be anything if written to from elsewhere (e.g. from an untyped MMKV storage, from a previous version where the user specified different types, or from native). -- https://github.com/mrousavy/react-native-mmkv/pull/826#issuecomment-2943157435 (emphasis added)

Due to situations like these, even if this PR was merged, I would still need to maintain my own validation layer around MMKV, at which point I might as well continue typing my validation layer instead of MMKV.

thehale avatar Aug 28 '25 04:08 thehale

I agree, yea. I don't think this should be MMKV's duty. Type validation is a whole other project.

I really appreciate everyone's thoughts and time that went into this, and especially from you @ipakhomov for creating the PR, but I'm going to close this as this is out of scope for MMKV itself. Maybe we can create a typed storage wrapper ontop of this as a separate project?

mrousavy avatar Aug 29 '25 14:08 mrousavy

@mrousavy Thank you for the feedback. I’ll consider creating a separate wrapper package based on this.

ipakhomov avatar Aug 29 '25 14:08 ipakhomov