@nice-code/state 0.7.0 → 0.9.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.
- package/build/Store-B65MojT2.d.ts +201 -0
- package/build/Store-CI9N0P6I.js +366 -0
- package/build/Store-CI9N0P6I.js.map +1 -0
- package/build/Store-PjfFkZ2I.js +349 -0
- package/build/Store-PjfFkZ2I.js.map +1 -0
- package/build/devtools/browser/index.d.ts +120 -0
- package/build/devtools/browser/index.js +2750 -2357
- package/build/devtools/browser/index.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +2 -244
- package/build/react/index.d.ts +58 -0
- package/build/react/index.js +59 -308
- package/build/react/index.js.map +1 -0
- package/package.json +29 -26
- package/build/types/core/Store.d.ts +0 -136
- package/build/types/core/index.d.ts +0 -1
- package/build/types/devtools/browser/NiceStateDevtools.d.ts +0 -10
- package/build/types/devtools/browser/components/ChangeDetailPanel.d.ts +0 -12
- package/build/types/devtools/browser/components/ChangeList.d.ts +0 -9
- package/build/types/devtools/browser/components/DiffView.d.ts +0 -13
- package/build/types/devtools/browser/components/JsonDiffView.d.ts +0 -24
- package/build/types/devtools/browser/components/JsonView.d.ts +0 -7
- package/build/types/devtools/browser/components/PanelChrome.d.ts +0 -54
- package/build/types/devtools/browser/components/SectionLabel.d.ts +0 -4
- package/build/types/devtools/browser/components/StateInspector.d.ts +0 -16
- package/build/types/devtools/browser/components/StoreTabs.d.ts +0 -12
- package/build/types/devtools/browser/components/utils.d.ts +0 -98
- package/build/types/devtools/browser/devtools_dock.d.ts +0 -54
- package/build/types/devtools/browser/index.d.ts +0 -3
- package/build/types/devtools/core/StateDevtools.types.d.ts +0 -43
- package/build/types/devtools/core/StateDevtoolsCore.d.ts +0 -56
- package/build/types/devtools/core/devtools_colors.d.ts +0 -26
- package/build/types/index.d.ts +0 -1
- package/build/types/react/InjectStoreState.d.ts +0 -18
- package/build/types/react/index.d.ts +0 -3
- package/build/types/react/useLocalStore.d.ts +0 -8
- package/build/types/react/useStoreState.d.ts +0 -23
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { deepEqual } from "fast-equals";
|
|
2
|
+
import { applyPatches, enablePatches, produce, produceWithPatches } from "immer";
|
|
3
|
+
//#region src/core/Store.ts
|
|
4
|
+
enablePatches();
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*
|
|
8
|
+
* Builds the per-emission runner for a selector subscription.
|
|
9
|
+
*
|
|
10
|
+
* Change detection follows the "Fast-Path" rule: Immer's structural sharing
|
|
11
|
+
* means an untouched branch keeps its exact reference, so a strict `===` check
|
|
12
|
+
* resolves the overwhelmingly common "nothing changed" case in 0ms. Only when
|
|
13
|
+
* the reference actually changes do we pay for a `deepEqual` traversal to
|
|
14
|
+
* confirm the change is structural and not just a new-but-equal reference.
|
|
15
|
+
*/
|
|
16
|
+
function makeSubscriptionFunction(store, watch, listener) {
|
|
17
|
+
let lastWatchState = watch(store.getRawState());
|
|
18
|
+
return () => {
|
|
19
|
+
const currentState = store.getRawState();
|
|
20
|
+
const nextWatchState = watch(currentState);
|
|
21
|
+
if (nextWatchState === lastWatchState) return;
|
|
22
|
+
if (deepEqual(nextWatchState, lastWatchState)) {
|
|
23
|
+
lastWatchState = nextWatchState;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const previousWatched = lastWatchState;
|
|
27
|
+
lastWatchState = nextWatchState;
|
|
28
|
+
listener(nextWatchState, currentState, previousWatched);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @internal
|
|
33
|
+
*
|
|
34
|
+
* Builds a reaction creator. Reactions follow the same Fast-Path rule as
|
|
35
|
+
* subscriptions, then run their recipe inside Immer to derive further state.
|
|
36
|
+
*/
|
|
37
|
+
function makeReactionFunctionCreator(watch, reaction) {
|
|
38
|
+
return (store) => {
|
|
39
|
+
let lastWatchState = watch(store.getRawState());
|
|
40
|
+
return (forceRun = false) => {
|
|
41
|
+
const currentState = store.getRawState();
|
|
42
|
+
const nextWatchState = watch(currentState);
|
|
43
|
+
if (!forceRun) {
|
|
44
|
+
if (nextWatchState === lastWatchState) return;
|
|
45
|
+
if (deepEqual(nextWatchState, lastWatchState)) {
|
|
46
|
+
lastWatchState = nextWatchState;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const previousWatched = lastWatchState;
|
|
51
|
+
lastWatchState = nextWatchState;
|
|
52
|
+
if (store._hasPatchListeners()) {
|
|
53
|
+
const [nextState, patches, inversePatches] = produceWithPatches(currentState, (draft) => {
|
|
54
|
+
reaction(nextWatchState, draft, currentState, previousWatched);
|
|
55
|
+
});
|
|
56
|
+
store._updateStateWithoutReaction(nextState);
|
|
57
|
+
if (patches.length > 0) store._emitPatches(patches, inversePatches);
|
|
58
|
+
} else {
|
|
59
|
+
const nextState = produce(currentState, (draft) => {
|
|
60
|
+
reaction(nextWatchState, draft, currentState, previousWatched);
|
|
61
|
+
});
|
|
62
|
+
store._updateStateWithoutReaction(nextState);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A framework-agnostic, Immer-backed state container.
|
|
69
|
+
*
|
|
70
|
+
* The store owns a single immutable state value and a `Set` of plain
|
|
71
|
+
* listeners. Every mutation runs through Immer's `produce`, and listeners are
|
|
72
|
+
* only notified when the resulting root reference actually changes — making
|
|
73
|
+
* "no-op" updates genuinely free for subscribers.
|
|
74
|
+
*
|
|
75
|
+
* @typeParam S Your store's state interface.
|
|
76
|
+
*/
|
|
77
|
+
var Store = class {
|
|
78
|
+
currentState;
|
|
79
|
+
createInitialState;
|
|
80
|
+
ssr = false;
|
|
81
|
+
/** Plain pub/sub listeners — a Set both dedupes and dispatches fast. */
|
|
82
|
+
listeners = /* @__PURE__ */ new Set();
|
|
83
|
+
reactionCreators = [];
|
|
84
|
+
reactions = [];
|
|
85
|
+
clientSubscriptions = [];
|
|
86
|
+
/**
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
_patchListeners = [];
|
|
90
|
+
constructor(initialState) {
|
|
91
|
+
if (initialState instanceof Function) {
|
|
92
|
+
this.createInitialState = initialState;
|
|
93
|
+
this.currentState = initialState();
|
|
94
|
+
} else {
|
|
95
|
+
this.createInitialState = () => initialState;
|
|
96
|
+
this.currentState = initialState;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
get state() {
|
|
100
|
+
return Object.freeze(this.currentState);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
_setInternalOptions({ ssr, reactionCreators = [] }) {
|
|
106
|
+
this.ssr = ssr;
|
|
107
|
+
this.reactionCreators = reactionCreators;
|
|
108
|
+
this.reactions = reactionCreators.map((rc) => rc(this));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
_getReactionCreators() {
|
|
114
|
+
return this.reactionCreators;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @internal
|
|
118
|
+
*/
|
|
119
|
+
_instantiateReactions() {
|
|
120
|
+
this.reactions = this.reactionCreators.map((rc) => rc(this));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
_getInitialState() {
|
|
126
|
+
return this.createInitialState();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
_hasPatchListeners() {
|
|
132
|
+
return this._patchListeners.length > 0;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* @internal
|
|
136
|
+
*/
|
|
137
|
+
_emitPatches(patches, inversePatches) {
|
|
138
|
+
for (const listener of this._patchListeners) listener(patches, inversePatches);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* @internal
|
|
142
|
+
*
|
|
143
|
+
* Writes a new state value without running reactions. Used by reactions
|
|
144
|
+
* themselves to avoid re-entrancy.
|
|
145
|
+
*/
|
|
146
|
+
_updateStateWithoutReaction(nextState) {
|
|
147
|
+
this.currentState = nextState;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @internal
|
|
151
|
+
*
|
|
152
|
+
* Commits a new root state, runs reactions, then dispatches to all
|
|
153
|
+
* subscribers. Callers are responsible for the `nextState !== currentState`
|
|
154
|
+
* reference guard before invoking this.
|
|
155
|
+
*/
|
|
156
|
+
_updateState(nextState) {
|
|
157
|
+
this.currentState = nextState;
|
|
158
|
+
for (const runReaction of this.reactions) runReaction();
|
|
159
|
+
if (this.ssr) return;
|
|
160
|
+
for (const runSubscription of this.clientSubscriptions) runSubscription();
|
|
161
|
+
for (const listener of this.listeners) listener();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Returns the raw state object contained within this store at this moment.
|
|
165
|
+
*
|
|
166
|
+
* ---
|
|
167
|
+
* ** WARNING **
|
|
168
|
+
*
|
|
169
|
+
* Most of the time, if you're using this in your app, there's probably a
|
|
170
|
+
* better way to do it (a selector subscription or the React adapter).
|
|
171
|
+
* ---
|
|
172
|
+
*/
|
|
173
|
+
getRawState() {
|
|
174
|
+
return this.currentState;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Subscribe a plain listener to store mutations. The listener fires once per
|
|
178
|
+
* committed update (root reference change) and receives no arguments — read
|
|
179
|
+
* the latest value with {@link getRawState}.
|
|
180
|
+
*
|
|
181
|
+
* This is the low-level primitive that vanilla code and framework adapters
|
|
182
|
+
* (e.g. React's `useSyncExternalStore`) build upon.
|
|
183
|
+
*
|
|
184
|
+
* @returns An unsubscribe function.
|
|
185
|
+
*/
|
|
186
|
+
subscribe(listener) {
|
|
187
|
+
this.listeners.add(listener);
|
|
188
|
+
return () => {
|
|
189
|
+
this.listeners.delete(listener);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Subscribe to a derived slice of state. The `listener` only fires when the
|
|
194
|
+
* watched value changes structurally (per the Fast-Path rule).
|
|
195
|
+
*
|
|
196
|
+
* @returns An unsubscribe function.
|
|
197
|
+
*/
|
|
198
|
+
watch(watch, listener) {
|
|
199
|
+
if (this.ssr) return () => {
|
|
200
|
+
console.warn(`@nice-code/state: Subscriptions made on the server side are not registered, so this unsubscribe call does nothing.`);
|
|
201
|
+
};
|
|
202
|
+
const run = makeSubscriptionFunction(this, watch, listener);
|
|
203
|
+
this.clientSubscriptions.push(run);
|
|
204
|
+
return () => {
|
|
205
|
+
this.clientSubscriptions = this.clientSubscriptions.filter((f) => f !== run);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Register a reaction: when the watched slice changes, the `reaction` recipe
|
|
210
|
+
* runs inside Immer to derive further state on the same store.
|
|
211
|
+
*
|
|
212
|
+
* @returns A function that removes the reaction.
|
|
213
|
+
*/
|
|
214
|
+
createReaction(watch, reaction, { runNow = false, runNowWithSideEffects = false } = {}) {
|
|
215
|
+
const creator = makeReactionFunctionCreator(watch, reaction);
|
|
216
|
+
this.reactionCreators.push(creator);
|
|
217
|
+
const run = creator(this);
|
|
218
|
+
this.reactions.push(run);
|
|
219
|
+
if (runNow || runNowWithSideEffects) {
|
|
220
|
+
run(true);
|
|
221
|
+
if (runNowWithSideEffects && !this.ssr) this._updateState(this.currentState);
|
|
222
|
+
}
|
|
223
|
+
return () => {
|
|
224
|
+
this.reactions = this.reactions.filter((f) => f !== run);
|
|
225
|
+
this.reactionCreators = this.reactionCreators.filter((f) => f !== creator);
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Subscribe to the Immer patches produced by each update.
|
|
230
|
+
*
|
|
231
|
+
* @returns An unsubscribe function.
|
|
232
|
+
*/
|
|
233
|
+
listenToPatches(patchListener) {
|
|
234
|
+
this._patchListeners.push(patchListener);
|
|
235
|
+
return () => {
|
|
236
|
+
this._patchListeners = this._patchListeners.filter((f) => f !== patchListener);
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Mutate the store via one or more Immer update functions.
|
|
241
|
+
*
|
|
242
|
+
* @param updater A single update function, or an array applied in order.
|
|
243
|
+
* @param patchesCallback Optional callback receiving the patches for this update.
|
|
244
|
+
*/
|
|
245
|
+
update(updater, patchesCallback) {
|
|
246
|
+
update(this, updater, patchesCallback);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Replace the store's state entirely with a new state value.
|
|
250
|
+
*/
|
|
251
|
+
replace(newState) {
|
|
252
|
+
if (newState !== this.currentState) this._updateState(newState);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Replace the store's state by mapping from the current state.
|
|
256
|
+
*/
|
|
257
|
+
replaceFromCurrent(replacer) {
|
|
258
|
+
const nextState = replacer(this.currentState);
|
|
259
|
+
if (nextState !== this.currentState) this._updateState(nextState);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Apply a set of Immer patches to the store.
|
|
263
|
+
*/
|
|
264
|
+
applyPatches(patches) {
|
|
265
|
+
applyPatchesToStore(this, patches);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Apply Immer patches to a store, committing only if the root reference changes.
|
|
270
|
+
*/
|
|
271
|
+
function applyPatchesToStore(store, patches) {
|
|
272
|
+
const currentState = store.getRawState();
|
|
273
|
+
const nextState = applyPatches(currentState, patches);
|
|
274
|
+
if (nextState !== currentState) store._updateState(nextState);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* @internal
|
|
278
|
+
*
|
|
279
|
+
* Runs an updater (or array of updaters) through Immer, collecting patches.
|
|
280
|
+
*/
|
|
281
|
+
function applyUpdaterWithPatches(currentState, updater) {
|
|
282
|
+
if (typeof updater === "function") {
|
|
283
|
+
const [next, patches, inversePatches] = produceWithPatches(currentState, (draft) => {
|
|
284
|
+
updater(draft, currentState);
|
|
285
|
+
});
|
|
286
|
+
return [
|
|
287
|
+
next,
|
|
288
|
+
patches,
|
|
289
|
+
inversePatches
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
let state = currentState;
|
|
293
|
+
const patches = [];
|
|
294
|
+
const inversePatches = [];
|
|
295
|
+
for (const single of updater) {
|
|
296
|
+
const [next, p, ip] = produceWithPatches(state, (draft) => {
|
|
297
|
+
single(draft, state);
|
|
298
|
+
});
|
|
299
|
+
state = next;
|
|
300
|
+
patches.push(...p);
|
|
301
|
+
inversePatches.push(...ip);
|
|
302
|
+
}
|
|
303
|
+
return [
|
|
304
|
+
state,
|
|
305
|
+
patches,
|
|
306
|
+
inversePatches
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* @internal
|
|
311
|
+
*
|
|
312
|
+
* Runs an updater (or array of updaters) through Immer without tracking patches.
|
|
313
|
+
*/
|
|
314
|
+
function applyUpdater(currentState, updater) {
|
|
315
|
+
if (typeof updater === "function") return produce(currentState, (draft) => {
|
|
316
|
+
updater(draft, currentState);
|
|
317
|
+
});
|
|
318
|
+
return updater.reduce((previousState, single) => produce(previousState, (draft) => {
|
|
319
|
+
single(draft, previousState);
|
|
320
|
+
}), currentState);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Mutate a store via one or more Immer update functions.
|
|
324
|
+
*
|
|
325
|
+
* Patches are only computed when there's a consumer for them (a registered
|
|
326
|
+
* patch listener or a `patchesCallback`), keeping the common path allocation-free.
|
|
327
|
+
* Listeners are notified only when the root reference actually changes.
|
|
328
|
+
*
|
|
329
|
+
* @param store The store to update.
|
|
330
|
+
* @param updater A single update function, or an array applied in order.
|
|
331
|
+
* @param patchesCallback Optional callback receiving the patches for this update.
|
|
332
|
+
*/
|
|
333
|
+
function update(store, updater, patchesCallback) {
|
|
334
|
+
const currentState = store.getRawState();
|
|
335
|
+
let nextState;
|
|
336
|
+
if (store._hasPatchListeners() || patchesCallback != null) {
|
|
337
|
+
const [next, patches, inversePatches] = applyUpdaterWithPatches(currentState, updater);
|
|
338
|
+
if (patches.length > 0) {
|
|
339
|
+
if (patchesCallback != null) patchesCallback(patches, inversePatches);
|
|
340
|
+
store._emitPatches(patches, inversePatches);
|
|
341
|
+
}
|
|
342
|
+
nextState = next;
|
|
343
|
+
} else nextState = applyUpdater(currentState, updater);
|
|
344
|
+
if (nextState !== currentState) store._updateState(nextState);
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
export { applyPatchesToStore as n, update as r, Store as t };
|
|
348
|
+
|
|
349
|
+
//# sourceMappingURL=Store-PjfFkZ2I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Store-PjfFkZ2I.js","names":[],"sources":["../src/core/Store.ts"],"sourcesContent":["import { deepEqual } from \"fast-equals\";\r\nimport {\r\n applyPatches,\r\n type Draft,\r\n enablePatches,\r\n type Patch,\r\n type PatchListener,\r\n produce,\r\n produceWithPatches,\r\n} from \"immer\";\r\n\r\n// Immer emits JSON patches for `produceWithPatches`, which the store exposes via\r\n// `listenToPatches`. This must run once, before any patch-producing update.\r\nenablePatches();\r\n\r\n/**\r\n * A plain pub/sub listener. Fired (in the order added) every time the store's\r\n * root state reference changes. This is the vanilla integration point that any\r\n * ecosystem — including the React adapter's `useSyncExternalStore` — builds on.\r\n */\r\nexport type TUpdateListener = () => void;\r\n\r\n/**\r\n * @typeParam S The store's state\r\n * @param draft The mutable draft to change during this update (Immer proxy).\r\n * @param original A readonly snapshot of the state as it was before this update.\r\n */\r\nexport type TUpdateFunction<S> = (draft: Draft<S>, original: S) => void;\r\n\r\n/**\r\n * A selector that derives a watched slice `T` from the full store state `S`.\r\n */\r\nexport type TStoreWatch<S extends object, T> = (state: S) => T;\r\n\r\n/**\r\n * Fired by {@link Store.watch} whenever the watched slice changes structurally.\r\n */\r\nexport type TStoreSubscriptionListener<S extends object, T> = (\r\n watched: T,\r\n allState: S,\r\n previousWatched: T,\r\n) => void;\r\n\r\n/**\r\n * Runs inside an Immer `produce` whenever a watched slice changes, letting the\r\n * store derive further state from its own mutations.\r\n */\r\nexport type TReactionFunction<S extends object, T> = (\r\n watched: T,\r\n draft: Draft<S>,\r\n original: S,\r\n previousWatched: T,\r\n) => void;\r\n\r\nexport type TStoreActionUpdate<S extends object> = (\r\n updater: TUpdateFunction<S> | TUpdateFunction<S>[],\r\n patchesCallback?: TPatchesCallback,\r\n) => void;\r\n\r\nexport type TStoreAction<S extends object> = (update: TStoreActionUpdate<S>) => void;\r\n\r\nexport type TPatchesCallback = (patches: Patch[], inversePatches: Patch[]) => void;\r\n\r\n/**\r\n * @internal\r\n * Drives a registered reaction. `forceRun` bypasses the change-detection\r\n * fast-path (used when a reaction is created with `runNow`).\r\n */\r\ntype TRunReactionFunction = (forceRun?: boolean) => void;\r\n\r\n/**\r\n * @internal\r\n * Drives a registered selector subscription on every store emission.\r\n */\r\ntype TRunSubscriptionFunction = () => void;\r\n\r\n/**\r\n * @internal\r\n * Binds a reaction definition to a concrete store instance.\r\n */\r\ntype TReactionCreator<S extends object> = (store: Store<S>) => TRunReactionFunction;\r\n\r\nexport interface IStoreInternalOptions<S extends object> {\r\n ssr: boolean;\r\n reactionCreators?: TReactionCreator<S>[];\r\n}\r\n\r\nexport interface ICreateReactionOptions {\r\n runNow?: boolean;\r\n runNowWithSideEffects?: boolean;\r\n}\r\n\r\n/**\r\n * @internal\r\n *\r\n * Builds the per-emission runner for a selector subscription.\r\n *\r\n * Change detection follows the \"Fast-Path\" rule: Immer's structural sharing\r\n * means an untouched branch keeps its exact reference, so a strict `===` check\r\n * resolves the overwhelmingly common \"nothing changed\" case in 0ms. Only when\r\n * the reference actually changes do we pay for a `deepEqual` traversal to\r\n * confirm the change is structural and not just a new-but-equal reference.\r\n */\r\nfunction makeSubscriptionFunction<S extends object, T>(\r\n store: Store<S>,\r\n watch: TStoreWatch<S, T>,\r\n listener: TStoreSubscriptionListener<S, T>,\r\n): TRunSubscriptionFunction {\r\n let lastWatchState: T = watch(store.getRawState());\r\n\r\n return () => {\r\n const currentState = store.getRawState();\r\n const nextWatchState = watch(currentState);\r\n\r\n // Fast-path: reference is identical → the watched branch was never touched.\r\n if (nextWatchState === lastWatchState) {\r\n return;\r\n }\r\n\r\n // Reference changed — fall back to deep equality to confirm real change.\r\n if (deepEqual(nextWatchState, lastWatchState)) {\r\n // Structurally equal: adopt the new reference so the fast-path stays hot,\r\n // but do not notify — nothing the watcher cares about actually changed.\r\n lastWatchState = nextWatchState;\r\n return;\r\n }\r\n\r\n const previousWatched = lastWatchState;\r\n lastWatchState = nextWatchState;\r\n listener(nextWatchState, currentState, previousWatched);\r\n };\r\n}\r\n\r\n/**\r\n * @internal\r\n *\r\n * Builds a reaction creator. Reactions follow the same Fast-Path rule as\r\n * subscriptions, then run their recipe inside Immer to derive further state.\r\n */\r\nfunction makeReactionFunctionCreator<S extends object, T>(\r\n watch: TStoreWatch<S, T>,\r\n reaction: TReactionFunction<S, T>,\r\n): TReactionCreator<S> {\r\n return (store) => {\r\n let lastWatchState: T = watch(store.getRawState());\r\n\r\n return (forceRun = false) => {\r\n const currentState = store.getRawState();\r\n const nextWatchState = watch(currentState);\r\n\r\n if (!forceRun) {\r\n // Fast-path: identical reference → skip entirely.\r\n if (nextWatchState === lastWatchState) {\r\n return;\r\n }\r\n\r\n // New reference but structurally equal → adopt and skip the recipe.\r\n if (deepEqual(nextWatchState, lastWatchState)) {\r\n lastWatchState = nextWatchState;\r\n return;\r\n }\r\n }\r\n\r\n const previousWatched = lastWatchState;\r\n lastWatchState = nextWatchState;\r\n\r\n // Reactions mutate via Immer but must not re-enter the reaction loop, so\r\n // they write back through the no-reaction update path.\r\n if (store._hasPatchListeners()) {\r\n const [nextState, patches, inversePatches] = produceWithPatches(\r\n currentState,\r\n (draft: Draft<S>) => {\r\n reaction(nextWatchState, draft, currentState, previousWatched);\r\n },\r\n );\r\n\r\n store._updateStateWithoutReaction(nextState);\r\n\r\n if (patches.length > 0) {\r\n store._emitPatches(patches, inversePatches);\r\n }\r\n } else {\r\n const nextState = produce(currentState, (draft: Draft<S>) => {\r\n reaction(nextWatchState, draft, currentState, previousWatched);\r\n });\r\n store._updateStateWithoutReaction(nextState);\r\n }\r\n };\r\n };\r\n}\r\n\r\n/**\r\n * A framework-agnostic, Immer-backed state container.\r\n *\r\n * The store owns a single immutable state value and a `Set` of plain\r\n * listeners. Every mutation runs through Immer's `produce`, and listeners are\r\n * only notified when the resulting root reference actually changes — making\r\n * \"no-op\" updates genuinely free for subscribers.\r\n *\r\n * @typeParam S Your store's state interface.\r\n */\r\nexport class Store<S extends object = object> {\r\n private currentState: S;\r\n private readonly createInitialState: () => S;\r\n private ssr = false;\r\n\r\n /** Plain pub/sub listeners — a Set both dedupes and dispatches fast. */\r\n private readonly listeners = new Set<TUpdateListener>();\r\n\r\n private reactionCreators: TReactionCreator<S>[] = [];\r\n private reactions: TRunReactionFunction[] = [];\r\n private clientSubscriptions: TRunSubscriptionFunction[] = [];\r\n\r\n /**\r\n * @internal\r\n */\r\n public _patchListeners: PatchListener[] = [];\r\n\r\n constructor(initialState: S | (() => S)) {\r\n if (initialState instanceof Function) {\r\n this.createInitialState = initialState;\r\n this.currentState = initialState();\r\n } else {\r\n this.createInitialState = () => initialState;\r\n this.currentState = initialState;\r\n }\r\n }\r\n\r\n get state(): S {\r\n return Object.freeze(this.currentState);\r\n }\r\n\r\n /**\r\n * @internal\r\n */\r\n _setInternalOptions({ ssr, reactionCreators = [] }: IStoreInternalOptions<S>) {\r\n this.ssr = ssr;\r\n this.reactionCreators = reactionCreators;\r\n this.reactions = reactionCreators.map((rc) => rc(this));\r\n }\r\n\r\n /**\r\n * @internal\r\n */\r\n _getReactionCreators(): TReactionCreator<S>[] {\r\n return this.reactionCreators;\r\n }\r\n\r\n /**\r\n * @internal\r\n */\r\n _instantiateReactions() {\r\n this.reactions = this.reactionCreators.map((rc) => rc(this));\r\n }\r\n\r\n /**\r\n * @internal\r\n */\r\n _getInitialState(): S {\r\n return this.createInitialState();\r\n }\r\n\r\n /**\r\n * @internal\r\n */\r\n _hasPatchListeners(): boolean {\r\n return this._patchListeners.length > 0;\r\n }\r\n\r\n /**\r\n * @internal\r\n */\r\n _emitPatches(patches: Patch[], inversePatches: Patch[]) {\r\n for (const listener of this._patchListeners) {\r\n listener(patches, inversePatches);\r\n }\r\n }\r\n\r\n /**\r\n * @internal\r\n *\r\n * Writes a new state value without running reactions. Used by reactions\r\n * themselves to avoid re-entrancy.\r\n */\r\n _updateStateWithoutReaction(nextState: S) {\r\n this.currentState = nextState;\r\n }\r\n\r\n /**\r\n * @internal\r\n *\r\n * Commits a new root state, runs reactions, then dispatches to all\r\n * subscribers. Callers are responsible for the `nextState !== currentState`\r\n * reference guard before invoking this.\r\n */\r\n _updateState(nextState: S) {\r\n this.currentState = nextState;\r\n\r\n // Reactions run first and may derive further state (without re-triggering).\r\n for (const runReaction of this.reactions) {\r\n runReaction();\r\n }\r\n\r\n if (this.ssr) {\r\n return;\r\n }\r\n\r\n for (const runSubscription of this.clientSubscriptions) {\r\n runSubscription();\r\n }\r\n\r\n for (const listener of this.listeners) {\r\n listener();\r\n }\r\n }\r\n\r\n /**\r\n * Returns the raw state object contained within this store at this moment.\r\n *\r\n * ---\r\n * ** WARNING **\r\n *\r\n * Most of the time, if you're using this in your app, there's probably a\r\n * better way to do it (a selector subscription or the React adapter).\r\n * ---\r\n */\r\n getRawState(): S {\r\n return this.currentState;\r\n }\r\n\r\n /**\r\n * Subscribe a plain listener to store mutations. The listener fires once per\r\n * committed update (root reference change) and receives no arguments — read\r\n * the latest value with {@link getRawState}.\r\n *\r\n * This is the low-level primitive that vanilla code and framework adapters\r\n * (e.g. React's `useSyncExternalStore`) build upon.\r\n *\r\n * @returns An unsubscribe function.\r\n */\r\n subscribe(listener: TUpdateListener): () => void {\r\n this.listeners.add(listener);\r\n return () => {\r\n this.listeners.delete(listener);\r\n };\r\n }\r\n\r\n /**\r\n * Subscribe to a derived slice of state. The `listener` only fires when the\r\n * watched value changes structurally (per the Fast-Path rule).\r\n *\r\n * @returns An unsubscribe function.\r\n */\r\n watch<T>(watch: TStoreWatch<S, T>, listener: TStoreSubscriptionListener<S, T>): () => void {\r\n if (this.ssr) {\r\n return () => {\r\n console.warn(\r\n `@nice-code/state: Subscriptions made on the server side are not registered, so this unsubscribe call does nothing.`,\r\n );\r\n };\r\n }\r\n\r\n const run = makeSubscriptionFunction(this, watch, listener);\r\n this.clientSubscriptions.push(run);\r\n\r\n return () => {\r\n this.clientSubscriptions = this.clientSubscriptions.filter((f) => f !== run);\r\n };\r\n }\r\n\r\n /**\r\n * Register a reaction: when the watched slice changes, the `reaction` recipe\r\n * runs inside Immer to derive further state on the same store.\r\n *\r\n * @returns A function that removes the reaction.\r\n */\r\n createReaction<T>(\r\n watch: TStoreWatch<S, T>,\r\n reaction: TReactionFunction<S, T>,\r\n { runNow = false, runNowWithSideEffects = false }: ICreateReactionOptions = {},\r\n ): () => void {\r\n const creator = makeReactionFunctionCreator(watch, reaction);\r\n this.reactionCreators.push(creator);\r\n\r\n const run = creator(this);\r\n this.reactions.push(run);\r\n\r\n if (runNow || runNowWithSideEffects) {\r\n run(true);\r\n\r\n if (runNowWithSideEffects && !this.ssr) {\r\n this._updateState(this.currentState);\r\n }\r\n }\r\n\r\n return () => {\r\n this.reactions = this.reactions.filter((f) => f !== run);\r\n this.reactionCreators = this.reactionCreators.filter((f) => f !== creator);\r\n };\r\n }\r\n\r\n /**\r\n * Subscribe to the Immer patches produced by each update.\r\n *\r\n * @returns An unsubscribe function.\r\n */\r\n listenToPatches(patchListener: PatchListener): () => void {\r\n this._patchListeners.push(patchListener);\r\n return () => {\r\n this._patchListeners = this._patchListeners.filter((f) => f !== patchListener);\r\n };\r\n }\r\n\r\n /**\r\n * Mutate the store via one or more Immer update functions.\r\n *\r\n * @param updater A single update function, or an array applied in order.\r\n * @param patchesCallback Optional callback receiving the patches for this update.\r\n */\r\n update(updater: TUpdateFunction<S> | TUpdateFunction<S>[], patchesCallback?: TPatchesCallback) {\r\n update(this, updater, patchesCallback);\r\n }\r\n\r\n /**\r\n * Replace the store's state entirely with a new state value.\r\n */\r\n replace(newState: S) {\r\n if (newState !== this.currentState) {\r\n this._updateState(newState);\r\n }\r\n }\r\n\r\n /**\r\n * Replace the store's state by mapping from the current state.\r\n */\r\n replaceFromCurrent(replacer: (state: S) => S) {\r\n const nextState = replacer(this.currentState);\r\n if (nextState !== this.currentState) {\r\n this._updateState(nextState);\r\n }\r\n }\r\n\r\n /**\r\n * Apply a set of Immer patches to the store.\r\n */\r\n applyPatches(patches: Patch[]) {\r\n applyPatchesToStore(this, patches);\r\n }\r\n}\r\n\r\n/**\r\n * Apply Immer patches to a store, committing only if the root reference changes.\r\n */\r\nexport function applyPatchesToStore<S extends object = object>(store: Store<S>, patches: Patch[]) {\r\n const currentState = store.getRawState();\r\n const nextState = applyPatches(currentState, patches);\r\n if (nextState !== currentState) {\r\n store._updateState(nextState);\r\n }\r\n}\r\n\r\n/**\r\n * @internal\r\n *\r\n * Runs an updater (or array of updaters) through Immer, collecting patches.\r\n */\r\nfunction applyUpdaterWithPatches<S extends object>(\r\n currentState: S,\r\n updater: TUpdateFunction<S> | TUpdateFunction<S>[],\r\n): [S, Patch[], Patch[]] {\r\n if (typeof updater === \"function\") {\r\n const [next, patches, inversePatches] = produceWithPatches(currentState, (draft: Draft<S>) => {\r\n updater(draft, currentState);\r\n });\r\n return [next, patches, inversePatches];\r\n }\r\n\r\n let state = currentState;\r\n const patches: Patch[] = [];\r\n const inversePatches: Patch[] = [];\r\n\r\n for (const single of updater) {\r\n const [next, p, ip] = produceWithPatches(state, (draft: Draft<S>) => {\r\n single(draft, state);\r\n });\r\n state = next;\r\n patches.push(...p);\r\n inversePatches.push(...ip);\r\n }\r\n\r\n return [state, patches, inversePatches];\r\n}\r\n\r\n/**\r\n * @internal\r\n *\r\n * Runs an updater (or array of updaters) through Immer without tracking patches.\r\n */\r\nfunction applyUpdater<S extends object>(\r\n currentState: S,\r\n updater: TUpdateFunction<S> | TUpdateFunction<S>[],\r\n): S {\r\n if (typeof updater === \"function\") {\r\n return produce(currentState, (draft: Draft<S>) => {\r\n updater(draft, currentState);\r\n });\r\n }\r\n\r\n return updater.reduce<S>(\r\n (previousState, single) =>\r\n produce(previousState, (draft: Draft<S>) => {\r\n single(draft, previousState);\r\n }),\r\n currentState,\r\n );\r\n}\r\n\r\n/**\r\n * Mutate a store via one or more Immer update functions.\r\n *\r\n * Patches are only computed when there's a consumer for them (a registered\r\n * patch listener or a `patchesCallback`), keeping the common path allocation-free.\r\n * Listeners are notified only when the root reference actually changes.\r\n *\r\n * @param store The store to update.\r\n * @param updater A single update function, or an array applied in order.\r\n * @param patchesCallback Optional callback receiving the patches for this update.\r\n */\r\nexport function update<S extends object = object>(\r\n store: Store<S>,\r\n updater: TUpdateFunction<S> | TUpdateFunction<S>[],\r\n patchesCallback?: TPatchesCallback,\r\n) {\r\n const currentState = store.getRawState();\r\n\r\n let nextState: S;\r\n\r\n if (store._hasPatchListeners() || patchesCallback != null) {\r\n const [next, patches, inversePatches] = applyUpdaterWithPatches(currentState, updater);\r\n\r\n if (patches.length > 0) {\r\n if (patchesCallback != null) {\r\n patchesCallback(patches, inversePatches);\r\n }\r\n store._emitPatches(patches, inversePatches);\r\n }\r\n\r\n nextState = next;\r\n } else {\r\n nextState = applyUpdater(currentState, updater);\r\n }\r\n\r\n // Reference guard: an updater that mutated nothing yields the same reference,\r\n // so we skip the dispatch entirely.\r\n if (nextState !== currentState) {\r\n store._updateState(nextState);\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { r as Store } from "../../Store-B65MojT2.js";
|
|
2
|
+
import { Patch } from "immer";
|
|
3
|
+
//#region ../nice-devtools-shared/src/components/PanelChrome.d.ts
|
|
4
|
+
/** Where a devtools panel is docked. */
|
|
5
|
+
type TDevtoolsPosition = "dock-bottom" | "dock-top" | "dock-left" | "dock-right";
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/devtools/core/StateDevtools.types.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* How a recorded change came to be. `update` is the normal Immer-patch path,
|
|
10
|
+
* `replace` is a whole-state swap with no patches, and the two `devtools-*`
|
|
11
|
+
* sources are writes the devtools itself made (manual edits / reverts) so they
|
|
12
|
+
* can be visually distinguished from real app traffic.
|
|
13
|
+
*/
|
|
14
|
+
type TStateChangeSource = "update" | "replace" | "devtools-edit" | "devtools-revert";
|
|
15
|
+
/**
|
|
16
|
+
* A single committed mutation of a registered store. Captures both the Immer
|
|
17
|
+
* patches (when available) and full before/after snapshots so the panel can
|
|
18
|
+
* render a precise diff and offer a one-click revert.
|
|
19
|
+
*/
|
|
20
|
+
interface IDevtoolsStateChange {
|
|
21
|
+
cuid: string;
|
|
22
|
+
storeId: string;
|
|
23
|
+
storeLabel: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
patches: Patch[];
|
|
26
|
+
inversePatches: Patch[];
|
|
27
|
+
prevSnapshot: unknown;
|
|
28
|
+
snapshot: unknown;
|
|
29
|
+
source: TStateChangeSource;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Live summary of a registered store, refreshed on every commit so the panel's
|
|
33
|
+
* tabs and state inspector always reflect the current value.
|
|
34
|
+
*/
|
|
35
|
+
interface IDevtoolsStoreInfo {
|
|
36
|
+
id: string;
|
|
37
|
+
label: string;
|
|
38
|
+
currentState: unknown;
|
|
39
|
+
changeCount: number;
|
|
40
|
+
lastChangeTime?: number;
|
|
41
|
+
}
|
|
42
|
+
interface IStateDevtoolsSnapshot {
|
|
43
|
+
stores: IDevtoolsStoreInfo[];
|
|
44
|
+
/** Most-recent-first across all registered stores. */
|
|
45
|
+
changes: IDevtoolsStateChange[];
|
|
46
|
+
paused: boolean;
|
|
47
|
+
}
|
|
48
|
+
type TStateDevtoolsListener = (snapshot: IStateDevtoolsSnapshot) => void;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/devtools/core/StateDevtoolsCore.d.ts
|
|
51
|
+
interface IStateDevtoolsCoreOptions {
|
|
52
|
+
/** Cap on retained changes across all stores (oldest dropped first). */
|
|
53
|
+
maxChanges?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Framework-agnostic collector for nice-state stores.
|
|
57
|
+
*
|
|
58
|
+
* Register any {@link Store} and the core hooks its patch + update streams,
|
|
59
|
+
* pairing the two so each committed mutation becomes a single
|
|
60
|
+
* {@link IDevtoolsStateChange} with patches and full before/after snapshots.
|
|
61
|
+
* The browser panel ({@link NiceStateDevtools}) renders whatever this exposes.
|
|
62
|
+
*
|
|
63
|
+
* Registering a store attaches a patch listener, which makes Immer compute
|
|
64
|
+
* patches for that store's updates — a negligible dev-time cost. Keep this out
|
|
65
|
+
* of production bundles (the panel is dev-gated, but the core is not).
|
|
66
|
+
*/
|
|
67
|
+
declare class StateDevtoolsCore {
|
|
68
|
+
private readonly _stores;
|
|
69
|
+
private _changes;
|
|
70
|
+
private readonly _listeners;
|
|
71
|
+
private readonly _maxChanges;
|
|
72
|
+
private _paused;
|
|
73
|
+
private _cuidCounter;
|
|
74
|
+
private _sourceOverride;
|
|
75
|
+
constructor(options?: IStateDevtoolsCoreOptions);
|
|
76
|
+
/**
|
|
77
|
+
* Start observing a store under a human-readable label. Returns an
|
|
78
|
+
* unregister function. If the label collides with an existing store, a numeric
|
|
79
|
+
* suffix is appended to keep ids unique.
|
|
80
|
+
*/
|
|
81
|
+
registerStore(label: string, store: Store<any>): () => void;
|
|
82
|
+
unregisterStore(id: string): void;
|
|
83
|
+
/**
|
|
84
|
+
* Replace a registered store's state wholesale — the primary "edit directly
|
|
85
|
+
* for testing" entry point. The resulting change is tagged `devtools-edit`.
|
|
86
|
+
*/
|
|
87
|
+
applyEdit(storeId: string, newState: unknown): void;
|
|
88
|
+
/**
|
|
89
|
+
* Undo a change by applying its inverse patches. Cleanest for the most recent
|
|
90
|
+
* change of a store; older reverts may not apply if later changes touched the
|
|
91
|
+
* same paths. The resulting change is tagged `devtools-revert`.
|
|
92
|
+
*/
|
|
93
|
+
revertChange(change: IDevtoolsStateChange): void;
|
|
94
|
+
setPaused(paused: boolean): void;
|
|
95
|
+
togglePaused(): void;
|
|
96
|
+
/** Drop the recorded timeline; registered stores and their state remain. */
|
|
97
|
+
clear(): void;
|
|
98
|
+
getSnapshot(): IStateDevtoolsSnapshot;
|
|
99
|
+
subscribe(listener: TStateDevtoolsListener): () => void;
|
|
100
|
+
private _recordChange;
|
|
101
|
+
private _uniqueId;
|
|
102
|
+
private _buildSnapshot;
|
|
103
|
+
private _notify;
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/devtools/browser/NiceStateDevtools.d.ts
|
|
107
|
+
interface INiceStateDevtoolsProps {
|
|
108
|
+
core: StateDevtoolsCore;
|
|
109
|
+
position?: TDevtoolsPosition;
|
|
110
|
+
initialOpen?: boolean;
|
|
111
|
+
/** Show the panel even when NODE_ENV is not "development". */
|
|
112
|
+
forceEnable?: boolean;
|
|
113
|
+
}
|
|
114
|
+
declare function NiceStateDevtools({
|
|
115
|
+
forceEnable,
|
|
116
|
+
...props
|
|
117
|
+
}: INiceStateDevtoolsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
118
|
+
//#endregion
|
|
119
|
+
export { type IDevtoolsStateChange, type IDevtoolsStoreInfo, type INiceStateDevtoolsProps, type IStateDevtoolsCoreOptions, type IStateDevtoolsSnapshot, NiceStateDevtools, StateDevtoolsCore, type TDevtoolsPosition, type TStateChangeSource, type TStateDevtoolsListener };
|
|
120
|
+
//# sourceMappingURL=index.d.ts.map
|