@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.
package/dist/esm/createStore.js
CHANGED
|
@@ -11,6 +11,15 @@ import {
|
|
|
11
11
|
ClientRoot
|
|
12
12
|
} from "./react-bindings.js";
|
|
13
13
|
const __SnapshotContext = RSC ? null : createContext(null);
|
|
14
|
+
let leavingPage = false;
|
|
15
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
16
|
+
window.addEventListener("pagehide", () => {
|
|
17
|
+
leavingPage = true;
|
|
18
|
+
});
|
|
19
|
+
window.addEventListener("pageshow", () => {
|
|
20
|
+
leavingPage = false;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
14
23
|
const storesByName = /* @__PURE__ */ new Map();
|
|
15
24
|
const MERGE = /* @__PURE__ */ Symbol("createStore.merge");
|
|
16
25
|
const QUIET = /* @__PURE__ */ Symbol("createStore.quiet");
|
|
@@ -145,12 +154,48 @@ function defineEvents() {
|
|
|
145
154
|
return void 0;
|
|
146
155
|
}
|
|
147
156
|
function createStore(initialState, options = {}) {
|
|
157
|
+
checkStoreOptions(options);
|
|
148
158
|
if ("queries" in options)
|
|
149
159
|
throw new Error(
|
|
150
160
|
`[${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).`
|
|
151
161
|
);
|
|
152
162
|
return withScopes(initialState, options, makeBase(initialState, options));
|
|
153
163
|
}
|
|
164
|
+
const STORE_OPTIONS = /* @__PURE__ */ new Set([
|
|
165
|
+
"name",
|
|
166
|
+
"persist",
|
|
167
|
+
"syncTabs",
|
|
168
|
+
"history",
|
|
169
|
+
"devtools",
|
|
170
|
+
"shared",
|
|
171
|
+
"ssr",
|
|
172
|
+
"logger",
|
|
173
|
+
"onChange",
|
|
174
|
+
"offline",
|
|
175
|
+
"validate",
|
|
176
|
+
"events",
|
|
177
|
+
"computed",
|
|
178
|
+
"keyActions",
|
|
179
|
+
"actions",
|
|
180
|
+
"queries"
|
|
181
|
+
]);
|
|
182
|
+
const warnedOptions = /* @__PURE__ */ new Set();
|
|
183
|
+
function checkStoreOptions(opts) {
|
|
184
|
+
const options = opts;
|
|
185
|
+
const warn = (message) => {
|
|
186
|
+
if (warnedOptions.has(message)) return;
|
|
187
|
+
warnedOptions.add(message);
|
|
188
|
+
console.warn(message);
|
|
189
|
+
};
|
|
190
|
+
const id = options.name ?? "store";
|
|
191
|
+
for (const k of Object.keys(options))
|
|
192
|
+
if (!STORE_OPTIONS.has(k))
|
|
193
|
+
warn(`[${id}] createStore() has no option "${k}", so it is ignored. Options: ${[...STORE_OPTIONS].filter((o) => o !== "queries").join(", ")}.`);
|
|
194
|
+
if (!options.name && (options.persist || options.syncTabs || options.offline || options.shared))
|
|
195
|
+
warn(
|
|
196
|
+
`[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.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
154
199
|
const warnedSharedOnServer = /* @__PURE__ */ new Set();
|
|
155
200
|
const warnedShape = /* @__PURE__ */ new Set();
|
|
156
201
|
const SHAPE = /* @__PURE__ */ Symbol("createStore.shape");
|
|
@@ -1007,6 +1052,8 @@ function build(initialState, options, sharedDefinition) {
|
|
|
1007
1052
|
}
|
|
1008
1053
|
};
|
|
1009
1054
|
let hydrated = !persistStorage;
|
|
1055
|
+
let warnedUnsavable = false;
|
|
1056
|
+
const earlyWrites = /* @__PURE__ */ new Set();
|
|
1010
1057
|
let resolveHydrated;
|
|
1011
1058
|
const whenHydrated = new Promise((r) => resolveHydrated = r);
|
|
1012
1059
|
let storageIsAsync = false;
|
|
@@ -1033,7 +1080,26 @@ function build(initialState, options, sharedDefinition) {
|
|
|
1033
1080
|
saveTimer = void 0;
|
|
1034
1081
|
if (!persistStorage || !hydrated) return;
|
|
1035
1082
|
const data = Object.fromEntries(persistedKeys.map((k) => [k, state[k]]));
|
|
1036
|
-
|
|
1083
|
+
let text;
|
|
1084
|
+
try {
|
|
1085
|
+
text = JSON.stringify(storageIsAsync ? { $version: persistVersion, $saved: Date.now(), state: data } : { $version: persistVersion, state: data });
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
if (!warnedUnsavable) {
|
|
1088
|
+
warnedUnsavable = true;
|
|
1089
|
+
const bad = persistedKeys.find((k) => {
|
|
1090
|
+
try {
|
|
1091
|
+
JSON.stringify(state[k]);
|
|
1092
|
+
return false;
|
|
1093
|
+
} catch {
|
|
1094
|
+
return true;
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
console.warn(
|
|
1098
|
+
`[${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.`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1037
1103
|
lastSavedText = text;
|
|
1038
1104
|
quietly(() => {
|
|
1039
1105
|
const written = persistStorage.setItem(persistKey, text);
|
|
@@ -1138,9 +1204,12 @@ function build(initialState, options, sharedDefinition) {
|
|
|
1138
1204
|
}
|
|
1139
1205
|
if (rescued != null && isSavedState(rescued) && savedAt(rescued) > savedAt(stored)) text = rescued;
|
|
1140
1206
|
const values = trusted(pick(readSaved(text)), "storage");
|
|
1207
|
+
for (const k of earlyWrites) delete values[k];
|
|
1208
|
+
const keptEarly = earlyWrites.size > 0;
|
|
1209
|
+
earlyWrites.clear();
|
|
1141
1210
|
hydrated = true;
|
|
1142
1211
|
if (!destroyed) apply(values, "storage", "loaded from storage", false);
|
|
1143
|
-
if (!destroyed && text === rescued && rescued != null) saveNow();
|
|
1212
|
+
if (!destroyed && (keptEarly || text === rescued && rescued != null)) saveNow();
|
|
1144
1213
|
finishHydration();
|
|
1145
1214
|
},
|
|
1146
1215
|
(error) => {
|
|
@@ -1245,6 +1314,7 @@ function build(initialState, options, sharedDefinition) {
|
|
|
1245
1314
|
updateHistoryInfo();
|
|
1246
1315
|
}
|
|
1247
1316
|
function batch(fn) {
|
|
1317
|
+
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); }).`);
|
|
1248
1318
|
batchDepth++;
|
|
1249
1319
|
if (batchDepth === 1 && historyLimit) batchChanges = /* @__PURE__ */ Object.create(null);
|
|
1250
1320
|
let result;
|
|
@@ -1337,6 +1407,7 @@ function build(initialState, options, sharedDefinition) {
|
|
|
1337
1407
|
(k) => isStateKey(k) && !Object.is(state[k], values[k])
|
|
1338
1408
|
);
|
|
1339
1409
|
if (keys.length === 0) return;
|
|
1410
|
+
if (!hydrated && source === "local") for (const k of keys) earlyWrites.add(k);
|
|
1340
1411
|
const prev = {};
|
|
1341
1412
|
for (const k of keys) {
|
|
1342
1413
|
prev[k] = state[k];
|
|
@@ -1572,15 +1643,6 @@ function build(initialState, options, sharedDefinition) {
|
|
|
1572
1643
|
ownPromises.add(promise);
|
|
1573
1644
|
return promise;
|
|
1574
1645
|
}
|
|
1575
|
-
let leavingPage = false;
|
|
1576
|
-
if (isBrowser && typeof window.addEventListener === "function") {
|
|
1577
|
-
window.addEventListener("pagehide", () => {
|
|
1578
|
-
leavingPage = true;
|
|
1579
|
-
});
|
|
1580
|
-
window.addEventListener("pageshow", () => {
|
|
1581
|
-
leavingPage = false;
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
1646
|
const SERVER_LOADING = Object.freeze({
|
|
1585
1647
|
state: "loading",
|
|
1586
1648
|
loading: true,
|
|
@@ -2082,11 +2144,15 @@ function build(initialState, options, sharedDefinition) {
|
|
|
2082
2144
|
}
|
|
2083
2145
|
const k = cacheKey(q, params);
|
|
2084
2146
|
const prev = cache2.get(k)?.data;
|
|
2147
|
+
outdateRequest(k);
|
|
2085
2148
|
setEntry(k, {
|
|
2086
2149
|
data: typeof updater === "function" ? updater(prev) : updater,
|
|
2087
2150
|
status: "success"
|
|
2088
2151
|
});
|
|
2089
2152
|
}
|
|
2153
|
+
function outdateRequest(k) {
|
|
2154
|
+
if (inflight.has(k)) versions.set(k, (versions.get(k) ?? 0) + 1);
|
|
2155
|
+
}
|
|
2090
2156
|
function abort(q) {
|
|
2091
2157
|
if (isQuery(q)) inlineAborts.get(q)?.abort();
|
|
2092
2158
|
for (const [k, c] of [...namedAborts])
|
|
@@ -2224,6 +2290,8 @@ function build(initialState, options, sharedDefinition) {
|
|
|
2224
2290
|
};
|
|
2225
2291
|
const api = Object.assign(Object.create(setterProtoOf(def, API_NAMES, "apiProto")), apiMembers);
|
|
2226
2292
|
Object.defineProperty(api, SETTER_OF, { value: (k) => setterFor(k) });
|
|
2293
|
+
if (options.actions != null && typeof options.actions !== "function")
|
|
2294
|
+
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]); } }) }).`);
|
|
2227
2295
|
const actions = options.actions?.(api) ?? {};
|
|
2228
2296
|
const keyActionDefs = options.keyActions ?? {};
|
|
2229
2297
|
const ssrCfg = options.ssr;
|
|
@@ -2618,6 +2686,7 @@ function build(initialState, options, sharedDefinition) {
|
|
|
2618
2686
|
setter = (value, serverCall) => {
|
|
2619
2687
|
const prev = cache2.get(k)?.data;
|
|
2620
2688
|
const next = typeof value === "function" ? value(prev) : value;
|
|
2689
|
+
outdateRequest(k);
|
|
2621
2690
|
setEntry(k, { data: next, status: "success" });
|
|
2622
2691
|
if (!serverCall) return Promise.resolve();
|
|
2623
2692
|
const done = Promise.resolve().then(serverCall).then(
|