@oxog/state 1.0.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/LICENSE +21 -0
- package/README.md +295 -0
- package/dist/iife/index.global.js +47 -0
- package/dist/index.cjs +1292 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1061 -0
- package/dist/index.d.ts +1061 -0
- package/dist/index.js +1265 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1292 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/utils/deep-clone.ts
|
|
6
|
+
function deepClone(value, seen) {
|
|
7
|
+
if (value === null || typeof value !== "object") {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
if (value instanceof Date) {
|
|
11
|
+
return new Date(value.getTime());
|
|
12
|
+
}
|
|
13
|
+
if (!seen) {
|
|
14
|
+
seen = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
}
|
|
16
|
+
if (seen.has(value)) {
|
|
17
|
+
return seen.get(value);
|
|
18
|
+
}
|
|
19
|
+
seen.set(value, value);
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
const cloned = [];
|
|
22
|
+
seen.set(value, cloned);
|
|
23
|
+
value.forEach((item, index) => {
|
|
24
|
+
cloned[index] = deepClone(item, seen);
|
|
25
|
+
});
|
|
26
|
+
return cloned;
|
|
27
|
+
}
|
|
28
|
+
if (value.constructor === Object) {
|
|
29
|
+
const cloned = {};
|
|
30
|
+
seen.set(value, cloned);
|
|
31
|
+
for (const key in value) {
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
33
|
+
cloned[key] = deepClone(value[key], seen);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return cloned;
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/utils/deep-equal.ts
|
|
42
|
+
function deepEqual(a, b) {
|
|
43
|
+
if (Object.is(a, b)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(a)) {
|
|
53
|
+
const arrB = b;
|
|
54
|
+
if (a.length !== arrB.length) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < a.length; i++) {
|
|
58
|
+
if (!deepEqual(a[i], arrB[i])) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const keysA = Object.keys(a);
|
|
65
|
+
const keysB = Object.keys(b);
|
|
66
|
+
if (keysA.length !== keysB.length) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
for (const key of keysA) {
|
|
70
|
+
if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/utils/shallow-equal.ts
|
|
78
|
+
function shallowEqual(a, b) {
|
|
79
|
+
if (Object.is(a, b)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const keysA = Object.keys(a);
|
|
86
|
+
const keysB = Object.keys(b);
|
|
87
|
+
if (keysA.length !== keysB.length) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
for (const key of keysA) {
|
|
91
|
+
if (!keysB.includes(key) || !Object.is(a[key], b[key])) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/utils/deep-merge.ts
|
|
99
|
+
function deepMerge(target, source) {
|
|
100
|
+
if (source === null) {
|
|
101
|
+
return target;
|
|
102
|
+
}
|
|
103
|
+
if (typeof source !== "object") {
|
|
104
|
+
return source;
|
|
105
|
+
}
|
|
106
|
+
const output = deepClone(target);
|
|
107
|
+
for (const key in source) {
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
109
|
+
const sourceValue = source[key];
|
|
110
|
+
const targetValue = output[key];
|
|
111
|
+
if (sourceValue instanceof Date) {
|
|
112
|
+
output[key] = new Date(sourceValue.getTime());
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && !(sourceValue instanceof Date) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue) && !(targetValue instanceof Date)) {
|
|
116
|
+
output[key] = deepMerge(targetValue, sourceValue);
|
|
117
|
+
} else {
|
|
118
|
+
output[key] = cloneValue(sourceValue);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return output;
|
|
123
|
+
}
|
|
124
|
+
function cloneValue(value) {
|
|
125
|
+
if (value === null || typeof value !== "object") {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
if (value instanceof Date) {
|
|
129
|
+
return new Date(value.getTime());
|
|
130
|
+
}
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
return value.map((v) => cloneValue(v));
|
|
133
|
+
}
|
|
134
|
+
if (value.constructor === Object) {
|
|
135
|
+
const cloned = {};
|
|
136
|
+
for (const key in value) {
|
|
137
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
138
|
+
cloned[key] = cloneValue(value[key]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return cloned;
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/utils/is-function.ts
|
|
147
|
+
function isFunction(value) {
|
|
148
|
+
return typeof value === "function";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/utils/pick.ts
|
|
152
|
+
function pick(obj, keys) {
|
|
153
|
+
const result = {};
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
156
|
+
result[key] = obj[key];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/utils/omit.ts
|
|
163
|
+
function omit(obj, keys) {
|
|
164
|
+
const result = { ...obj };
|
|
165
|
+
for (const key of keys) {
|
|
166
|
+
delete result[key];
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/utils/identity.ts
|
|
172
|
+
function identity(value) {
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/kernel.ts
|
|
177
|
+
var EventBus = class {
|
|
178
|
+
listeners = /* @__PURE__ */ new Map();
|
|
179
|
+
/**
|
|
180
|
+
* Subscribe to an event.
|
|
181
|
+
*
|
|
182
|
+
* @param event - Event name
|
|
183
|
+
* @param handler - Event handler
|
|
184
|
+
* @returns Unsubscribe function
|
|
185
|
+
*/
|
|
186
|
+
on(event, handler) {
|
|
187
|
+
if (!this.listeners.has(event)) {
|
|
188
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
189
|
+
}
|
|
190
|
+
this.listeners.get(event).add(handler);
|
|
191
|
+
return () => {
|
|
192
|
+
this.listeners.get(event)?.delete(handler);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Emit an event to all subscribers.
|
|
197
|
+
*
|
|
198
|
+
* @param event - Event name
|
|
199
|
+
* @param data - Event data
|
|
200
|
+
*/
|
|
201
|
+
emit(event, data) {
|
|
202
|
+
const handlers = this.listeners.get(event);
|
|
203
|
+
if (handlers) {
|
|
204
|
+
for (const handler of handlers) {
|
|
205
|
+
try {
|
|
206
|
+
handler(data);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(`Error in ${event} handler:`, error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/** Clear all listeners. */
|
|
214
|
+
destroy() {
|
|
215
|
+
this.listeners.clear();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var Kernel = class {
|
|
219
|
+
plugins = /* @__PURE__ */ new Map();
|
|
220
|
+
eventBus = new EventBus();
|
|
221
|
+
config;
|
|
222
|
+
errorHandlers = /* @__PURE__ */ new Set();
|
|
223
|
+
initializing = false;
|
|
224
|
+
constructor(config) {
|
|
225
|
+
this.config = config || {};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Register a plugin.
|
|
229
|
+
*
|
|
230
|
+
* @param plugin - The plugin to register
|
|
231
|
+
* @param options - Plugin options
|
|
232
|
+
* @param store - The store instance
|
|
233
|
+
* @throws {Error} If plugin already registered or dependencies missing
|
|
234
|
+
*/
|
|
235
|
+
register(plugin, options, store) {
|
|
236
|
+
if (!plugin.name || !plugin.version || typeof plugin.install !== "function") {
|
|
237
|
+
throw new Error("Invalid plugin: must have name, version, and install function");
|
|
238
|
+
}
|
|
239
|
+
if (this.plugins.has(plugin.name)) {
|
|
240
|
+
throw new Error(`Plugin '${plugin.name}' is already registered`);
|
|
241
|
+
}
|
|
242
|
+
if (plugin.dependencies) {
|
|
243
|
+
for (const dep of plugin.dependencies) {
|
|
244
|
+
if (!this.plugins.has(dep)) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Plugin '${plugin.name}' requires '${dep}' to be registered first`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const instance = {
|
|
252
|
+
plugin,
|
|
253
|
+
options
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
plugin.install(store, options);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
throw new Error(`Failed to install plugin '${plugin.name}': ${error}`);
|
|
259
|
+
}
|
|
260
|
+
this.plugins.set(plugin.name, instance);
|
|
261
|
+
if (!this.initializing && plugin.onInit) {
|
|
262
|
+
this.runOnInit(plugin, store);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Initialize all plugins.
|
|
267
|
+
*
|
|
268
|
+
* @param store - The store instance
|
|
269
|
+
*/
|
|
270
|
+
async initializeAll(store) {
|
|
271
|
+
this.initializing = true;
|
|
272
|
+
const plugins = Array.from(this.plugins.values());
|
|
273
|
+
for (const { plugin } of plugins) {
|
|
274
|
+
if (plugin.onInit) {
|
|
275
|
+
await this.runOnInit(plugin, store);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
this.initializing = false;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Run plugin's onInit safely.
|
|
282
|
+
*/
|
|
283
|
+
async runOnInit(plugin, store) {
|
|
284
|
+
try {
|
|
285
|
+
await plugin.onInit(store);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error(`Error in ${plugin.name} onInit:`, error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Unregister a plugin.
|
|
292
|
+
*
|
|
293
|
+
* @param name - Plugin name
|
|
294
|
+
*/
|
|
295
|
+
async unregister(name) {
|
|
296
|
+
const instance = this.plugins.get(name);
|
|
297
|
+
if (!instance) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (instance.plugin.onDestroy) {
|
|
301
|
+
try {
|
|
302
|
+
await instance.plugin.onDestroy();
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error(`Error in ${name} onDestroy:`, error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
this.plugins.delete(name);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Emit a state change event.
|
|
311
|
+
*
|
|
312
|
+
* @param state - New state
|
|
313
|
+
* @param prevState - Previous state
|
|
314
|
+
*/
|
|
315
|
+
emitStateChange(state, prevState) {
|
|
316
|
+
this.eventBus.emit("stateChange", { state, prevState });
|
|
317
|
+
for (const { plugin } of this.plugins.values()) {
|
|
318
|
+
if (plugin.onStateChange) {
|
|
319
|
+
try {
|
|
320
|
+
plugin.onStateChange(state, prevState);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error(`Error in ${plugin.name} onStateChange:`, error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Emit an error event.
|
|
329
|
+
*
|
|
330
|
+
* @param error - The error
|
|
331
|
+
*/
|
|
332
|
+
emitError(error) {
|
|
333
|
+
this.eventBus.emit("error", error);
|
|
334
|
+
for (const { plugin } of this.plugins.values()) {
|
|
335
|
+
if (plugin.onError) {
|
|
336
|
+
try {
|
|
337
|
+
plugin.onError(error);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error(`Error in ${plugin.name} onError:`, err);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const handler of this.errorHandlers) {
|
|
344
|
+
try {
|
|
345
|
+
handler(error);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.error("Error in error handler:", err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Add an error handler.
|
|
353
|
+
*
|
|
354
|
+
* @param handler - Error handler function
|
|
355
|
+
* @returns Unsubscribe function
|
|
356
|
+
*/
|
|
357
|
+
onError(handler) {
|
|
358
|
+
this.errorHandlers.add(handler);
|
|
359
|
+
return () => {
|
|
360
|
+
this.errorHandlers.delete(handler);
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Subscribe to an event.
|
|
365
|
+
*
|
|
366
|
+
* @param event - Event name
|
|
367
|
+
* @param handler - Event handler
|
|
368
|
+
* @returns Unsubscribe function
|
|
369
|
+
*/
|
|
370
|
+
on(event, handler) {
|
|
371
|
+
return this.eventBus.on(event, handler);
|
|
372
|
+
}
|
|
373
|
+
/** Destroy the kernel and cleanup. */
|
|
374
|
+
async destroy() {
|
|
375
|
+
const pluginNames = Array.from(this.plugins.keys());
|
|
376
|
+
for (const name of pluginNames) {
|
|
377
|
+
await this.unregister(name);
|
|
378
|
+
}
|
|
379
|
+
this.eventBus.destroy();
|
|
380
|
+
this.errorHandlers.clear();
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
function createKernel(config) {
|
|
384
|
+
return new Kernel(config);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/batch-context.ts
|
|
388
|
+
var batchDepth = 0;
|
|
389
|
+
var batchStores = /* @__PURE__ */ new Set();
|
|
390
|
+
var batchManagedStores = /* @__PURE__ */ new Set();
|
|
391
|
+
function isBatching() {
|
|
392
|
+
return batchDepth > 0;
|
|
393
|
+
}
|
|
394
|
+
function getBatchDepth() {
|
|
395
|
+
return batchDepth;
|
|
396
|
+
}
|
|
397
|
+
function incrementBatchDepth() {
|
|
398
|
+
return ++batchDepth;
|
|
399
|
+
}
|
|
400
|
+
function decrementBatchDepth() {
|
|
401
|
+
return --batchDepth;
|
|
402
|
+
}
|
|
403
|
+
function getBatchManagedStores() {
|
|
404
|
+
return batchManagedStores;
|
|
405
|
+
}
|
|
406
|
+
function clearBatchStores() {
|
|
407
|
+
batchStores.clear();
|
|
408
|
+
batchManagedStores.clear();
|
|
409
|
+
}
|
|
410
|
+
function addBatchStore(store) {
|
|
411
|
+
batchStores.add(store);
|
|
412
|
+
}
|
|
413
|
+
function addManagedStore(store) {
|
|
414
|
+
batchManagedStores.add(store);
|
|
415
|
+
}
|
|
416
|
+
function isStoreManaged(store) {
|
|
417
|
+
return batchManagedStores.has(store);
|
|
418
|
+
}
|
|
419
|
+
var sharedBatchContext = {
|
|
420
|
+
get depth() {
|
|
421
|
+
return batchDepth;
|
|
422
|
+
},
|
|
423
|
+
get stores() {
|
|
424
|
+
return batchStores;
|
|
425
|
+
},
|
|
426
|
+
get managedStores() {
|
|
427
|
+
return batchManagedStores;
|
|
428
|
+
},
|
|
429
|
+
isBatching,
|
|
430
|
+
batch(store, fn) {
|
|
431
|
+
const wasAlreadyBatching = isStoreManaged(store);
|
|
432
|
+
incrementBatchDepth();
|
|
433
|
+
addBatchStore(store);
|
|
434
|
+
if (!wasAlreadyBatching && "beginBatch" in store) {
|
|
435
|
+
store.beginBatch();
|
|
436
|
+
addManagedStore(store);
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const result = fn();
|
|
440
|
+
decrementBatchDepth();
|
|
441
|
+
if (getBatchDepth() === 0) {
|
|
442
|
+
for (const s of getBatchManagedStores()) {
|
|
443
|
+
if ("flushNotifications" in s) {
|
|
444
|
+
s.flushNotifications();
|
|
445
|
+
}
|
|
446
|
+
if ("endBatch" in s) {
|
|
447
|
+
s.endBatch();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
clearBatchStores();
|
|
451
|
+
}
|
|
452
|
+
return result;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
decrementBatchDepth();
|
|
455
|
+
if (getBatchDepth() === 0) {
|
|
456
|
+
for (const s of getBatchManagedStores()) {
|
|
457
|
+
if ("endBatch" in s) {
|
|
458
|
+
s.endBatch();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
clearBatchStores();
|
|
462
|
+
}
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// src/types.ts
|
|
469
|
+
var StoreErrorCode = /* @__PURE__ */ ((StoreErrorCode2) => {
|
|
470
|
+
StoreErrorCode2["STORE_DESTROYED"] = "STORE_DESTROYED";
|
|
471
|
+
StoreErrorCode2["PLUGIN_EXISTS"] = "PLUGIN_EXISTS";
|
|
472
|
+
StoreErrorCode2["PLUGIN_DEPENDENCY_MISSING"] = "PLUGIN_DEPENDENCY_MISSING";
|
|
473
|
+
StoreErrorCode2["INVALID_STATE_UPDATE"] = "INVALID_STATE_UPDATE";
|
|
474
|
+
StoreErrorCode2["ACTION_ERROR"] = "ACTION_ERROR";
|
|
475
|
+
return StoreErrorCode2;
|
|
476
|
+
})(StoreErrorCode || {});
|
|
477
|
+
var StoreError = class extends Error {
|
|
478
|
+
constructor(code, message) {
|
|
479
|
+
super(message);
|
|
480
|
+
this.code = code;
|
|
481
|
+
this.name = "StoreError";
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// src/store.ts
|
|
486
|
+
var StoreImpl = class {
|
|
487
|
+
state;
|
|
488
|
+
initialState;
|
|
489
|
+
listeners = /* @__PURE__ */ new Set();
|
|
490
|
+
selectorSubscriptions = /* @__PURE__ */ new Map();
|
|
491
|
+
kernel;
|
|
492
|
+
destroyed = false;
|
|
493
|
+
batchDepth = 0;
|
|
494
|
+
pendingNotify = false;
|
|
495
|
+
queuedState = null;
|
|
496
|
+
queuedPrevState = null;
|
|
497
|
+
actions = {};
|
|
498
|
+
constructor(initialState, kernel) {
|
|
499
|
+
this.state = deepClone(initialState);
|
|
500
|
+
this.initialState = deepClone(initialState);
|
|
501
|
+
this.kernel = kernel;
|
|
502
|
+
this.kernel.onError((error) => {
|
|
503
|
+
console.error("Store error:", error);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
/** Get current state snapshot. */
|
|
507
|
+
getState() {
|
|
508
|
+
this.checkNotDestroyed();
|
|
509
|
+
return this.state;
|
|
510
|
+
}
|
|
511
|
+
/** Update state with partial object or function. */
|
|
512
|
+
setState(partial) {
|
|
513
|
+
this.checkNotDestroyed();
|
|
514
|
+
const prevState = this.state;
|
|
515
|
+
const update = typeof partial === "function" ? partial(this.state) : partial;
|
|
516
|
+
this.state = { ...this.state, ...update };
|
|
517
|
+
this.notifyOrQueue(this.state, prevState);
|
|
518
|
+
}
|
|
519
|
+
/** Deep merge state. */
|
|
520
|
+
merge(partial) {
|
|
521
|
+
this.checkNotDestroyed();
|
|
522
|
+
const prevState = this.state;
|
|
523
|
+
this.state = deepMerge(this.state, partial);
|
|
524
|
+
this.notifyOrQueue(this.state, prevState);
|
|
525
|
+
}
|
|
526
|
+
/** Reset to initial state. */
|
|
527
|
+
reset() {
|
|
528
|
+
this.checkNotDestroyed();
|
|
529
|
+
const prevState = this.state;
|
|
530
|
+
this.state = deepClone(this.initialState);
|
|
531
|
+
this.notifyOrQueue(this.state, prevState);
|
|
532
|
+
}
|
|
533
|
+
subscribe(arg1, arg2, arg3) {
|
|
534
|
+
this.checkNotDestroyed();
|
|
535
|
+
if (arg2 === void 0 && typeof arg1 === "function") {
|
|
536
|
+
const listener2 = arg1;
|
|
537
|
+
this.listeners.add(listener2);
|
|
538
|
+
return () => {
|
|
539
|
+
this.listeners.delete(listener2);
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const selector2 = arg1;
|
|
543
|
+
const listener = arg2;
|
|
544
|
+
const equalityFn = arg3 || shallowEqual;
|
|
545
|
+
let subscription = this.selectorSubscriptions.get(selector2);
|
|
546
|
+
if (!subscription) {
|
|
547
|
+
const initialValue = selector2(this.state);
|
|
548
|
+
subscription = {
|
|
549
|
+
value: initialValue,
|
|
550
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
551
|
+
selector: selector2,
|
|
552
|
+
equalityFn
|
|
553
|
+
};
|
|
554
|
+
this.selectorSubscriptions.set(selector2, subscription);
|
|
555
|
+
}
|
|
556
|
+
subscription.listeners.add(listener);
|
|
557
|
+
return () => {
|
|
558
|
+
const sub = this.selectorSubscriptions.get(selector2);
|
|
559
|
+
if (sub) {
|
|
560
|
+
sub.listeners.delete(listener);
|
|
561
|
+
if (sub.listeners.size === 0) {
|
|
562
|
+
this.selectorSubscriptions.delete(selector2);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
/** Register a plugin. */
|
|
568
|
+
use(plugin, options) {
|
|
569
|
+
this.checkNotDestroyed();
|
|
570
|
+
this.kernel.register(plugin, options, this);
|
|
571
|
+
return this;
|
|
572
|
+
}
|
|
573
|
+
/** Destroy store and cleanup. */
|
|
574
|
+
destroy() {
|
|
575
|
+
if (this.destroyed) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
this.destroyed = true;
|
|
579
|
+
this.kernel.destroy();
|
|
580
|
+
this.listeners.clear();
|
|
581
|
+
this.selectorSubscriptions.clear();
|
|
582
|
+
this.actions = {};
|
|
583
|
+
}
|
|
584
|
+
/** Add an action (for fluent builder pattern). */
|
|
585
|
+
addAction(name, fn) {
|
|
586
|
+
this.actions[name] = fn;
|
|
587
|
+
return this;
|
|
588
|
+
}
|
|
589
|
+
/** Get all actions. */
|
|
590
|
+
getActions() {
|
|
591
|
+
return this.actions;
|
|
592
|
+
}
|
|
593
|
+
/** Check if store is destroyed. */
|
|
594
|
+
checkNotDestroyed() {
|
|
595
|
+
if (this.destroyed) {
|
|
596
|
+
throw new Error("Cannot use destroyed store");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
/** Notify listeners or queue if batching. */
|
|
600
|
+
notifyOrQueue(state, prevState) {
|
|
601
|
+
const inGlobalBatch = sharedBatchContext.isBatching();
|
|
602
|
+
if (inGlobalBatch) {
|
|
603
|
+
const managedStores = sharedBatchContext.managedStores;
|
|
604
|
+
if (!managedStores.has(this)) {
|
|
605
|
+
managedStores.add(this);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (this.batchDepth > 0 || inGlobalBatch) {
|
|
609
|
+
this.queuedState = state;
|
|
610
|
+
if (!this.pendingNotify) {
|
|
611
|
+
this.queuedPrevState = prevState;
|
|
612
|
+
}
|
|
613
|
+
this.pendingNotify = true;
|
|
614
|
+
} else {
|
|
615
|
+
this.notify(state, prevState);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/** Notify all listeners. */
|
|
619
|
+
notify(state, prevState) {
|
|
620
|
+
this.kernel.emitStateChange(state, prevState);
|
|
621
|
+
for (const listener of this.listeners) {
|
|
622
|
+
try {
|
|
623
|
+
listener(state, prevState);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
this.kernel.emitError(error);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
for (const subscription of this.selectorSubscriptions.values()) {
|
|
629
|
+
const newValue = subscription.selector(state);
|
|
630
|
+
if (!subscription.equalityFn(newValue, subscription.value)) {
|
|
631
|
+
const prevValue = subscription.value;
|
|
632
|
+
subscription.value = newValue;
|
|
633
|
+
for (const listener of subscription.listeners) {
|
|
634
|
+
try {
|
|
635
|
+
listener(newValue, prevValue);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
this.kernel.emitError(error);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/** Flush pending notifications (end of batch). */
|
|
644
|
+
flushNotifications() {
|
|
645
|
+
if (this.pendingNotify) {
|
|
646
|
+
this.notify(this.queuedState, this.queuedPrevState);
|
|
647
|
+
this.pendingNotify = false;
|
|
648
|
+
this.queuedState = null;
|
|
649
|
+
this.queuedPrevState = null;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/** Start batching. */
|
|
653
|
+
beginBatch() {
|
|
654
|
+
this.batchDepth++;
|
|
655
|
+
}
|
|
656
|
+
/** End batching. */
|
|
657
|
+
endBatch() {
|
|
658
|
+
this.batchDepth--;
|
|
659
|
+
if (this.batchDepth === 0) {
|
|
660
|
+
this.flushNotifications();
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
var StoreBuilderImpl = class {
|
|
665
|
+
store;
|
|
666
|
+
constructor(initialState, kernel) {
|
|
667
|
+
this.store = new StoreImpl(initialState, kernel);
|
|
668
|
+
}
|
|
669
|
+
getState() {
|
|
670
|
+
return this.store.getState();
|
|
671
|
+
}
|
|
672
|
+
setState(partial) {
|
|
673
|
+
this.store.setState(partial);
|
|
674
|
+
}
|
|
675
|
+
merge(partial) {
|
|
676
|
+
this.store.merge(partial);
|
|
677
|
+
}
|
|
678
|
+
reset() {
|
|
679
|
+
this.store.reset();
|
|
680
|
+
}
|
|
681
|
+
subscribe(arg1, arg2, arg3) {
|
|
682
|
+
if (arg2 === void 0) {
|
|
683
|
+
return this.store.subscribe(arg1);
|
|
684
|
+
} else {
|
|
685
|
+
return this.store.subscribe(arg1, arg2, arg3);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
use(plugin, options) {
|
|
689
|
+
this.store.use(plugin, options);
|
|
690
|
+
return this;
|
|
691
|
+
}
|
|
692
|
+
destroy() {
|
|
693
|
+
this.store.destroy();
|
|
694
|
+
}
|
|
695
|
+
action(name, fn) {
|
|
696
|
+
this.store.addAction(name, fn);
|
|
697
|
+
this[name] = (...args) => {
|
|
698
|
+
const currentState = this.getState();
|
|
699
|
+
const result = fn(currentState, ...args);
|
|
700
|
+
if (result instanceof Promise) {
|
|
701
|
+
return result.then((partial) => {
|
|
702
|
+
this.setState(partial);
|
|
703
|
+
return partial;
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
this.setState(result);
|
|
707
|
+
return result;
|
|
708
|
+
};
|
|
709
|
+
return this;
|
|
710
|
+
}
|
|
711
|
+
/** Add action (internal use). */
|
|
712
|
+
addAction(name, fn) {
|
|
713
|
+
this.store.addAction(name, fn);
|
|
714
|
+
return this;
|
|
715
|
+
}
|
|
716
|
+
/** Get actions (internal use). */
|
|
717
|
+
getActions() {
|
|
718
|
+
return this.store.getActions();
|
|
719
|
+
}
|
|
720
|
+
/** Start batching. */
|
|
721
|
+
beginBatch() {
|
|
722
|
+
this.store.beginBatch();
|
|
723
|
+
}
|
|
724
|
+
/** End batching. */
|
|
725
|
+
endBatch() {
|
|
726
|
+
this.store.endBatch();
|
|
727
|
+
}
|
|
728
|
+
/** Flush pending notifications. */
|
|
729
|
+
flushNotifications() {
|
|
730
|
+
this.store.flushNotifications();
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
function createStore(initialState, actions) {
|
|
734
|
+
const kernel = createKernel({ name: "store" });
|
|
735
|
+
const state = { ...initialState };
|
|
736
|
+
const extractedActions = {};
|
|
737
|
+
for (const key in state) {
|
|
738
|
+
if (isFunction(state[key]) && key.startsWith("$")) {
|
|
739
|
+
extractedActions[key.substring(1)] = state[key];
|
|
740
|
+
delete state[key];
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const allActions = { ...extractedActions, ...actions };
|
|
744
|
+
const builder = new StoreBuilderImpl(state, kernel);
|
|
745
|
+
if (allActions) {
|
|
746
|
+
for (const [name, fn] of Object.entries(allActions)) {
|
|
747
|
+
builder.addAction(name, fn);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const actionList = builder.getActions();
|
|
751
|
+
for (const [name, fn] of Object.entries(actionList)) {
|
|
752
|
+
builder[name] = (...args) => {
|
|
753
|
+
const currentState = builder.getState();
|
|
754
|
+
const result = fn(currentState, ...args);
|
|
755
|
+
if (result instanceof Promise) {
|
|
756
|
+
return result.then((partial) => {
|
|
757
|
+
builder.setState(partial);
|
|
758
|
+
return partial;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
builder.setState(result);
|
|
762
|
+
return result;
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
kernel.initializeAll(builder).catch((error) => {
|
|
766
|
+
console.error("Error initializing plugins:", error);
|
|
767
|
+
});
|
|
768
|
+
return new Proxy(builder, {
|
|
769
|
+
get(target, prop) {
|
|
770
|
+
if (prop in target) {
|
|
771
|
+
return target[prop];
|
|
772
|
+
}
|
|
773
|
+
const store = target.store;
|
|
774
|
+
if (store && typeof store[prop] === "function") {
|
|
775
|
+
return store[prop].bind(store);
|
|
776
|
+
}
|
|
777
|
+
if (store && prop in store) {
|
|
778
|
+
return store[prop];
|
|
779
|
+
}
|
|
780
|
+
return void 0;
|
|
781
|
+
},
|
|
782
|
+
set(target, prop, value) {
|
|
783
|
+
if (prop in target || typeof value === "function") {
|
|
784
|
+
target[prop] = value;
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
const store = target.store;
|
|
788
|
+
if (store) {
|
|
789
|
+
store[prop] = value;
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
function batch(fn) {
|
|
797
|
+
return fn();
|
|
798
|
+
}
|
|
799
|
+
function useStore(store, selector2 = identity, equalityFn = shallowEqual) {
|
|
800
|
+
const selectorRef = react.useRef(selector2);
|
|
801
|
+
const equalityFnRef = react.useRef(equalityFn);
|
|
802
|
+
const storeRef = react.useRef(store);
|
|
803
|
+
if (selectorRef.current !== selector2) {
|
|
804
|
+
selectorRef.current = selector2;
|
|
805
|
+
}
|
|
806
|
+
if (equalityFnRef.current !== equalityFn) {
|
|
807
|
+
equalityFnRef.current = equalityFn;
|
|
808
|
+
}
|
|
809
|
+
if (storeRef.current !== store) {
|
|
810
|
+
storeRef.current = store;
|
|
811
|
+
}
|
|
812
|
+
const getSnapshot = () => {
|
|
813
|
+
const state = storeRef.current.getState();
|
|
814
|
+
return selectorRef.current(state);
|
|
815
|
+
};
|
|
816
|
+
const getServerSnapshot = getSnapshot;
|
|
817
|
+
const subscribe = (callback) => {
|
|
818
|
+
const unsubscribe = storeRef.current.subscribe(
|
|
819
|
+
(state) => selectorRef.current(state),
|
|
820
|
+
(value, prevValue) => {
|
|
821
|
+
if (!equalityFnRef.current(value, prevValue)) {
|
|
822
|
+
callback();
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
equalityFnRef.current
|
|
826
|
+
);
|
|
827
|
+
return unsubscribe;
|
|
828
|
+
};
|
|
829
|
+
const selectedState = react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
830
|
+
react.useDebugValue(selectedState);
|
|
831
|
+
return selectedState;
|
|
832
|
+
}
|
|
833
|
+
function useCreateStore(initialState) {
|
|
834
|
+
const storeRef = react.useRef();
|
|
835
|
+
if (storeRef.current === void 0) {
|
|
836
|
+
storeRef.current = createStore(initialState);
|
|
837
|
+
}
|
|
838
|
+
react.useEffect(() => {
|
|
839
|
+
return () => {
|
|
840
|
+
if (storeRef.current) {
|
|
841
|
+
storeRef.current.destroy();
|
|
842
|
+
storeRef.current = void 0;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
}, []);
|
|
846
|
+
return storeRef.current;
|
|
847
|
+
}
|
|
848
|
+
function useAction(store, actionName) {
|
|
849
|
+
const storeRef = react.useRef(store);
|
|
850
|
+
if (storeRef.current !== store) {
|
|
851
|
+
storeRef.current = store;
|
|
852
|
+
}
|
|
853
|
+
const action = storeRef.current[actionName];
|
|
854
|
+
const actionRef = react.useRef(action);
|
|
855
|
+
if (actionRef.current !== action) {
|
|
856
|
+
actionRef.current = action;
|
|
857
|
+
}
|
|
858
|
+
return actionRef.current;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/plugins/persist.ts
|
|
862
|
+
var defaultStorage = {
|
|
863
|
+
getItem: (key) => {
|
|
864
|
+
if (typeof window === "undefined") return null;
|
|
865
|
+
try {
|
|
866
|
+
return window.localStorage.getItem(key);
|
|
867
|
+
} catch {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
setItem: (key, value) => {
|
|
872
|
+
if (typeof window === "undefined") return;
|
|
873
|
+
try {
|
|
874
|
+
window.localStorage.setItem(key, value);
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
/* c8 ignore next 3 */
|
|
879
|
+
removeItem: () => {
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
var sessionStorage = {
|
|
883
|
+
getItem: (key) => {
|
|
884
|
+
if (typeof window === "undefined") return null;
|
|
885
|
+
try {
|
|
886
|
+
return window.sessionStorage.getItem(key);
|
|
887
|
+
} catch {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
setItem: (key, value) => {
|
|
892
|
+
if (typeof window === "undefined") return;
|
|
893
|
+
try {
|
|
894
|
+
window.sessionStorage.setItem(key, value);
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
removeItem: (key) => {
|
|
899
|
+
if (typeof window === "undefined") return;
|
|
900
|
+
try {
|
|
901
|
+
window.sessionStorage.removeItem(key);
|
|
902
|
+
} catch {
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
function persist(options) {
|
|
907
|
+
const { key, storage = defaultStorage, whitelist, blacklist } = options;
|
|
908
|
+
return {
|
|
909
|
+
name: "persist",
|
|
910
|
+
version: "1.0.0",
|
|
911
|
+
install(store) {
|
|
912
|
+
try {
|
|
913
|
+
const saved = storage.getItem(key);
|
|
914
|
+
if (saved) {
|
|
915
|
+
const parsed = JSON.parse(saved);
|
|
916
|
+
store.merge(parsed);
|
|
917
|
+
}
|
|
918
|
+
} catch (error) {
|
|
919
|
+
console.error(`Failed to hydrate state from '${key}':`, error);
|
|
920
|
+
}
|
|
921
|
+
store.subscribe((state) => {
|
|
922
|
+
try {
|
|
923
|
+
let toSave = state;
|
|
924
|
+
if (whitelist) {
|
|
925
|
+
toSave = pick(state, whitelist);
|
|
926
|
+
} else if (blacklist) {
|
|
927
|
+
toSave = omit(state, blacklist);
|
|
928
|
+
}
|
|
929
|
+
storage.setItem(key, JSON.stringify(toSave));
|
|
930
|
+
} catch (error) {
|
|
931
|
+
console.error(`Failed to persist state to '${key}':`, error);
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
},
|
|
935
|
+
onDestroy() {
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function createStorage(storage) {
|
|
940
|
+
return storage;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/plugins/devtools.ts
|
|
944
|
+
function hasDevtools() {
|
|
945
|
+
return typeof window !== "undefined" && !!window.__REDUX_DEVTOOLS_EXTENSION__;
|
|
946
|
+
}
|
|
947
|
+
function getDevtools() {
|
|
948
|
+
if (typeof window === "undefined") return null;
|
|
949
|
+
return window.__REDUX_DEVTOOLS_EXTENSION__ || null;
|
|
950
|
+
}
|
|
951
|
+
function devtools(options = {}) {
|
|
952
|
+
const { name = "OxogState Store", enabled = true, maxAge = 50 } = options;
|
|
953
|
+
let connection = null;
|
|
954
|
+
let history2 = [];
|
|
955
|
+
return {
|
|
956
|
+
name: "devtools",
|
|
957
|
+
version: "1.0.0",
|
|
958
|
+
install(store) {
|
|
959
|
+
if (!enabled || !hasDevtools()) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const extension = getDevtools();
|
|
963
|
+
if (!extension) return;
|
|
964
|
+
connection = extension.connect({ name });
|
|
965
|
+
connection.init(store.getState());
|
|
966
|
+
store.subscribe((state, prevState) => {
|
|
967
|
+
if (!connection) return;
|
|
968
|
+
history2.push({ state, action: "UPDATE" });
|
|
969
|
+
if (history2.length > maxAge) {
|
|
970
|
+
history2.shift();
|
|
971
|
+
}
|
|
972
|
+
connection.send({ type: "UPDATE", prev: prevState }, state);
|
|
973
|
+
});
|
|
974
|
+
connection.subscribe((message) => {
|
|
975
|
+
if (message.type === "DISPATCH" && message.payload) {
|
|
976
|
+
switch (message.payload.type) {
|
|
977
|
+
case "JUMP_TO_STATE":
|
|
978
|
+
case "JUMP_TO_ACTION": {
|
|
979
|
+
const index = message.payload.type === "JUMP_TO_ACTION" ? history2.findIndex((h) => h.action === message.payload.action) : message.payload.index;
|
|
980
|
+
if (index >= 0 && index < history2.length) {
|
|
981
|
+
const entry = history2[index];
|
|
982
|
+
if (entry) {
|
|
983
|
+
store.setState(entry.state);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
case "COMMIT": {
|
|
989
|
+
connection.init(store.getState());
|
|
990
|
+
history2 = [];
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
case "ROLLBACK": {
|
|
994
|
+
if (history2.length > 1) {
|
|
995
|
+
const previous = history2[history2.length - 2];
|
|
996
|
+
if (previous) {
|
|
997
|
+
store.setState(previous.state);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
case "RESET": {
|
|
1003
|
+
store.reset();
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
},
|
|
1010
|
+
onDestroy() {
|
|
1011
|
+
connection = null;
|
|
1012
|
+
history2 = [];
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/plugins/history.ts
|
|
1018
|
+
function history(options = {}) {
|
|
1019
|
+
const { limit = 50, keys } = options;
|
|
1020
|
+
let historyState = null;
|
|
1021
|
+
let isInternalUpdate = false;
|
|
1022
|
+
return {
|
|
1023
|
+
name: "history",
|
|
1024
|
+
version: "1.0.0",
|
|
1025
|
+
install(store) {
|
|
1026
|
+
historyState = {
|
|
1027
|
+
past: [],
|
|
1028
|
+
present: store.getState(),
|
|
1029
|
+
future: []
|
|
1030
|
+
};
|
|
1031
|
+
store.subscribe((state) => {
|
|
1032
|
+
if (!historyState || isInternalUpdate) return;
|
|
1033
|
+
const previousPresent = historyState.present;
|
|
1034
|
+
const newPresent = state;
|
|
1035
|
+
let hasChanged = true;
|
|
1036
|
+
if (keys && keys.length > 0) {
|
|
1037
|
+
hasChanged = keys.some((key) => {
|
|
1038
|
+
return previousPresent[key] !== newPresent[key];
|
|
1039
|
+
});
|
|
1040
|
+
} else {
|
|
1041
|
+
hasChanged = previousPresent !== newPresent;
|
|
1042
|
+
}
|
|
1043
|
+
if (!hasChanged) return;
|
|
1044
|
+
historyState.past.push(previousPresent);
|
|
1045
|
+
if (historyState.past.length > limit) {
|
|
1046
|
+
historyState.past.shift();
|
|
1047
|
+
}
|
|
1048
|
+
historyState.present = newPresent;
|
|
1049
|
+
historyState.future = [];
|
|
1050
|
+
});
|
|
1051
|
+
store.undo = () => {
|
|
1052
|
+
if (!historyState || historyState.past.length === 0) return;
|
|
1053
|
+
const previous = historyState.past.pop();
|
|
1054
|
+
historyState.future.push(historyState.present);
|
|
1055
|
+
historyState.present = previous;
|
|
1056
|
+
isInternalUpdate = true;
|
|
1057
|
+
store.setState(previous);
|
|
1058
|
+
isInternalUpdate = false;
|
|
1059
|
+
};
|
|
1060
|
+
store.redo = () => {
|
|
1061
|
+
if (!historyState || historyState.future.length === 0) return;
|
|
1062
|
+
const next = historyState.future.pop();
|
|
1063
|
+
historyState.past.push(historyState.present);
|
|
1064
|
+
historyState.present = next;
|
|
1065
|
+
isInternalUpdate = true;
|
|
1066
|
+
store.setState(next);
|
|
1067
|
+
isInternalUpdate = false;
|
|
1068
|
+
};
|
|
1069
|
+
store.clearHistory = () => {
|
|
1070
|
+
if (!historyState) return;
|
|
1071
|
+
historyState.past = [];
|
|
1072
|
+
historyState.future = [];
|
|
1073
|
+
};
|
|
1074
|
+
store.canUndo = () => {
|
|
1075
|
+
return historyState !== null && historyState.past.length > 0;
|
|
1076
|
+
};
|
|
1077
|
+
store.canRedo = () => {
|
|
1078
|
+
return historyState !== null && historyState.future.length > 0;
|
|
1079
|
+
};
|
|
1080
|
+
},
|
|
1081
|
+
onDestroy() {
|
|
1082
|
+
historyState = null;
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
function hasHistory(store) {
|
|
1087
|
+
return typeof store.undo === "function" && typeof store.redo === "function";
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/plugins/sync.ts
|
|
1091
|
+
function sync(options = {}) {
|
|
1092
|
+
const { channel = "oxog-state" } = options;
|
|
1093
|
+
let broadcastChannel = null;
|
|
1094
|
+
let currentStore = null;
|
|
1095
|
+
let isLocalUpdate = false;
|
|
1096
|
+
return {
|
|
1097
|
+
name: "sync",
|
|
1098
|
+
version: "1.0.0",
|
|
1099
|
+
install(store) {
|
|
1100
|
+
currentStore = store;
|
|
1101
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
1102
|
+
console.warn("BroadcastChannel is not supported in this environment");
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
broadcastChannel = new BroadcastChannel(channel);
|
|
1106
|
+
broadcastChannel.onmessage = (event) => {
|
|
1107
|
+
if (!isLocalUpdate) {
|
|
1108
|
+
try {
|
|
1109
|
+
store.setState(event.data);
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
console.error("Error applying sync state:", error);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
},
|
|
1116
|
+
onInit() {
|
|
1117
|
+
if (broadcastChannel && currentStore) {
|
|
1118
|
+
isLocalUpdate = true;
|
|
1119
|
+
try {
|
|
1120
|
+
broadcastChannel.postMessage(currentStore.getState());
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
console.error("Error broadcasting initial state:", error);
|
|
1123
|
+
}
|
|
1124
|
+
isLocalUpdate = false;
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1127
|
+
onDestroy() {
|
|
1128
|
+
if (broadcastChannel) {
|
|
1129
|
+
broadcastChannel.close();
|
|
1130
|
+
broadcastChannel = null;
|
|
1131
|
+
}
|
|
1132
|
+
currentStore = null;
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
function triggerSync(store, channel = "oxog-state") {
|
|
1137
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
1138
|
+
console.warn("BroadcastChannel is not supported in this environment");
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const broadcastChannel = new BroadcastChannel(channel);
|
|
1142
|
+
broadcastChannel.postMessage(store.getState());
|
|
1143
|
+
broadcastChannel.close();
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/plugins/immer.ts
|
|
1147
|
+
function createProxy(base, copies) {
|
|
1148
|
+
return new Proxy(base || {}, {
|
|
1149
|
+
get(target, key) {
|
|
1150
|
+
const value = target[key];
|
|
1151
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1152
|
+
if (copies.has(value)) {
|
|
1153
|
+
return createProxy(copies.get(value), copies);
|
|
1154
|
+
}
|
|
1155
|
+
return createProxy(value, copies);
|
|
1156
|
+
}
|
|
1157
|
+
return value;
|
|
1158
|
+
},
|
|
1159
|
+
set(target, key, value) {
|
|
1160
|
+
if (!copies.has(target)) {
|
|
1161
|
+
copies.set(target, { ...target });
|
|
1162
|
+
}
|
|
1163
|
+
const copy = copies.get(target);
|
|
1164
|
+
copy[key] = value;
|
|
1165
|
+
return true;
|
|
1166
|
+
},
|
|
1167
|
+
deleteProperty(target, key) {
|
|
1168
|
+
if (!copies.has(target)) {
|
|
1169
|
+
copies.set(target, { ...target });
|
|
1170
|
+
}
|
|
1171
|
+
const copy = copies.get(target);
|
|
1172
|
+
delete copy[key];
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
function finalize(base, copies) {
|
|
1178
|
+
if (!copies.has(base)) {
|
|
1179
|
+
return deepClone2(base);
|
|
1180
|
+
}
|
|
1181
|
+
const copy = copies.get(base);
|
|
1182
|
+
for (const key in copy) {
|
|
1183
|
+
if (Object.prototype.hasOwnProperty.call(copy, key)) {
|
|
1184
|
+
const value = copy[key];
|
|
1185
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1186
|
+
copy[key] = finalize(value, copies);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return copy;
|
|
1191
|
+
}
|
|
1192
|
+
function deepClone2(value) {
|
|
1193
|
+
if (value === null || typeof value !== "object") {
|
|
1194
|
+
return value;
|
|
1195
|
+
}
|
|
1196
|
+
if (Array.isArray(value)) {
|
|
1197
|
+
return value.map((v) => deepClone2(v));
|
|
1198
|
+
}
|
|
1199
|
+
const cloned = {};
|
|
1200
|
+
for (const key in value) {
|
|
1201
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
1202
|
+
cloned[key] = deepClone2(value[key]);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
return cloned;
|
|
1206
|
+
}
|
|
1207
|
+
function produce(base, recipe) {
|
|
1208
|
+
const copies = /* @__PURE__ */ new Map();
|
|
1209
|
+
const draft = createProxy(base, copies);
|
|
1210
|
+
recipe(draft);
|
|
1211
|
+
return finalize(base, copies);
|
|
1212
|
+
}
|
|
1213
|
+
function immer() {
|
|
1214
|
+
return {
|
|
1215
|
+
name: "immer",
|
|
1216
|
+
version: "1.0.0",
|
|
1217
|
+
install(store) {
|
|
1218
|
+
const originalSetState = store.setState.bind(store);
|
|
1219
|
+
store.setState = (update) => {
|
|
1220
|
+
if (typeof update === "function") {
|
|
1221
|
+
const newState = produce(store.getState(), update);
|
|
1222
|
+
originalSetState(newState);
|
|
1223
|
+
} else {
|
|
1224
|
+
originalSetState(update);
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/plugins/selector.ts
|
|
1232
|
+
function selector(options) {
|
|
1233
|
+
const { selectors } = options;
|
|
1234
|
+
return {
|
|
1235
|
+
name: "selector",
|
|
1236
|
+
version: "1.0.0",
|
|
1237
|
+
install(store) {
|
|
1238
|
+
const originalGetState = store.getState.bind(store);
|
|
1239
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1240
|
+
const computedState = new Proxy({}, {
|
|
1241
|
+
get(_, prop) {
|
|
1242
|
+
const selectorFn = selectors[prop];
|
|
1243
|
+
if (selectorFn) {
|
|
1244
|
+
const currentState = originalGetState();
|
|
1245
|
+
const cacheKey = prop;
|
|
1246
|
+
const cached = cache.get(cacheKey);
|
|
1247
|
+
if (cached && cached.state === currentState) {
|
|
1248
|
+
return cached.value;
|
|
1249
|
+
}
|
|
1250
|
+
const value = selectorFn(currentState);
|
|
1251
|
+
cache.set(cacheKey, { value, state: currentState });
|
|
1252
|
+
return value;
|
|
1253
|
+
}
|
|
1254
|
+
return originalGetState()[prop];
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
store.getState = () => computedState;
|
|
1258
|
+
store.subscribe(() => {
|
|
1259
|
+
cache.clear();
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
exports.StoreError = StoreError;
|
|
1266
|
+
exports.StoreErrorCode = StoreErrorCode;
|
|
1267
|
+
exports.batch = batch;
|
|
1268
|
+
exports.createStorage = createStorage;
|
|
1269
|
+
exports.createStore = createStore;
|
|
1270
|
+
exports.deepClone = deepClone;
|
|
1271
|
+
exports.deepEqual = deepEqual;
|
|
1272
|
+
exports.deepMerge = deepMerge;
|
|
1273
|
+
exports.devtools = devtools;
|
|
1274
|
+
exports.hasHistory = hasHistory;
|
|
1275
|
+
exports.history = history;
|
|
1276
|
+
exports.identity = identity;
|
|
1277
|
+
exports.immer = immer;
|
|
1278
|
+
exports.isFunction = isFunction;
|
|
1279
|
+
exports.omit = omit;
|
|
1280
|
+
exports.persist = persist;
|
|
1281
|
+
exports.pick = pick;
|
|
1282
|
+
exports.produce = produce;
|
|
1283
|
+
exports.selector = selector;
|
|
1284
|
+
exports.sessionStorage = sessionStorage;
|
|
1285
|
+
exports.shallowEqual = shallowEqual;
|
|
1286
|
+
exports.sync = sync;
|
|
1287
|
+
exports.triggerSync = triggerSync;
|
|
1288
|
+
exports.useAction = useAction;
|
|
1289
|
+
exports.useCreateStore = useCreateStore;
|
|
1290
|
+
exports.useStore = useStore;
|
|
1291
|
+
//# sourceMappingURL=index.cjs.map
|
|
1292
|
+
//# sourceMappingURL=index.cjs.map
|