@multiplatform.one/store 6.7.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,46 +24,90 @@ var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
24
24
  }), mod);
25
25
  var index_native_exports = {};
26
26
  __export(index_native_exports, {
27
+ logNativeStorageResolution: () => logNativeStorageResolution,
28
+ resolveNativeStorage: () => resolveNativeStorage,
27
29
  storage: () => storage
28
30
  });
29
31
  module.exports = __toCommonJS(index_native_exports);
30
- function createStorage() {
32
+ function _instanceof(left, right) {
33
+ "@swc/helpers - instanceof";
34
+
35
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
36
+ return !!right[Symbol.hasInstance](left);
37
+ } else {
38
+ return left instanceof right;
39
+ }
40
+ }
41
+ var defaultLoaders = {
42
+ // Literal require(...) calls on purpose: metro resolves them natively and
43
+ // vxrn/rolldown rewrites literal requires into bundled module inits (a
44
+ // passed-around `require` reference would survive as a bare identifier and
45
+ // crash). Lazy so a genuinely missing native module throws HERE — inside
46
+ // the try/catch — instead of at bundle evaluation.
47
+ loadMMKV: function loadMMKV() {
48
+ return require("react-native-mmkv");
49
+ },
50
+ loadAsyncStorage: function loadAsyncStorage() {
51
+ return require("@react-native-async-storage/async-storage");
52
+ }
53
+ };
54
+ function resolveNativeStorage() {
55
+ var loaders = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : defaultLoaders;
56
+ var errors = {};
31
57
  try {
32
- var {
33
- MMKV
34
- } = require("react-native-mmkv");
35
- var mmkv = new MMKV();
58
+ var mmkvModule = loaders.loadMMKV();
59
+ if (typeof (mmkvModule === null || mmkvModule === void 0 ? void 0 : mmkvModule.createMMKV) !== "function") {
60
+ throw new Error("no createMMKV() export (react-native-mmkv v4 API expected)");
61
+ }
62
+ var mmkv = mmkvModule.createMMKV();
36
63
  return {
37
- getItem: function getItem(name) {
38
- var _mmkv_getString;
39
- return (_mmkv_getString = mmkv.getString(name)) !== null && _mmkv_getString !== void 0 ? _mmkv_getString : null;
40
- },
41
- setItem: function setItem(name, value) {
42
- return mmkv.set(name, value);
43
- },
44
- removeItem: function removeItem(name) {
45
- return mmkv.delete(name);
64
+ backend: "mmkv",
65
+ errors,
66
+ storage: {
67
+ getItem: function getItem(name) {
68
+ var _mmkv_getString;
69
+ return (_mmkv_getString = mmkv.getString(name)) !== null && _mmkv_getString !== void 0 ? _mmkv_getString : null;
70
+ },
71
+ setItem: function setItem(name, value) {
72
+ mmkv.set(name, value);
73
+ },
74
+ removeItem: function removeItem(name) {
75
+ mmkv.remove(name);
76
+ }
46
77
  }
47
78
  };
48
- } catch (unused) {
49
- try {
50
- var AsyncStorage = require("@react-native-async-storage/async-storage").default;
51
- if (AsyncStorage && typeof AsyncStorage.getItem === "function") {
52
- return {
53
- getItem: function getItem(name) {
54
- return AsyncStorage.getItem(name);
55
- },
56
- setItem: function setItem(name, value) {
57
- return AsyncStorage.setItem(name, value);
58
- },
59
- removeItem: function removeItem(name) {
60
- return AsyncStorage.removeItem(name);
61
- }
62
- };
63
- }
64
- } catch (unused2) {}
65
- var map = /* @__PURE__ */new Map();
79
+ } catch (err) {
80
+ errors.mmkv = err;
81
+ }
82
+ try {
83
+ var asyncStorageModule = loaders.loadAsyncStorage();
84
+ var asyncStorage = asyncStorageModule === null || asyncStorageModule === void 0 ? void 0 : asyncStorageModule.default;
85
+ if (!asyncStorage || typeof asyncStorage.getItem !== "function") {
86
+ throw new Error("no usable default export on @react-native-async-storage/async-storage");
87
+ }
66
88
  return {
89
+ backend: "async-storage",
90
+ errors,
91
+ storage: {
92
+ getItem: function getItem(name) {
93
+ return asyncStorage.getItem(name);
94
+ },
95
+ setItem: function setItem(name, value) {
96
+ return asyncStorage.setItem(name, value);
97
+ },
98
+ removeItem: function removeItem(name) {
99
+ return asyncStorage.removeItem(name);
100
+ }
101
+ }
102
+ };
103
+ } catch (err) {
104
+ errors.asyncStorage = err;
105
+ }
106
+ var map = /* @__PURE__ */new Map();
107
+ return {
108
+ backend: "memory",
109
+ errors,
110
+ storage: {
67
111
  getItem: function getItem(name) {
68
112
  var _map_get;
69
113
  return (_map_get = map.get(name)) !== null && _map_get !== void 0 ? _map_get : null;
@@ -74,8 +118,27 @@ function createStorage() {
74
118
  removeItem: function removeItem(name) {
75
119
  map.delete(name);
76
120
  }
77
- };
121
+ }
122
+ };
123
+ }
124
+ function describeError(err) {
125
+ return _instanceof(err, Error) ? err.message : String(err);
126
+ }
127
+ function logNativeStorageResolution(resolution2) {
128
+ var logger = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : console;
129
+ if (resolution2.backend === "mmkv") {
130
+ logger.info("[@multiplatform.one/store] native storage backend: mmkv");
131
+ return;
78
132
  }
133
+ var causes = [`react-native-mmkv unavailable: ${describeError(resolution2.errors.mmkv)}`];
134
+ if (resolution2.backend === "memory") {
135
+ causes.push(`async-storage unavailable: ${describeError(resolution2.errors.asyncStorage)}`);
136
+ }
137
+ logger.warn(`[@multiplatform.one/store] native storage fell back to ${resolution2.backend}` + (resolution2.backend === "memory" ? " \u2014 persisted state will NOT survive app restarts" : "") + ` (${causes.join("; ")})`);
138
+ }
139
+ var resolution = resolveNativeStorage();
140
+ if (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test") {
141
+ logNativeStorageResolution(resolution);
79
142
  }
80
- var storage = createStorage();
143
+ var storage = resolution.storage;
81
144
  //# sourceMappingURL=index.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["index_native_exports","__export","storage","module","exports","__toCommonJS","createStorage","MMKV","require","mmkv","getItem","name","_mmkv_getString","getString","setItem","value","set","removeItem","delete","unused","AsyncStorage","default","unused2","map","Map","_map_get","get"],"sources":["../../../src/storage/index.native.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,oBAAA;AAAAC,QAAA,CAAAD,oBAAA;EAAAE,OAAA,EAAAA,CAAA,KAAAA;AAAA;AAAAC,MAAA,CAAAC,OAAA,GAAAC,YAAA,CAAAL,oBAAA;AAAA,SAASM,cAAA,EAAgB;EACrB,IAAI;IACA,IAAI;MAAEC;IAAK,IAAIC,OAAA,CAAQ,mBAAmB;IAC1C,IAAIC,IAAA,GAAO,IAAIF,IAAA,CAAK;IACpB,OAAO;MACHG,OAAA,EAAS,SAASA,QAAQC,IAAA,EAAM;QAC5B,IAAIC,eAAA;QACJ,QAAQA,eAAA,GAAkBH,IAAA,CAAKI,SAAA,CAAUF,IAAI,OAAO,QAAQC,eAAA,KAAoB,SAASA,eAAA,GAAkB;MAC/G;MACAE,OAAA,EAAS,SAASA,QAAQH,IAAA,EAAMI,KAAA,EAAO;QACnC,OAAON,IAAA,CAAKO,GAAA,CAAIL,IAAA,EAAMI,KAAK;MAC/B;MACAE,UAAA,EAAY,SAASA,WAAWN,IAAA,EAAM;QAClC,OAAOF,IAAA,CAAKS,MAAA,CAAOP,IAAI;MAC3B;IACJ;EACJ,SAASQ,MAAA,EAAQ;IACb,IAAI;MAEA,IAAIC,YAAA,GAAeZ,OAAA,CAAQ,2CAA2C,EAAEa,OAAA;MAExE,IAAID,YAAA,IAAgB,OAAOA,YAAA,CAAaV,OAAA,KAAY,YAAY;QAC5D,OAAO;UACHA,OAAA,EAAS,SAASA,QAAQC,IAAA,EAAM;YAC5B,OAAOS,YAAA,CAAaV,OAAA,CAAQC,IAAI;UACpC;UACAG,OAAA,EAAS,SAASA,QAAQH,IAAA,EAAMI,KAAA,EAAO;YACnC,OAAOK,YAAA,CAAaN,OAAA,CAAQH,IAAA,EAAMI,KAAK;UAC3C;UACAE,UAAA,EAAY,SAASA,WAAWN,IAAA,EAAM;YAClC,OAAOS,YAAA,CAAaH,UAAA,CAAWN,IAAI;UACvC;QACJ;MACJ;IACJ,SAASW,OAAA,EAAQ,CAAC;IAElB,IAAIC,GAAA,GAAM,mBAAIC,GAAA,CAAI;IAClB,OAAO;MACHd,OAAA,EAAS,SAASA,QAAQC,IAAA,EAAM;QAC5B,IAAIc,QAAA;QACJ,QAAQA,QAAA,GAAWF,GAAA,CAAIG,GAAA,CAAIf,IAAI,OAAO,QAAQc,QAAA,KAAa,SAASA,QAAA,GAAW;MACnF;MACAX,OAAA,EAAS,SAASA,QAAQH,IAAA,EAAMI,KAAA,EAAO;QACnCQ,GAAA,CAAIP,GAAA,CAAIL,IAAA,EAAMI,KAAK;MACvB;MACAE,UAAA,EAAY,SAASA,WAAWN,IAAA,EAAM;QAClCY,GAAA,CAAIL,MAAA,CAAOP,IAAI;MACnB;IACJ;EACJ;AACJ;AACO,IAAIT,OAAA,GAAUI,aAAA,CAAc","ignoreList":[]}
1
+ {"version":3,"names":["index_native_exports","__export","logNativeStorageResolution","resolveNativeStorage","storage","module","exports","__toCommonJS","_instanceof","left","right","Symbol","hasInstance","defaultLoaders","loadMMKV","require","loadAsyncStorage","loaders","arguments","length","errors","mmkvModule","createMMKV","Error","mmkv","backend","getItem","name","_mmkv_getString","getString","setItem","value","set","removeItem","remove","err","asyncStorageModule","asyncStorage","default","map","Map","_map_get","get","delete","describeError","message","String","resolution2","logger","console","info","causes","push","warn","join","resolution","process","env","NODE_ENV"],"sources":["../../../src/storage/index.native.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
@@ -1,40 +1,82 @@
1
- function createStorage() {
1
+ function _instanceof(left, right) {
2
+ "@swc/helpers - instanceof";
3
+
4
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
5
+ return !!right[Symbol.hasInstance](left);
6
+ } else {
7
+ return left instanceof right;
8
+ }
9
+ }
10
+ var defaultLoaders = {
11
+ // Literal require(...) calls on purpose: metro resolves them natively and
12
+ // vxrn/rolldown rewrites literal requires into bundled module inits (a
13
+ // passed-around `require` reference would survive as a bare identifier and
14
+ // crash). Lazy so a genuinely missing native module throws HERE — inside
15
+ // the try/catch — instead of at bundle evaluation.
16
+ loadMMKV: function loadMMKV() {
17
+ return require("react-native-mmkv");
18
+ },
19
+ loadAsyncStorage: function loadAsyncStorage() {
20
+ return require("@react-native-async-storage/async-storage");
21
+ }
22
+ };
23
+ function resolveNativeStorage() {
24
+ var loaders = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : defaultLoaders;
25
+ var errors = {};
2
26
  try {
3
- var {
4
- MMKV
5
- } = require("react-native-mmkv");
6
- var mmkv = new MMKV();
27
+ var mmkvModule = loaders.loadMMKV();
28
+ if (typeof (mmkvModule === null || mmkvModule === void 0 ? void 0 : mmkvModule.createMMKV) !== "function") {
29
+ throw new Error("no createMMKV() export (react-native-mmkv v4 API expected)");
30
+ }
31
+ var mmkv = mmkvModule.createMMKV();
7
32
  return {
8
- getItem: function getItem(name) {
9
- var _mmkv_getString;
10
- return (_mmkv_getString = mmkv.getString(name)) !== null && _mmkv_getString !== void 0 ? _mmkv_getString : null;
11
- },
12
- setItem: function setItem(name, value) {
13
- return mmkv.set(name, value);
14
- },
15
- removeItem: function removeItem(name) {
16
- return mmkv.delete(name);
33
+ backend: "mmkv",
34
+ errors,
35
+ storage: {
36
+ getItem: function getItem(name) {
37
+ var _mmkv_getString;
38
+ return (_mmkv_getString = mmkv.getString(name)) !== null && _mmkv_getString !== void 0 ? _mmkv_getString : null;
39
+ },
40
+ setItem: function setItem(name, value) {
41
+ mmkv.set(name, value);
42
+ },
43
+ removeItem: function removeItem(name) {
44
+ mmkv.remove(name);
45
+ }
17
46
  }
18
47
  };
19
- } catch (unused) {
20
- try {
21
- var AsyncStorage = require("@react-native-async-storage/async-storage").default;
22
- if (AsyncStorage && typeof AsyncStorage.getItem === "function") {
23
- return {
24
- getItem: function getItem(name) {
25
- return AsyncStorage.getItem(name);
26
- },
27
- setItem: function setItem(name, value) {
28
- return AsyncStorage.setItem(name, value);
29
- },
30
- removeItem: function removeItem(name) {
31
- return AsyncStorage.removeItem(name);
32
- }
33
- };
34
- }
35
- } catch (unused2) {}
36
- var map = /* @__PURE__ */new Map();
48
+ } catch (err) {
49
+ errors.mmkv = err;
50
+ }
51
+ try {
52
+ var asyncStorageModule = loaders.loadAsyncStorage();
53
+ var asyncStorage = asyncStorageModule === null || asyncStorageModule === void 0 ? void 0 : asyncStorageModule.default;
54
+ if (!asyncStorage || typeof asyncStorage.getItem !== "function") {
55
+ throw new Error("no usable default export on @react-native-async-storage/async-storage");
56
+ }
37
57
  return {
58
+ backend: "async-storage",
59
+ errors,
60
+ storage: {
61
+ getItem: function getItem(name) {
62
+ return asyncStorage.getItem(name);
63
+ },
64
+ setItem: function setItem(name, value) {
65
+ return asyncStorage.setItem(name, value);
66
+ },
67
+ removeItem: function removeItem(name) {
68
+ return asyncStorage.removeItem(name);
69
+ }
70
+ }
71
+ };
72
+ } catch (err) {
73
+ errors.asyncStorage = err;
74
+ }
75
+ var map = /* @__PURE__ */new Map();
76
+ return {
77
+ backend: "memory",
78
+ errors,
79
+ storage: {
38
80
  getItem: function getItem(name) {
39
81
  var _map_get;
40
82
  return (_map_get = map.get(name)) !== null && _map_get !== void 0 ? _map_get : null;
@@ -45,9 +87,28 @@ function createStorage() {
45
87
  removeItem: function removeItem(name) {
46
88
  map.delete(name);
47
89
  }
48
- };
90
+ }
91
+ };
92
+ }
93
+ function describeError(err) {
94
+ return _instanceof(err, Error) ? err.message : String(err);
95
+ }
96
+ function logNativeStorageResolution(resolution2) {
97
+ var logger = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : console;
98
+ if (resolution2.backend === "mmkv") {
99
+ logger.info("[@multiplatform.one/store] native storage backend: mmkv");
100
+ return;
49
101
  }
102
+ var causes = [`react-native-mmkv unavailable: ${describeError(resolution2.errors.mmkv)}`];
103
+ if (resolution2.backend === "memory") {
104
+ causes.push(`async-storage unavailable: ${describeError(resolution2.errors.asyncStorage)}`);
105
+ }
106
+ logger.warn(`[@multiplatform.one/store] native storage fell back to ${resolution2.backend}` + (resolution2.backend === "memory" ? " \u2014 persisted state will NOT survive app restarts" : "") + ` (${causes.join("; ")})`);
107
+ }
108
+ var resolution = resolveNativeStorage();
109
+ if (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test") {
110
+ logNativeStorageResolution(resolution);
50
111
  }
51
- var storage = createStorage();
52
- export { storage };
112
+ var storage = resolution.storage;
113
+ export { logNativeStorageResolution, resolveNativeStorage, storage };
53
114
  //# sourceMappingURL=index.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["createStorage","MMKV","require","mmkv","getItem","name","_mmkv_getString","getString","setItem","value","set","removeItem","delete","unused","AsyncStorage","default","unused2","map","Map","_map_get","get","storage"],"sources":["../../../src/storage/index.native.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,cAAA,EAAgB;EACrB,IAAI;IACA,IAAI;MAAEC;IAAK,IAAIC,OAAA,CAAQ,mBAAmB;IAC1C,IAAIC,IAAA,GAAO,IAAIF,IAAA,CAAK;IACpB,OAAO;MACHG,OAAA,EAAS,SAASA,QAAQC,IAAA,EAAM;QAC5B,IAAIC,eAAA;QACJ,QAAQA,eAAA,GAAkBH,IAAA,CAAKI,SAAA,CAAUF,IAAI,OAAO,QAAQC,eAAA,KAAoB,SAASA,eAAA,GAAkB;MAC/G;MACAE,OAAA,EAAS,SAASA,QAAQH,IAAA,EAAMI,KAAA,EAAO;QACnC,OAAON,IAAA,CAAKO,GAAA,CAAIL,IAAA,EAAMI,KAAK;MAC/B;MACAE,UAAA,EAAY,SAASA,WAAWN,IAAA,EAAM;QAClC,OAAOF,IAAA,CAAKS,MAAA,CAAOP,IAAI;MAC3B;IACJ;EACJ,SAASQ,MAAA,EAAQ;IACb,IAAI;MAEA,IAAIC,YAAA,GAAeZ,OAAA,CAAQ,2CAA2C,EAAEa,OAAA;MAExE,IAAID,YAAA,IAAgB,OAAOA,YAAA,CAAaV,OAAA,KAAY,YAAY;QAC5D,OAAO;UACHA,OAAA,EAAS,SAASA,QAAQC,IAAA,EAAM;YAC5B,OAAOS,YAAA,CAAaV,OAAA,CAAQC,IAAI;UACpC;UACAG,OAAA,EAAS,SAASA,QAAQH,IAAA,EAAMI,KAAA,EAAO;YACnC,OAAOK,YAAA,CAAaN,OAAA,CAAQH,IAAA,EAAMI,KAAK;UAC3C;UACAE,UAAA,EAAY,SAASA,WAAWN,IAAA,EAAM;YAClC,OAAOS,YAAA,CAAaH,UAAA,CAAWN,IAAI;UACvC;QACJ;MACJ;IACJ,SAASW,OAAA,EAAQ,CAAC;IAElB,IAAIC,GAAA,GAAM,mBAAIC,GAAA,CAAI;IAClB,OAAO;MACHd,OAAA,EAAS,SAASA,QAAQC,IAAA,EAAM;QAC5B,IAAIc,QAAA;QACJ,QAAQA,QAAA,GAAWF,GAAA,CAAIG,GAAA,CAAIf,IAAI,OAAO,QAAQc,QAAA,KAAa,SAASA,QAAA,GAAW;MACnF;MACAX,OAAA,EAAS,SAASA,QAAQH,IAAA,EAAMI,KAAA,EAAO;QACnCQ,GAAA,CAAIP,GAAA,CAAIL,IAAA,EAAMI,KAAK;MACvB;MACAE,UAAA,EAAY,SAASA,WAAWN,IAAA,EAAM;QAClCY,GAAA,CAAIL,MAAA,CAAOP,IAAI;MACnB;IACJ;EACJ;AACJ;AACO,IAAIgB,OAAA,GAAUrB,aAAA,CAAc","ignoreList":[]}
1
+ {"version":3,"names":["_instanceof","left","right","Symbol","hasInstance","defaultLoaders","loadMMKV","require","loadAsyncStorage","resolveNativeStorage","loaders","arguments","length","errors","mmkvModule","createMMKV","Error","mmkv","backend","storage","getItem","name","_mmkv_getString","getString","setItem","value","set","removeItem","remove","err","asyncStorageModule","asyncStorage","default","map","Map","_map_get","get","delete","describeError","message","String","logNativeStorageResolution","resolution2","logger","console","info","causes","push","warn","join","resolution","process","env","NODE_ENV"],"sources":["../../../src/storage/index.native.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/store",
3
- "version": "6.7.0",
3
+ "version": "7.1.0",
4
4
  "description": "multiplatform.one TanStack Store with cross-platform persistent storage",
5
5
  "keywords": [
6
6
  "react",
@@ -57,7 +57,7 @@
57
57
  "react-native-mmkv": "^4.3.1"
58
58
  },
59
59
  "devDependencies": {
60
- "@tamagui/build": "2.0.0-rc.41",
60
+ "@tamagui/build": "2.7.6",
61
61
  "@testing-library/jest-dom": "^6.9.1",
62
62
  "@testing-library/react": "^16.3.2",
63
63
  "@vitejs/plugin-react": "^6.0.1",
@@ -67,18 +67,18 @@
67
67
  "typescript": "~5.9.3",
68
68
  "vite": "^8.0.10",
69
69
  "vitest": "^4.1.5",
70
- "@multiplatform.one/test-utils": "6.7.0",
71
- "@multiplatform.one/config": "6.7.0"
70
+ "@multiplatform.one/config": "7.1.0",
71
+ "@multiplatform.one/test-utils": "7.1.0"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "react": "^19.1.0"
75
75
  },
76
- "optionalDependencies": {
77
- "@tauri-apps/plugin-store": "^2.4.2"
78
- },
79
76
  "engines": {
80
77
  "node": ">=18.0.0"
81
78
  },
79
+ "optionalDependencies": {
80
+ "@tauri-apps/plugin-store": "^2.4.2"
81
+ },
82
82
  "scripts": {
83
83
  "build": "rm -rf dist types 2>/dev/null && tamagui-build --ts-project ./tsconfig.build.json --exclude '\\.spec\\.|\\.test\\.|\\.stories\\.'",
84
84
  "pretest": "node -e \"const p=require('path').resolve(__dirname,'../../node_modules/@testing-library/react/node_modules');['react','react-dom','scheduler'].forEach(d=>{try{require('fs').rmSync(p+'/'+d,{recursive:true,force:true})}catch{}})\"",
@@ -1,30 +1,117 @@
1
+ /**
2
+ * Native storage twin — backend priority: MMKV (react-native-mmkv v4,
3
+ * synchronous JSI/Nitro) → AsyncStorage (environments without Nitro, e.g.
4
+ * Expo Go) → in-memory Map (nothing survives a restart).
5
+ *
6
+ * react-native-mmkv v4 exports `MMKV` as a TYPE only — the runtime API is
7
+ * the `createMMKV()` factory. The previous implementation destructured the
8
+ * non-existent `MMKV` class (v3 API) and threw `undefined is not a
9
+ * constructor` on every launch, silently landing all native persistence on
10
+ * the in-memory fallback (favorites lost on restart — 2026-08-13 iOS smoke
11
+ * test, p-results/native-smoke/REPORT.md #2). Resolution is therefore
12
+ * factored into a pure, loader-injectable function (spec-covered) and the
13
+ * chosen backend is logged once in dev — a degraded backend can never be
14
+ * silent again.
15
+ */
16
+
1
17
  import type { Storage } from "../persist";
2
18
 
3
- function createStorage(): Storage {
19
+ export type NativeStorageBackend = "mmkv" | "async-storage" | "memory";
20
+
21
+ /** Runtime subset of react-native-mmkv v4's MMKV HybridObject. */
22
+ interface MMKVLike {
23
+ getString(key: string): string | undefined;
24
+ set(key: string, value: string): void;
25
+ remove(key: string): boolean;
26
+ }
27
+
28
+ interface AsyncStorageLike {
29
+ getItem(key: string): Promise<string | null>;
30
+ setItem(key: string, value: string): Promise<void>;
31
+ removeItem(key: string): Promise<void>;
32
+ }
33
+
34
+ /** Module loaders, injectable so specs can drive every resolution branch. */
35
+ export interface NativeStorageLoaders {
36
+ /** Load react-native-mmkv (throws when unavailable). */
37
+ loadMMKV(): unknown;
38
+ /** Load @react-native-async-storage/async-storage (throws when unavailable). */
39
+ loadAsyncStorage(): unknown;
40
+ }
41
+
42
+ export interface NativeStorageResolution {
43
+ storage: Storage;
44
+ backend: NativeStorageBackend;
45
+ /** Why higher-priority backends were skipped (surfaced by the dev log). */
46
+ errors: { mmkv?: unknown; asyncStorage?: unknown };
47
+ }
48
+
49
+ const defaultLoaders: NativeStorageLoaders = {
50
+ // Literal require(...) calls on purpose: metro resolves them natively and
51
+ // vxrn/rolldown rewrites literal requires into bundled module inits (a
52
+ // passed-around `require` reference would survive as a bare identifier and
53
+ // crash). Lazy so a genuinely missing native module throws HERE — inside
54
+ // the try/catch — instead of at bundle evaluation.
55
+ loadMMKV: () => require("react-native-mmkv"),
56
+ loadAsyncStorage: () => require("@react-native-async-storage/async-storage"),
57
+ };
58
+
59
+ export function resolveNativeStorage(
60
+ loaders: NativeStorageLoaders = defaultLoaders,
61
+ ): NativeStorageResolution {
62
+ const errors: NativeStorageResolution["errors"] = {};
63
+
4
64
  try {
5
- const { MMKV } = require("react-native-mmkv");
6
- const mmkv = new MMKV();
65
+ const mmkvModule = loaders.loadMMKV() as {
66
+ createMMKV?: (configuration?: unknown) => MMKVLike;
67
+ };
68
+ if (typeof mmkvModule?.createMMKV !== "function") {
69
+ // v3 shipped an `MMKV` class; v4 only ships the factory. Anything
70
+ // without createMMKV() is not a usable react-native-mmkv module.
71
+ throw new Error("no createMMKV() export (react-native-mmkv v4 API expected)");
72
+ }
73
+ const mmkv = mmkvModule.createMMKV();
7
74
  return {
8
- getItem: (name: string) => mmkv.getString(name) ?? null,
9
- setItem: (name: string, value: string) => mmkv.set(name, value),
10
- removeItem: (name: string) => mmkv.delete(name),
75
+ backend: "mmkv",
76
+ errors,
77
+ storage: {
78
+ getItem: (name: string) => mmkv.getString(name) ?? null,
79
+ setItem: (name: string, value: string) => {
80
+ mmkv.set(name, value);
81
+ },
82
+ removeItem: (name: string) => {
83
+ mmkv.remove(name);
84
+ },
85
+ },
11
86
  };
12
- } catch {
13
- try {
14
- // Fallback to AsyncStorage for Expo Go or environments without TurboModules
15
- const AsyncStorage = require("@react-native-async-storage/async-storage").default;
16
- // Verify the native module is available before returning
17
- if (AsyncStorage && typeof AsyncStorage.getItem === "function") {
18
- return {
19
- getItem: (name: string) => AsyncStorage.getItem(name),
20
- setItem: (name: string, value: string) => AsyncStorage.setItem(name, value),
21
- removeItem: (name: string) => AsyncStorage.removeItem(name),
22
- };
23
- }
24
- } catch {}
25
- // Final fallback: in-memory storage
26
- const map = new Map<string, string>();
87
+ } catch (err) {
88
+ errors.mmkv = err;
89
+ }
90
+
91
+ try {
92
+ const asyncStorageModule = loaders.loadAsyncStorage() as { default?: AsyncStorageLike };
93
+ const asyncStorage = asyncStorageModule?.default;
94
+ if (!asyncStorage || typeof asyncStorage.getItem !== "function") {
95
+ throw new Error("no usable default export on @react-native-async-storage/async-storage");
96
+ }
27
97
  return {
98
+ backend: "async-storage",
99
+ errors,
100
+ storage: {
101
+ getItem: (name: string) => asyncStorage.getItem(name),
102
+ setItem: (name: string, value: string) => asyncStorage.setItem(name, value),
103
+ removeItem: (name: string) => asyncStorage.removeItem(name),
104
+ },
105
+ };
106
+ } catch (err) {
107
+ errors.asyncStorage = err;
108
+ }
109
+
110
+ const map = new Map<string, string>();
111
+ return {
112
+ backend: "memory",
113
+ errors,
114
+ storage: {
28
115
  getItem: (name: string) => map.get(name) ?? null,
29
116
  setItem: (name: string, value: string) => {
30
117
  map.set(name, value);
@@ -32,8 +119,42 @@ function createStorage(): Storage {
32
119
  removeItem: (name: string) => {
33
120
  map.delete(name);
34
121
  },
35
- };
122
+ },
123
+ };
124
+ }
125
+
126
+ function describeError(err: unknown): string {
127
+ return err instanceof Error ? err.message : String(err);
128
+ }
129
+
130
+ /**
131
+ * One dev-mode line naming the winning backend — a console.warn when
132
+ * persistence degraded, so a broken storage twin is loud instead of silent.
133
+ */
134
+ export function logNativeStorageResolution(
135
+ resolution: NativeStorageResolution,
136
+ logger: Pick<Console, "info" | "warn"> = console,
137
+ ): void {
138
+ if (resolution.backend === "mmkv") {
139
+ logger.info("[@multiplatform.one/store] native storage backend: mmkv");
140
+ return;
36
141
  }
142
+ const causes = [`react-native-mmkv unavailable: ${describeError(resolution.errors.mmkv)}`];
143
+ if (resolution.backend === "memory") {
144
+ causes.push(`async-storage unavailable: ${describeError(resolution.errors.asyncStorage)}`);
145
+ }
146
+ logger.warn(
147
+ `[@multiplatform.one/store] native storage fell back to ${resolution.backend}` +
148
+ (resolution.backend === "memory" ? " — persisted state will NOT survive app restarts" : "") +
149
+ ` (${causes.join("; ")})`,
150
+ );
151
+ }
152
+
153
+ const resolution = resolveNativeStorage();
154
+ // NODE_ENV !== "test" keeps vitest imports of this module quiet; specs cover
155
+ // the log contract by calling logNativeStorageResolution directly.
156
+ if (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test") {
157
+ logNativeStorageResolution(resolution);
37
158
  }
38
159
 
39
- export const storage: Storage = createStorage();
160
+ export const storage: Storage = resolution.storage;
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Native storage resolution specs — lock down the backend-priority contract
3
+ * (mmkv → async-storage → memory), the react-native-mmkv v4 API usage
4
+ * (`createMMKV()` factory; `MMKV` is a type-only export in v4, and
5
+ * destructuring it was the silent-in-memory persistence bug on iOS), and
6
+ * the loud dev-mode fallback log.
7
+ */
8
+
9
+ import { describe, expect, it, vi } from "vitest";
10
+ import type { Storage } from "../persist";
11
+ import { logNativeStorageResolution, resolveNativeStorage } from "./index.native";
12
+
13
+ function createMMKVv4Module() {
14
+ const data = new Map<string, string>();
15
+ const instance = {
16
+ getString: (key: string) => data.get(key),
17
+ set: (key: string, value: string) => {
18
+ data.set(key, value);
19
+ },
20
+ remove: (key: string) => data.delete(key),
21
+ };
22
+ const createMMKV = vi.fn(() => instance);
23
+ return { module: { createMMKV }, createMMKV, data };
24
+ }
25
+
26
+ function createAsyncStorageModule() {
27
+ const data = new Map<string, string>();
28
+ return {
29
+ module: {
30
+ default: {
31
+ getItem: async (key: string) => data.get(key) ?? null,
32
+ setItem: async (key: string, value: string) => {
33
+ data.set(key, value);
34
+ },
35
+ removeItem: async (key: string) => {
36
+ data.delete(key);
37
+ },
38
+ },
39
+ },
40
+ data,
41
+ };
42
+ }
43
+
44
+ const throwing = (name: string) => () => {
45
+ throw new Error(`${name} unavailable`);
46
+ };
47
+
48
+ async function roundTrip(storage: Storage): Promise<{
49
+ afterSet: string | null;
50
+ afterRemove: string | null;
51
+ missing: string | null;
52
+ }> {
53
+ await storage.setItem("k", "v");
54
+ const afterSet = await storage.getItem("k");
55
+ await storage.removeItem("k");
56
+ const afterRemove = await storage.getItem("k");
57
+ const missing = await storage.getItem("never-written");
58
+ return { afterSet, afterRemove, missing };
59
+ }
60
+
61
+ describe("resolveNativeStorage", () => {
62
+ it("picks MMKV via the v4 createMMKV() factory and round-trips values", async () => {
63
+ const mmkv = createMMKVv4Module();
64
+ const resolution = resolveNativeStorage({
65
+ loadMMKV: () => mmkv.module,
66
+ loadAsyncStorage: throwing("async-storage"),
67
+ });
68
+
69
+ expect(resolution.backend).toBe("mmkv");
70
+ expect(mmkv.createMMKV).toHaveBeenCalledTimes(1);
71
+ expect(resolution.errors.mmkv).toBeUndefined();
72
+ await expect(roundTrip(resolution.storage)).resolves.toEqual({
73
+ afterSet: "v",
74
+ afterRemove: null,
75
+ missing: null,
76
+ });
77
+ expect(mmkv.data.size).toBe(0);
78
+ });
79
+
80
+ it("regression: a v3-shaped module (MMKV class, no createMMKV) is not treated as usable", () => {
81
+ // react-native-mmkv v4 exports `MMKV` as a type only; the old
82
+ // `new MMKV()` code threw at runtime and silently fell to memory.
83
+ class FakeV3MMKV {}
84
+ const asyncStorage = createAsyncStorageModule();
85
+ const resolution = resolveNativeStorage({
86
+ loadMMKV: () => ({ MMKV: FakeV3MMKV }),
87
+ loadAsyncStorage: () => asyncStorage.module,
88
+ });
89
+
90
+ expect(resolution.backend).toBe("async-storage");
91
+ expect(String(resolution.errors.mmkv)).toContain("createMMKV");
92
+ });
93
+
94
+ it("falls back to AsyncStorage when the MMKV module fails to load", async () => {
95
+ const asyncStorage = createAsyncStorageModule();
96
+ const resolution = resolveNativeStorage({
97
+ loadMMKV: throwing("react-native-mmkv"),
98
+ loadAsyncStorage: () => asyncStorage.module,
99
+ });
100
+
101
+ expect(resolution.backend).toBe("async-storage");
102
+ expect(String(resolution.errors.mmkv)).toContain("react-native-mmkv unavailable");
103
+ await expect(roundTrip(resolution.storage)).resolves.toEqual({
104
+ afterSet: "v",
105
+ afterRemove: null,
106
+ missing: null,
107
+ });
108
+ });
109
+
110
+ it("falls back to memory when AsyncStorage has no usable default export", async () => {
111
+ const resolution = resolveNativeStorage({
112
+ loadMMKV: throwing("react-native-mmkv"),
113
+ loadAsyncStorage: () => ({ default: undefined }),
114
+ });
115
+
116
+ expect(resolution.backend).toBe("memory");
117
+ expect(resolution.errors.asyncStorage).toBeDefined();
118
+ // Memory storage still honors the Storage contract.
119
+ await expect(roundTrip(resolution.storage)).resolves.toEqual({
120
+ afterSet: "v",
121
+ afterRemove: null,
122
+ missing: null,
123
+ });
124
+ });
125
+
126
+ it("records both loader errors when everything fails", () => {
127
+ const resolution = resolveNativeStorage({
128
+ loadMMKV: throwing("react-native-mmkv"),
129
+ loadAsyncStorage: throwing("async-storage"),
130
+ });
131
+
132
+ expect(resolution.backend).toBe("memory");
133
+ expect(String(resolution.errors.mmkv)).toContain("react-native-mmkv unavailable");
134
+ expect(String(resolution.errors.asyncStorage)).toContain("async-storage unavailable");
135
+ });
136
+ });
137
+
138
+ describe("logNativeStorageResolution", () => {
139
+ it("logs a single info line when MMKV won", () => {
140
+ const info = vi.fn();
141
+ const warn = vi.fn();
142
+ const mmkv = createMMKVv4Module();
143
+ const resolution = resolveNativeStorage({
144
+ loadMMKV: () => mmkv.module,
145
+ loadAsyncStorage: throwing("async-storage"),
146
+ });
147
+
148
+ logNativeStorageResolution(resolution, { info, warn });
149
+
150
+ expect(info).toHaveBeenCalledTimes(1);
151
+ expect(info.mock.calls[0]?.[0]).toContain("backend: mmkv");
152
+ expect(warn).not.toHaveBeenCalled();
153
+ });
154
+
155
+ it("warns loudly (once) when persistence degraded to async-storage", () => {
156
+ const info = vi.fn();
157
+ const warn = vi.fn();
158
+ const asyncStorage = createAsyncStorageModule();
159
+ const resolution = resolveNativeStorage({
160
+ loadMMKV: throwing("react-native-mmkv"),
161
+ loadAsyncStorage: () => asyncStorage.module,
162
+ });
163
+
164
+ logNativeStorageResolution(resolution, { info, warn });
165
+
166
+ expect(warn).toHaveBeenCalledTimes(1);
167
+ const message = String(warn.mock.calls[0]?.[0]);
168
+ expect(message).toContain("fell back to async-storage");
169
+ expect(message).toContain("react-native-mmkv unavailable");
170
+ expect(info).not.toHaveBeenCalled();
171
+ });
172
+
173
+ it("warns that state will not survive restarts when memory won", () => {
174
+ const info = vi.fn();
175
+ const warn = vi.fn();
176
+ const resolution = resolveNativeStorage({
177
+ loadMMKV: throwing("react-native-mmkv"),
178
+ loadAsyncStorage: throwing("async-storage"),
179
+ });
180
+
181
+ logNativeStorageResolution(resolution, { info, warn });
182
+
183
+ expect(warn).toHaveBeenCalledTimes(1);
184
+ const message = String(warn.mock.calls[0]?.[0]);
185
+ expect(message).toContain("fell back to memory");
186
+ expect(message).toContain("NOT survive app restarts");
187
+ expect(message).toContain("react-native-mmkv unavailable");
188
+ expect(message).toContain("async-storage unavailable");
189
+ });
190
+ });
191
+
192
+ describe("module-level storage export", () => {
193
+ it("resolves without crashing outside React Native", async () => {
194
+ // Outside metro/vxrn the default loader chain must settle on SOME backend
195
+ // instead of throwing at import time (in this vitest environment it lands
196
+ // on AsyncStorage's web build over the test setup's no-op localStorage
197
+ // stub, so a round-trip cannot be asserted here — the injected-loader
198
+ // specs above cover the round-trip contract per backend).
199
+ const { storage } = await import("./index.native");
200
+ expect(typeof storage.getItem).toBe("function");
201
+ expect(typeof storage.setItem).toBe("function");
202
+ expect(typeof storage.removeItem).toBe("function");
203
+ await expect(Promise.resolve(storage.setItem("spec-key", "spec-value"))).resolves.not.toThrow();
204
+ await expect(Promise.resolve(storage.removeItem("spec-key"))).resolves.not.toThrow();
205
+ });
206
+ });
@@ -1,3 +1,41 @@
1
+ /**
2
+ * Native storage twin — backend priority: MMKV (react-native-mmkv v4,
3
+ * synchronous JSI/Nitro) → AsyncStorage (environments without Nitro, e.g.
4
+ * Expo Go) → in-memory Map (nothing survives a restart).
5
+ *
6
+ * react-native-mmkv v4 exports `MMKV` as a TYPE only — the runtime API is
7
+ * the `createMMKV()` factory. The previous implementation destructured the
8
+ * non-existent `MMKV` class (v3 API) and threw `undefined is not a
9
+ * constructor` on every launch, silently landing all native persistence on
10
+ * the in-memory fallback (favorites lost on restart — 2026-08-13 iOS smoke
11
+ * test, p-results/native-smoke/REPORT.md #2). Resolution is therefore
12
+ * factored into a pure, loader-injectable function (spec-covered) and the
13
+ * chosen backend is logged once in dev — a degraded backend can never be
14
+ * silent again.
15
+ */
1
16
  import type { Storage } from "../persist";
17
+ export type NativeStorageBackend = "mmkv" | "async-storage" | "memory";
18
+ /** Module loaders, injectable so specs can drive every resolution branch. */
19
+ export interface NativeStorageLoaders {
20
+ /** Load react-native-mmkv (throws when unavailable). */
21
+ loadMMKV(): unknown;
22
+ /** Load @react-native-async-storage/async-storage (throws when unavailable). */
23
+ loadAsyncStorage(): unknown;
24
+ }
25
+ export interface NativeStorageResolution {
26
+ storage: Storage;
27
+ backend: NativeStorageBackend;
28
+ /** Why higher-priority backends were skipped (surfaced by the dev log). */
29
+ errors: {
30
+ mmkv?: unknown;
31
+ asyncStorage?: unknown;
32
+ };
33
+ }
34
+ export declare function resolveNativeStorage(loaders?: NativeStorageLoaders): NativeStorageResolution;
35
+ /**
36
+ * One dev-mode line naming the winning backend — a console.warn when
37
+ * persistence degraded, so a broken storage twin is loud instead of silent.
38
+ */
39
+ export declare function logNativeStorageResolution(resolution: NativeStorageResolution, logger?: Pick<Console, "info" | "warn">): void;
2
40
  export declare const storage: Storage;
3
41
  //# sourceMappingURL=index.native.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.native.d.ts","sourceRoot":"","sources":["../../src/storage/index.native.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAsC1C,eAAO,MAAM,OAAO,EAAE,OAAyB,CAAC"}
1
+ {"version":3,"file":"index.native.d.ts","sourceRoot":"","sources":["../../src/storage/index.native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,eAAe,GAAG,QAAQ,CAAC;AAevE,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,QAAQ,IAAI,OAAO,CAAC;IACpB,gFAAgF;IAChF,gBAAgB,IAAI,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,2EAA2E;IAC3E,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACpD;AAYD,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,oBAAqC,GAC7C,uBAAuB,CA+DzB;AAMD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,uBAAuB,EACnC,MAAM,GAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAW,GAC/C,IAAI,CAcN;AASD,eAAO,MAAM,OAAO,EAAE,OAA4B,CAAC"}