@createstore/createstore 1.0.0 → 1.1.1

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.
@@ -29,6 +29,15 @@ __export(createStore_exports, {
29
29
  module.exports = __toCommonJS(createStore_exports);
30
30
  var import_react_bindings = require("./react-bindings.cjs");
31
31
  const __SnapshotContext = import_react_bindings.RSC ? null : (0, import_react_bindings.createContext)(null);
32
+ let leavingPage = false;
33
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
34
+ window.addEventListener("pagehide", () => {
35
+ leavingPage = true;
36
+ });
37
+ window.addEventListener("pageshow", () => {
38
+ leavingPage = false;
39
+ });
40
+ }
32
41
  const storesByName = /* @__PURE__ */ new Map();
33
42
  const MERGE = /* @__PURE__ */ Symbol("createStore.merge");
34
43
  const QUIET = /* @__PURE__ */ Symbol("createStore.quiet");
@@ -163,12 +172,48 @@ function defineEvents() {
163
172
  return void 0;
164
173
  }
165
174
  function createStore(initialState, options = {}) {
175
+ checkStoreOptions(options);
166
176
  if ("queries" in options)
167
177
  throw new Error(
168
178
  `[${options.name ?? "store"}] the queries option was replaced: declare each query in the state with query(fetch, options), e.g. { product: query((id, signal) => api.product(id, signal)) }, and read it with useGlobalState('product', id).`
169
179
  );
170
180
  return withScopes(initialState, options, makeBase(initialState, options));
171
181
  }
182
+ const STORE_OPTIONS = /* @__PURE__ */ new Set([
183
+ "name",
184
+ "persist",
185
+ "syncTabs",
186
+ "history",
187
+ "devtools",
188
+ "shared",
189
+ "ssr",
190
+ "logger",
191
+ "onChange",
192
+ "offline",
193
+ "validate",
194
+ "events",
195
+ "computed",
196
+ "keyActions",
197
+ "actions",
198
+ "queries"
199
+ ]);
200
+ const warnedOptions = /* @__PURE__ */ new Set();
201
+ function checkStoreOptions(opts) {
202
+ const options = opts;
203
+ const warn = (message) => {
204
+ if (warnedOptions.has(message)) return;
205
+ warnedOptions.add(message);
206
+ console.warn(message);
207
+ };
208
+ const id = options.name ?? "store";
209
+ for (const k of Object.keys(options))
210
+ if (!STORE_OPTIONS.has(k))
211
+ warn(`[${id}] createStore() has no option "${k}", so it is ignored. Options: ${[...STORE_OPTIONS].filter((o) => o !== "queries").join(", ")}.`);
212
+ if (!options.name && (options.persist || options.syncTabs || options.offline || options.shared))
213
+ warn(
214
+ `[store] persist, syncTabs, offline and shared use the store's name to tell stores apart. Without { name: '...' }, every unnamed store uses "store", so two of them would overwrite each other's saved data and mix up their tabs. Give this one a name.`
215
+ );
216
+ }
172
217
  const warnedSharedOnServer = /* @__PURE__ */ new Set();
173
218
  const warnedShape = /* @__PURE__ */ new Set();
174
219
  const SHAPE = /* @__PURE__ */ Symbol("createStore.shape");
@@ -1025,6 +1070,8 @@ function build(initialState, options, sharedDefinition) {
1025
1070
  }
1026
1071
  };
1027
1072
  let hydrated = !persistStorage;
1073
+ let warnedUnsavable = false;
1074
+ const earlyWrites = /* @__PURE__ */ new Set();
1028
1075
  let resolveHydrated;
1029
1076
  const whenHydrated = new Promise((r) => resolveHydrated = r);
1030
1077
  let storageIsAsync = false;
@@ -1051,7 +1098,26 @@ function build(initialState, options, sharedDefinition) {
1051
1098
  saveTimer = void 0;
1052
1099
  if (!persistStorage || !hydrated) return;
1053
1100
  const data = Object.fromEntries(persistedKeys.map((k) => [k, state[k]]));
1054
- const text = JSON.stringify(storageIsAsync ? { $version: persistVersion, $saved: Date.now(), state: data } : { $version: persistVersion, state: data });
1101
+ let text;
1102
+ try {
1103
+ text = JSON.stringify(storageIsAsync ? { $version: persistVersion, $saved: Date.now(), state: data } : { $version: persistVersion, state: data });
1104
+ } catch (error) {
1105
+ if (!warnedUnsavable) {
1106
+ warnedUnsavable = true;
1107
+ const bad = persistedKeys.find((k) => {
1108
+ try {
1109
+ JSON.stringify(state[k]);
1110
+ return false;
1111
+ } catch {
1112
+ return true;
1113
+ }
1114
+ });
1115
+ console.warn(
1116
+ `[${name}] couldn't save${bad ? ` "${bad}"` : ""}: ${error.message}. Saved state is JSON, so keep persisted keys to strings, numbers, booleans, null, arrays and plain objects, or leave this key out of saving with persist: { exclude: [...] }. Nothing is saved until it is fixed.`
1117
+ );
1118
+ }
1119
+ return;
1120
+ }
1055
1121
  lastSavedText = text;
1056
1122
  quietly(() => {
1057
1123
  const written = persistStorage.setItem(persistKey, text);
@@ -1156,9 +1222,12 @@ function build(initialState, options, sharedDefinition) {
1156
1222
  }
1157
1223
  if (rescued != null && isSavedState(rescued) && savedAt(rescued) > savedAt(stored)) text = rescued;
1158
1224
  const values = trusted(pick(readSaved(text)), "storage");
1225
+ for (const k of earlyWrites) delete values[k];
1226
+ const keptEarly = earlyWrites.size > 0;
1227
+ earlyWrites.clear();
1159
1228
  hydrated = true;
1160
1229
  if (!destroyed) apply(values, "storage", "loaded from storage", false);
1161
- if (!destroyed && text === rescued && rescued != null) saveNow();
1230
+ if (!destroyed && (keptEarly || text === rescued && rescued != null)) saveNow();
1162
1231
  finishHydration();
1163
1232
  },
1164
1233
  (error) => {
@@ -1263,6 +1332,7 @@ function build(initialState, options, sharedDefinition) {
1263
1332
  updateHistoryInfo();
1264
1333
  }
1265
1334
  function batch(fn) {
1335
+ if (typeof fn !== "function") throw new Error(`[${name}] batch() takes a function that makes the changes: store.batch(() => { store.setState('a', 1); store.setState('b', 2); }).`);
1266
1336
  batchDepth++;
1267
1337
  if (batchDepth === 1 && historyLimit) batchChanges = /* @__PURE__ */ Object.create(null);
1268
1338
  let result;
@@ -1355,6 +1425,7 @@ function build(initialState, options, sharedDefinition) {
1355
1425
  (k) => isStateKey(k) && !Object.is(state[k], values[k])
1356
1426
  );
1357
1427
  if (keys.length === 0) return;
1428
+ if (!hydrated && source === "local") for (const k of keys) earlyWrites.add(k);
1358
1429
  const prev = {};
1359
1430
  for (const k of keys) {
1360
1431
  prev[k] = state[k];
@@ -1590,15 +1661,6 @@ function build(initialState, options, sharedDefinition) {
1590
1661
  ownPromises.add(promise);
1591
1662
  return promise;
1592
1663
  }
1593
- let leavingPage = false;
1594
- if (isBrowser && typeof window.addEventListener === "function") {
1595
- window.addEventListener("pagehide", () => {
1596
- leavingPage = true;
1597
- });
1598
- window.addEventListener("pageshow", () => {
1599
- leavingPage = false;
1600
- });
1601
- }
1602
1664
  const SERVER_LOADING = Object.freeze({
1603
1665
  state: "loading",
1604
1666
  loading: true,
@@ -2100,11 +2162,15 @@ function build(initialState, options, sharedDefinition) {
2100
2162
  }
2101
2163
  const k = cacheKey(q, params);
2102
2164
  const prev = cache2.get(k)?.data;
2165
+ outdateRequest(k);
2103
2166
  setEntry(k, {
2104
2167
  data: typeof updater === "function" ? updater(prev) : updater,
2105
2168
  status: "success"
2106
2169
  });
2107
2170
  }
2171
+ function outdateRequest(k) {
2172
+ if (inflight.has(k)) versions.set(k, (versions.get(k) ?? 0) + 1);
2173
+ }
2108
2174
  function abort(q) {
2109
2175
  if (isQuery(q)) inlineAborts.get(q)?.abort();
2110
2176
  for (const [k, c] of [...namedAborts])
@@ -2242,6 +2308,8 @@ function build(initialState, options, sharedDefinition) {
2242
2308
  };
2243
2309
  const api = Object.assign(Object.create(setterProtoOf(def, API_NAMES, "apiProto")), apiMembers);
2244
2310
  Object.defineProperty(api, SETTER_OF, { value: (k) => setterFor(k) });
2311
+ if (options.actions != null && typeof options.actions !== "function")
2312
+ throw new Error(`[${name}] actions must be a function that receives the store's api and returns your actions: createStore(state, { actions: (api) => ({ add(x) { api.setState('items', (i) => [...i, x]); } }) }).`);
2245
2313
  const actions = options.actions?.(api) ?? {};
2246
2314
  const keyActionDefs = options.keyActions ?? {};
2247
2315
  const ssrCfg = options.ssr;
@@ -2636,6 +2704,7 @@ function build(initialState, options, sharedDefinition) {
2636
2704
  setter = (value, serverCall) => {
2637
2705
  const prev = cache2.get(k)?.data;
2638
2706
  const next = typeof value === "function" ? value(prev) : value;
2707
+ outdateRequest(k);
2639
2708
  setEntry(k, { data: next, status: "success" });
2640
2709
  if (!serverCall) return Promise.resolve();
2641
2710
  const done = Promise.resolve().then(serverCall).then(