@hurum/core 0.0.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/index.cjs ADDED
@@ -0,0 +1,1421 @@
1
+ 'use strict';
2
+
3
+ // src/events.ts
4
+ var EVENT_CREATOR_BRAND = /* @__PURE__ */ Symbol("hurum/event-creator");
5
+ function Event() {
6
+ return {};
7
+ }
8
+ function Events(prefix, events) {
9
+ const result = {};
10
+ for (const key of Object.keys(events)) {
11
+ const type = `${prefix}/${key}`;
12
+ const creator = function(payload) {
13
+ return { ...payload, type };
14
+ };
15
+ Object.defineProperty(creator, "type", {
16
+ value: type,
17
+ writable: false,
18
+ enumerable: true,
19
+ configurable: false
20
+ });
21
+ Object.defineProperty(creator, EVENT_CREATOR_BRAND, {
22
+ value: true,
23
+ writable: false,
24
+ enumerable: false,
25
+ configurable: false
26
+ });
27
+ result[key] = creator;
28
+ }
29
+ return result;
30
+ }
31
+ function isEventCreator(value) {
32
+ return typeof value === "function" && EVENT_CREATOR_BRAND in value && value[EVENT_CREATOR_BRAND] === true;
33
+ }
34
+
35
+ // src/command-executor.ts
36
+ var COMMAND_BRAND = /* @__PURE__ */ Symbol("hurum/command");
37
+ var EXECUTOR_BRAND = /* @__PURE__ */ Symbol("hurum/executor");
38
+ function CommandExecutor(nameOrFn, maybeFn) {
39
+ const name = typeof nameOrFn === "string" ? nameOrFn : nameOrFn.name || void 0;
40
+ const fn = typeof nameOrFn === "string" ? maybeFn : nameOrFn;
41
+ const commandId = /* @__PURE__ */ Symbol("hurum/command-id");
42
+ const command = {
43
+ [COMMAND_BRAND]: commandId,
44
+ ...name ? { name } : {}
45
+ };
46
+ const executor = {
47
+ [EXECUTOR_BRAND]: true,
48
+ __command: command,
49
+ __fn: fn
50
+ };
51
+ return [command, executor];
52
+ }
53
+ CommandExecutor.passthrough = function passthrough(nameOrEventCreator, eventCreatorOrTransform, maybeTransform) {
54
+ let name;
55
+ let eventCreator;
56
+ let transform;
57
+ if (typeof nameOrEventCreator === "string") {
58
+ name = nameOrEventCreator;
59
+ eventCreator = eventCreatorOrTransform;
60
+ transform = maybeTransform;
61
+ } else {
62
+ eventCreator = nameOrEventCreator;
63
+ transform = eventCreatorOrTransform;
64
+ }
65
+ const commandId = /* @__PURE__ */ Symbol("hurum/command-id");
66
+ const command = {
67
+ [COMMAND_BRAND]: commandId,
68
+ ...name ? { name } : {}
69
+ };
70
+ const executorFn = (input, { emit }) => {
71
+ const payload = transform ? transform(input) : input;
72
+ emit(eventCreator(payload));
73
+ };
74
+ const executor = {
75
+ [EXECUTOR_BRAND]: true,
76
+ __command: command,
77
+ __fn: executorFn
78
+ };
79
+ return [command, executor];
80
+ };
81
+ function getCommandId(command) {
82
+ return command[COMMAND_BRAND];
83
+ }
84
+ function getExecutorCommand(executor) {
85
+ return executor.__command;
86
+ }
87
+ function getExecutorFn(executor) {
88
+ return executor.__fn;
89
+ }
90
+
91
+ // src/intent.ts
92
+ var INTENT_BRAND = /* @__PURE__ */ Symbol("hurum/intent");
93
+ var INTENTS_BRAND = /* @__PURE__ */ Symbol("hurum/intents");
94
+ var PREPARED_INTENT_BRAND = /* @__PURE__ */ Symbol("hurum/prepared-intent");
95
+ function createIntentAction(mode, commands) {
96
+ const action = function(payload) {
97
+ return {
98
+ [PREPARED_INTENT_BRAND]: true,
99
+ intent: action,
100
+ payload
101
+ };
102
+ };
103
+ Object.defineProperty(action, INTENT_BRAND, {
104
+ value: true,
105
+ writable: false,
106
+ enumerable: false,
107
+ configurable: false
108
+ });
109
+ Object.defineProperty(action, "commands", {
110
+ value: commands,
111
+ writable: false,
112
+ enumerable: true,
113
+ configurable: false
114
+ });
115
+ Object.defineProperty(action, "mode", {
116
+ value: mode,
117
+ writable: false,
118
+ enumerable: true,
119
+ configurable: false
120
+ });
121
+ Object.defineProperty(action, "name", {
122
+ value: void 0,
123
+ writable: false,
124
+ enumerable: true,
125
+ configurable: true
126
+ });
127
+ return action;
128
+ }
129
+ function Intent(...commands) {
130
+ return createIntentAction("sequential", commands);
131
+ }
132
+ Intent.all = function all(...commands) {
133
+ return createIntentAction("all", commands);
134
+ };
135
+ Intent.allSettled = function allSettled(...commands) {
136
+ return createIntentAction("allSettled", commands);
137
+ };
138
+ function Intents(prefix, intents) {
139
+ const container = { ...intents };
140
+ for (const [key, descriptor] of Object.entries(container)) {
141
+ if (isIntentDescriptor(descriptor)) {
142
+ Object.defineProperty(descriptor, "name", {
143
+ value: `${prefix}/${key}`,
144
+ writable: false,
145
+ enumerable: true,
146
+ configurable: false
147
+ });
148
+ }
149
+ }
150
+ Object.defineProperty(container, INTENTS_BRAND, {
151
+ value: true,
152
+ writable: false,
153
+ enumerable: false,
154
+ configurable: false
155
+ });
156
+ Object.defineProperty(container, "__prefix", {
157
+ value: prefix,
158
+ writable: false,
159
+ enumerable: false,
160
+ configurable: false
161
+ });
162
+ return container;
163
+ }
164
+ function isIntentDescriptor(value) {
165
+ return (typeof value === "object" || typeof value === "function") && value !== null && INTENT_BRAND in value;
166
+ }
167
+ function isPreparedIntent(value) {
168
+ return typeof value === "object" && value !== null && PREPARED_INTENT_BRAND in value;
169
+ }
170
+
171
+ // src/computed.ts
172
+ function createTrackingProxy(state, accessed) {
173
+ return new Proxy(state, {
174
+ get(target, prop, receiver) {
175
+ if (typeof prop === "string") {
176
+ accessed.add(prop);
177
+ }
178
+ return Reflect.get(target, prop, receiver);
179
+ }
180
+ });
181
+ }
182
+ function initializeComputedNodes(rawState, computedDef) {
183
+ if (!computedDef) return [];
184
+ const nodes = [];
185
+ const nodesByName = /* @__PURE__ */ new Map();
186
+ for (const [name, fn] of Object.entries(computedDef)) {
187
+ const accessed = /* @__PURE__ */ new Set();
188
+ const trackingProxy = createTrackingProxy(rawState, accessed);
189
+ const value = fn(trackingProxy);
190
+ const node = {
191
+ name,
192
+ fn,
193
+ deps: accessed,
194
+ value,
195
+ prevValue: value
196
+ };
197
+ nodes.push(node);
198
+ nodesByName.set(name, node);
199
+ }
200
+ const sorted = topologicalSort(nodes, nodesByName);
201
+ return sorted;
202
+ }
203
+ function topologicalSort(nodes, nodesByName) {
204
+ const visited = /* @__PURE__ */ new Set();
205
+ const visiting = /* @__PURE__ */ new Set();
206
+ const sorted = [];
207
+ function visit(node) {
208
+ if (visited.has(node.name)) return;
209
+ if (visiting.has(node.name)) {
210
+ throw new Error(
211
+ `Circular dependency detected in computed fields: ${node.name}`
212
+ );
213
+ }
214
+ visiting.add(node.name);
215
+ for (const dep of node.deps) {
216
+ const depNode = nodesByName.get(dep);
217
+ if (depNode) {
218
+ visit(depNode);
219
+ }
220
+ }
221
+ visiting.delete(node.name);
222
+ visited.add(node.name);
223
+ sorted.push(node);
224
+ }
225
+ for (const node of nodes) {
226
+ visit(node);
227
+ }
228
+ return sorted;
229
+ }
230
+ function evaluateComputedNodes(nodes, rawState, changedKeys) {
231
+ const values = {};
232
+ let anyChanged = false;
233
+ for (const node of nodes) {
234
+ let needsReeval = false;
235
+ for (const dep of node.deps) {
236
+ if (changedKeys.has(dep)) {
237
+ needsReeval = true;
238
+ break;
239
+ }
240
+ }
241
+ if (needsReeval) {
242
+ const accessed = /* @__PURE__ */ new Set();
243
+ const trackingProxy = createTrackingProxy(rawState, accessed);
244
+ const newValue = node.fn(trackingProxy);
245
+ node.deps = accessed;
246
+ if (!structuralEqual(node.value, newValue)) {
247
+ node.prevValue = node.value;
248
+ node.value = newValue;
249
+ anyChanged = true;
250
+ changedKeys.add(node.name);
251
+ }
252
+ }
253
+ values[node.name] = node.value;
254
+ }
255
+ return { values, changed: anyChanged };
256
+ }
257
+ function structuralEqual(a, b) {
258
+ if (a === b) return true;
259
+ if (a === null || b === null) return false;
260
+ if (typeof a !== typeof b) return false;
261
+ if (typeof a !== "object") return false;
262
+ if (Array.isArray(a) && Array.isArray(b)) {
263
+ if (a.length !== b.length) return false;
264
+ for (let i = 0; i < a.length; i++) {
265
+ if (!structuralEqual(a[i], b[i])) return false;
266
+ }
267
+ return true;
268
+ }
269
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
270
+ const keysA = Object.keys(a);
271
+ const keysB = Object.keys(b);
272
+ if (keysA.length !== keysB.length) return false;
273
+ for (const key of keysA) {
274
+ if (!structuralEqual(a[key], b[key])) return false;
275
+ }
276
+ return true;
277
+ }
278
+
279
+ // src/nested.ts
280
+ var NESTED_BRAND = /* @__PURE__ */ Symbol("hurum/nested");
281
+ function Nested(store) {
282
+ return {
283
+ [NESTED_BRAND]: true,
284
+ kind: "single",
285
+ store
286
+ };
287
+ }
288
+ Nested.array = function array(store) {
289
+ return {
290
+ [NESTED_BRAND]: true,
291
+ kind: "array",
292
+ store
293
+ };
294
+ };
295
+ Nested.map = function map(store) {
296
+ return {
297
+ [NESTED_BRAND]: true,
298
+ kind: "map",
299
+ store
300
+ };
301
+ };
302
+ function isNestedMarker(value) {
303
+ return typeof value === "object" && value !== null && NESTED_BRAND in value;
304
+ }
305
+
306
+ // src/selector.ts
307
+ function isSelector(value) {
308
+ return typeof value === "object" && value !== null && "__selectorBrand" in value && value.__selectorBrand === true;
309
+ }
310
+ function createSelector(getState, storeSubscribe, selectorFn) {
311
+ let lastState;
312
+ let lastResult;
313
+ let initialized = false;
314
+ function compute() {
315
+ const currentState = getState();
316
+ if (initialized && currentState === lastState) {
317
+ return lastResult;
318
+ }
319
+ const newResult = selectorFn(currentState);
320
+ if (initialized && structuralEqual(lastResult, newResult)) {
321
+ lastState = currentState;
322
+ return lastResult;
323
+ }
324
+ lastState = currentState;
325
+ lastResult = newResult;
326
+ initialized = true;
327
+ return newResult;
328
+ }
329
+ compute();
330
+ return {
331
+ get: compute,
332
+ subscribe(cb) {
333
+ return storeSubscribe(() => {
334
+ const prev = lastResult;
335
+ const next = compute();
336
+ if (prev !== next) {
337
+ cb(next);
338
+ }
339
+ });
340
+ },
341
+ __selectorBrand: true
342
+ };
343
+ }
344
+
345
+ // src/store.ts
346
+ var STORE_DEF_BRAND = /* @__PURE__ */ Symbol("hurum/store-def");
347
+ var STORE_INSTANCE_BRAND = /* @__PURE__ */ Symbol("hurum/store-instance");
348
+ var IS_DEV = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process?.env?.["NODE_ENV"] !== "production";
349
+ var MAX_RELAY_DEPTH = 5;
350
+ function isPlainObject(value) {
351
+ if (typeof value !== "object" || value === null) return false;
352
+ const proto = Object.getPrototypeOf(value);
353
+ return proto === Object.prototype || proto === null;
354
+ }
355
+ function deepMerge(base, override) {
356
+ const result = { ...base };
357
+ for (const key of Object.keys(override)) {
358
+ const baseVal = base[key];
359
+ const overVal = override[key];
360
+ if (isPlainObject(baseVal) && isPlainObject(overVal)) {
361
+ result[key] = deepMerge(baseVal, overVal);
362
+ } else if (overVal !== void 0) {
363
+ result[key] = overVal;
364
+ }
365
+ }
366
+ return result;
367
+ }
368
+ function createBuilder(cfg) {
369
+ function buildConfig() {
370
+ return {
371
+ state: cfg.state,
372
+ on: cfg.on,
373
+ computed: cfg.computed,
374
+ intents: cfg.intents,
375
+ executors: cfg.executors,
376
+ relay: cfg.relay,
377
+ middleware: cfg.middleware,
378
+ childDepsMap: cfg.childDepsMap
379
+ };
380
+ }
381
+ function doCreate(options) {
382
+ const config = buildConfig();
383
+ const rawStateTemplate = {};
384
+ const nestedFields = {};
385
+ for (const [key, value] of Object.entries(config.state)) {
386
+ if (isNestedMarker(value)) {
387
+ nestedFields[key] = value;
388
+ } else {
389
+ rawStateTemplate[key] = value;
390
+ }
391
+ }
392
+ return createStoreInstance(config, rawStateTemplate, nestedFields, options);
393
+ }
394
+ const builder = {
395
+ on(eventOrNamespace, handlerOrMap) {
396
+ const newHandlers = {};
397
+ if (isEventCreator(eventOrNamespace)) {
398
+ const event = eventOrNamespace;
399
+ newHandlers[event.type] = handlerOrMap;
400
+ } else {
401
+ const namespace = eventOrNamespace;
402
+ const handlers = handlerOrMap;
403
+ for (const key of Object.keys(handlers)) {
404
+ const eventCreator = namespace[key];
405
+ if (eventCreator && typeof eventCreator === "function" && "type" in eventCreator) {
406
+ newHandlers[eventCreator.type] = handlers[key];
407
+ }
408
+ }
409
+ }
410
+ return createBuilder({
411
+ ...cfg,
412
+ on: { ...cfg.on, ...newHandlers }
413
+ });
414
+ },
415
+ computed(def) {
416
+ return createBuilder({
417
+ ...cfg,
418
+ computed: { ...cfg.computed, ...def }
419
+ });
420
+ },
421
+ intents(container) {
422
+ return createBuilder({
423
+ ...cfg,
424
+ intents: container
425
+ });
426
+ },
427
+ executors(...execs) {
428
+ return createBuilder({
429
+ ...cfg,
430
+ executors: [...cfg.executors ?? [], ...execs]
431
+ });
432
+ },
433
+ deps() {
434
+ return createBuilder({ ...cfg });
435
+ },
436
+ childDeps(key, mapper) {
437
+ return createBuilder({
438
+ ...cfg,
439
+ childDepsMap: {
440
+ ...cfg.childDepsMap,
441
+ [key]: mapper
442
+ }
443
+ });
444
+ },
445
+ relay(event, handler) {
446
+ return createBuilder({
447
+ ...cfg,
448
+ relay: { ...cfg.relay, [event.type]: handler }
449
+ });
450
+ },
451
+ middleware(...mws) {
452
+ const resolvedMws = mws.map((mw) => {
453
+ if ("create" in mw && typeof mw.create === "function") {
454
+ return mw.create();
455
+ }
456
+ return mw;
457
+ });
458
+ return createBuilder({
459
+ ...cfg,
460
+ middleware: [...cfg.middleware ?? [], ...resolvedMws]
461
+ });
462
+ },
463
+ create: doCreate
464
+ };
465
+ Object.defineProperty(builder, STORE_DEF_BRAND, {
466
+ value: true,
467
+ writable: false,
468
+ enumerable: false,
469
+ configurable: false
470
+ });
471
+ Object.defineProperty(builder, "__config", {
472
+ get: buildConfig,
473
+ enumerable: false,
474
+ configurable: false
475
+ });
476
+ return builder;
477
+ }
478
+ function Store(config) {
479
+ return createBuilder({
480
+ state: config.state
481
+ });
482
+ }
483
+ function isStoreDefinition(value) {
484
+ return typeof value === "object" && value !== null && STORE_DEF_BRAND in value;
485
+ }
486
+ function isStoreInstance(value) {
487
+ return typeof value === "object" && value !== null && STORE_INSTANCE_BRAND in value;
488
+ }
489
+ var instanceIdCounter = 0;
490
+ function nextInstanceId() {
491
+ return `nested-${++instanceIdCounter}`;
492
+ }
493
+ function createSendProxy(send, intentsContainer) {
494
+ const intentLookup = /* @__PURE__ */ new Map();
495
+ if (intentsContainer) {
496
+ for (const [key, value] of Object.entries(intentsContainer)) {
497
+ if (isIntentDescriptor(value)) {
498
+ intentLookup.set(key, value);
499
+ }
500
+ }
501
+ }
502
+ const boundIntents = /* @__PURE__ */ new Map();
503
+ return new Proxy(send, {
504
+ get(_target, prop) {
505
+ if (typeof prop !== "string") return void 0;
506
+ if (prop === "bind" || prop === "call" || prop === "apply" || prop === "length" || prop === "name" || prop === "prototype") {
507
+ return send[prop];
508
+ }
509
+ let bound = boundIntents.get(prop);
510
+ if (!bound) {
511
+ const descriptor = intentLookup.get(prop);
512
+ if (!descriptor) {
513
+ if (IS_DEV) {
514
+ console.warn(`[hurum] No intent named "${prop}" registered on this store.`);
515
+ }
516
+ return void 0;
517
+ }
518
+ bound = (payload) => send(descriptor, payload);
519
+ boundIntents.set(prop, bound);
520
+ }
521
+ return bound;
522
+ },
523
+ apply(_target, _thisArg, args) {
524
+ return send(args[0], args[1]);
525
+ }
526
+ });
527
+ }
528
+ function resolveNestedSingleDefaults(marker) {
529
+ const childDef = marker.store;
530
+ const childState = childDef.__config.state;
531
+ const safe = {};
532
+ for (const [k, v] of Object.entries(childState)) {
533
+ if (isNestedMarker(v)) {
534
+ if (v.kind === "array") safe[k] = [];
535
+ else if (v.kind === "map") safe[k] = {};
536
+ else safe[k] = resolveNestedSingleDefaults(v);
537
+ } else {
538
+ safe[k] = v;
539
+ }
540
+ }
541
+ return safe;
542
+ }
543
+ function createStoreInstance(config, rawStateTemplate, nestedFields, options) {
544
+ let rawState = { ...rawStateTemplate };
545
+ if (options?.initialState) {
546
+ rawState = deepMerge(rawState, options.initialState);
547
+ }
548
+ let deps = {};
549
+ if (options?.deps) {
550
+ deps = { ...deps, ...options.deps };
551
+ }
552
+ const executorMap = /* @__PURE__ */ new Map();
553
+ if (config.executors) {
554
+ for (const executor of config.executors) {
555
+ const command = getExecutorCommand(executor);
556
+ const commandId = getCommandId(command);
557
+ executorMap.set(commandId, getExecutorFn(executor));
558
+ }
559
+ }
560
+ let stateForInitialComputed = rawState;
561
+ if (Object.keys(nestedFields).length > 0) {
562
+ const nestedDefaults = {};
563
+ for (const [key, marker] of Object.entries(nestedFields)) {
564
+ if (marker.kind === "array") nestedDefaults[key] = [];
565
+ else if (marker.kind === "map") nestedDefaults[key] = {};
566
+ else nestedDefaults[key] = resolveNestedSingleDefaults(marker);
567
+ }
568
+ stateForInitialComputed = { ...rawState, ...nestedDefaults };
569
+ }
570
+ const computedNodes = initializeComputedNodes(
571
+ stateForInitialComputed,
572
+ config.computed
573
+ );
574
+ let computedValues = {};
575
+ for (const node of computedNodes) {
576
+ computedValues[node.name] = node.value;
577
+ }
578
+ let cachedCombinedState = null;
579
+ function invalidateCache() {
580
+ cachedCombinedState = null;
581
+ }
582
+ const stateListeners = /* @__PURE__ */ new Set();
583
+ const eventListeners = /* @__PURE__ */ new Set();
584
+ const runningControllers = /* @__PURE__ */ new Map();
585
+ let runningCount = 0;
586
+ let disposed = false;
587
+ let relayDepth = 0;
588
+ const relayProcessing = /* @__PURE__ */ new Set();
589
+ const eventLog = [];
590
+ const stateSnapshots = [];
591
+ const nestedManagers = [];
592
+ const scope = {};
593
+ function getNestedStates() {
594
+ const nestedState = {};
595
+ for (const mgr of nestedManagers) {
596
+ if (mgr.kind === "single") {
597
+ nestedState[mgr.key] = mgr.instance ? mgr.instance.getState() : null;
598
+ } else if (mgr.kind === "array") {
599
+ const arr = [];
600
+ for (const [, entry] of mgr.instances) {
601
+ arr.push(entry.instance.getState());
602
+ }
603
+ nestedState[mgr.key] = arr;
604
+ } else if (mgr.kind === "map") {
605
+ const rec = {};
606
+ for (const [key, entry] of mgr.instances) {
607
+ rec[key] = entry.instance.getState();
608
+ }
609
+ nestedState[mgr.key] = rec;
610
+ }
611
+ }
612
+ return nestedState;
613
+ }
614
+ function getCombinedState() {
615
+ if (cachedCombinedState !== null) return cachedCombinedState;
616
+ cachedCombinedState = { ...rawState, ...computedValues, ...getNestedStates() };
617
+ return cachedCombinedState;
618
+ }
619
+ function recomputeFromNestedChange() {
620
+ invalidateCache();
621
+ if (computedNodes.length === 0) return;
622
+ const nestedKeys = new Set(nestedManagers.map((m) => m.key));
623
+ if (nestedKeys.size === 0) return;
624
+ const stateForComputed = { ...rawState, ...getNestedStates() };
625
+ const { values, changed } = evaluateComputedNodes(computedNodes, stateForComputed, nestedKeys);
626
+ if (changed) {
627
+ computedValues = values;
628
+ }
629
+ }
630
+ function notifyStateListeners() {
631
+ const state = getCombinedState();
632
+ for (const listener of stateListeners) {
633
+ listener(state);
634
+ }
635
+ }
636
+ function notifyEventListeners(event) {
637
+ for (const listener of eventListeners) {
638
+ listener(event);
639
+ }
640
+ }
641
+ const forwardingEvents = /* @__PURE__ */ new Set();
642
+ let isForwardingToChildren = false;
643
+ function forwardEventToNestedChildren(event) {
644
+ if (nestedManagers.length === 0) return;
645
+ if (forwardingEvents.has(event.type)) return;
646
+ forwardingEvents.add(event.type);
647
+ isForwardingToChildren = true;
648
+ try {
649
+ for (const mgr of nestedManagers) {
650
+ if (mgr.kind === "single") {
651
+ const singleMgr = mgr;
652
+ if (singleMgr.instance) {
653
+ const childInternal = singleMgr.instance;
654
+ childInternal.__applyEvent(event, { skipEventLog: true });
655
+ }
656
+ } else if (mgr.kind === "array") {
657
+ const arrayMgr = mgr;
658
+ for (const [, entry] of arrayMgr.instances) {
659
+ const childInternal = entry.instance;
660
+ childInternal.__applyEvent(event, { skipEventLog: true });
661
+ }
662
+ } else if (mgr.kind === "map") {
663
+ const mapMgr = mgr;
664
+ for (const [, entry] of mapMgr.instances) {
665
+ const childInternal = entry.instance;
666
+ childInternal.__applyEvent(event, { skipEventLog: true });
667
+ }
668
+ }
669
+ }
670
+ } finally {
671
+ isForwardingToChildren = false;
672
+ forwardingEvents.delete(event.type);
673
+ }
674
+ }
675
+ function mwEvent(event, state) {
676
+ if (!config.middleware) return;
677
+ for (const mw of config.middleware) {
678
+ mw.onEvent?.(event, state);
679
+ }
680
+ }
681
+ function mwStateChange(prev, next) {
682
+ if (!config.middleware) return;
683
+ for (const mw of config.middleware) {
684
+ mw.onStateChange?.(prev, next);
685
+ }
686
+ }
687
+ function mwIntentStart(intent, payload) {
688
+ if (!config.middleware) return;
689
+ for (const mw of config.middleware) {
690
+ mw.onIntentStart?.(intent, payload);
691
+ }
692
+ }
693
+ function mwIntentEnd(intent, payload) {
694
+ if (!config.middleware) return;
695
+ for (const mw of config.middleware) {
696
+ mw.onIntentEnd?.(intent, payload);
697
+ }
698
+ }
699
+ function mwError(error, context) {
700
+ if (!config.middleware) return;
701
+ for (const mw of config.middleware) {
702
+ mw.onError?.(error, context);
703
+ }
704
+ }
705
+ function applyEvent(event, metaOrOptions) {
706
+ if (disposed) return;
707
+ const meta = metaOrOptions && "source" in metaOrOptions ? metaOrOptions : void 0;
708
+ const eventOptions = metaOrOptions && "skipEventLog" in metaOrOptions ? metaOrOptions : void 0;
709
+ if (!eventOptions?.skipEventLog) {
710
+ eventLog.push(event);
711
+ }
712
+ const handler = config.on?.[event.type];
713
+ if (handler) {
714
+ const prevState = getCombinedState();
715
+ const inputState = nestedManagers.length > 0 ? { ...rawState, ...getNestedStates() } : { ...rawState };
716
+ if (IS_DEV) Object.freeze(inputState);
717
+ const { type: _type, ...payload } = event;
718
+ const newState = meta ? handler(inputState, payload, meta) : handler(inputState, payload);
719
+ const changedKeys = /* @__PURE__ */ new Set();
720
+ for (const key of Object.keys(newState)) {
721
+ if (rawState[key] !== newState[key]) {
722
+ changedKeys.add(key);
723
+ }
724
+ }
725
+ rawState = newState;
726
+ invalidateCache();
727
+ if (nestedManagers.length > 0 && changedKeys.size > 0) {
728
+ syncNestedFromRawState(changedKeys);
729
+ }
730
+ if (config.computed && computedNodes.length > 0 && changedKeys.size > 0) {
731
+ const stateForComputed = nestedManagers.length > 0 ? { ...rawState, ...getNestedStates() } : rawState;
732
+ const { values } = evaluateComputedNodes(
733
+ computedNodes,
734
+ stateForComputed,
735
+ changedKeys
736
+ );
737
+ computedValues = values;
738
+ invalidateCache();
739
+ }
740
+ const nextState = getCombinedState();
741
+ stateSnapshots.push({ event, state: nextState });
742
+ mwEvent(event, nextState);
743
+ mwStateChange(prevState, nextState);
744
+ notifyEventListeners(event);
745
+ notifyStateListeners();
746
+ } else {
747
+ stateSnapshots.push({ event, state: getCombinedState() });
748
+ mwEvent(event, getCombinedState());
749
+ notifyEventListeners(event);
750
+ }
751
+ processRelay(event);
752
+ forwardEventToNestedChildren(event);
753
+ }
754
+ function processRelay(event) {
755
+ if (!config.relay || disposed) return;
756
+ const relayHandler = config.relay[event.type];
757
+ if (!relayHandler) return;
758
+ if (relayProcessing.has(event.type)) return;
759
+ relayDepth++;
760
+ if (relayDepth > MAX_RELAY_DEPTH) {
761
+ if (IS_DEV) {
762
+ console.error(
763
+ `[hurum] Relay depth limit exceeded (${MAX_RELAY_DEPTH}). Event: ${event.type}.`
764
+ );
765
+ }
766
+ relayDepth--;
767
+ return;
768
+ }
769
+ if (IS_DEV && relayDepth > 3) {
770
+ console.warn(`[hurum] Relay depth ${relayDepth} for event "${event.type}".`);
771
+ }
772
+ relayProcessing.add(event.type);
773
+ try {
774
+ const relayedEvents = relayHandler(event, rawState);
775
+ for (const relayedEvent of relayedEvents) {
776
+ applyEvent(relayedEvent);
777
+ }
778
+ } finally {
779
+ relayProcessing.delete(event.type);
780
+ relayDepth--;
781
+ }
782
+ }
783
+ function emit(event) {
784
+ if (disposed) return;
785
+ applyEvent(event);
786
+ }
787
+ const nestedExecutorIndex = /* @__PURE__ */ new Map();
788
+ function buildNestedExecutorIndex() {
789
+ for (const mgr of nestedManagers) {
790
+ if (mgr.kind === "single" && mgr.instance) {
791
+ const childDef = mgr.marker.store;
792
+ const childConfig = childDef.__config;
793
+ const childInternal = mgr.instance;
794
+ if (childConfig.executors) {
795
+ for (const executor of childConfig.executors) {
796
+ const command = getExecutorCommand(executor);
797
+ const cid = getCommandId(command);
798
+ nestedExecutorIndex.set(cid, childInternal);
799
+ }
800
+ }
801
+ const descendantCommands = childInternal.__getHandleableCommands();
802
+ for (const [cid, descendantStore] of descendantCommands) {
803
+ nestedExecutorIndex.set(cid, descendantStore);
804
+ }
805
+ }
806
+ }
807
+ }
808
+ function executeCommand(command, payload, signal) {
809
+ const commandId = getCommandId(command);
810
+ const executorFn = executorMap.get(commandId);
811
+ if (!executorFn) {
812
+ const childInstance = nestedExecutorIndex.get(commandId);
813
+ if (childInstance) {
814
+ return childInstance.__executeCommand(command, payload, signal);
815
+ }
816
+ return Promise.reject(new Error("[hurum] No executor registered for command"));
817
+ }
818
+ const context = {
819
+ deps,
820
+ emit,
821
+ getState: () => getCombinedState(),
822
+ signal,
823
+ scope
824
+ };
825
+ try {
826
+ const result = executorFn(payload, context);
827
+ return result instanceof Promise ? result : Promise.resolve();
828
+ } catch (error) {
829
+ return Promise.reject(error);
830
+ }
831
+ }
832
+ function baseSend(intentOrPrepared, payload) {
833
+ if (disposed) {
834
+ throw new Error("[hurum] Cannot send intent to a disposed store");
835
+ }
836
+ let intent;
837
+ let resolvedPayload;
838
+ if (isPreparedIntent(intentOrPrepared)) {
839
+ intent = intentOrPrepared.intent;
840
+ resolvedPayload = intentOrPrepared.payload;
841
+ } else {
842
+ intent = intentOrPrepared;
843
+ resolvedPayload = payload;
844
+ }
845
+ const refSymbol = /* @__PURE__ */ Symbol("hurum/intent-ref");
846
+ const ref = { __intentRefBrand: refSymbol };
847
+ const controller = new AbortController();
848
+ runningControllers.set(refSymbol, controller);
849
+ runningCount++;
850
+ mwIntentStart(intent, resolvedPayload);
851
+ const execute = async () => {
852
+ try {
853
+ if (intent.mode === "sequential") {
854
+ for (const command of intent.commands) {
855
+ if (controller.signal.aborted) break;
856
+ await executeCommand(command, resolvedPayload, controller.signal);
857
+ }
858
+ } else if (intent.mode === "all") {
859
+ const promises = intent.commands.map((command) => {
860
+ const childCtrl = new AbortController();
861
+ controller.signal.addEventListener("abort", () => childCtrl.abort(), { once: true });
862
+ return executeCommand(command, resolvedPayload, childCtrl.signal);
863
+ });
864
+ try {
865
+ await Promise.all(promises);
866
+ } catch (error) {
867
+ controller.abort();
868
+ throw error;
869
+ }
870
+ } else if (intent.mode === "allSettled") {
871
+ const promises = intent.commands.map((command) => {
872
+ const childCtrl = new AbortController();
873
+ controller.signal.addEventListener("abort", () => childCtrl.abort(), { once: true });
874
+ return executeCommand(command, resolvedPayload, childCtrl.signal).catch((err) => {
875
+ mwError(err instanceof Error ? err : new Error(String(err)), { intent, command, payload: resolvedPayload });
876
+ });
877
+ });
878
+ await Promise.allSettled(promises);
879
+ }
880
+ } catch (error) {
881
+ mwError(error instanceof Error ? error : new Error(String(error)), { intent, payload: resolvedPayload });
882
+ } finally {
883
+ runningControllers.delete(refSymbol);
884
+ runningCount--;
885
+ mwIntentEnd(intent, resolvedPayload);
886
+ }
887
+ };
888
+ execute();
889
+ return ref;
890
+ }
891
+ function cancel(ref) {
892
+ const refSymbol = ref.__intentRefBrand;
893
+ const controller = runningControllers.get(refSymbol);
894
+ if (controller) {
895
+ controller.abort();
896
+ runningControllers.delete(refSymbol);
897
+ runningCount--;
898
+ }
899
+ }
900
+ function cancelAll() {
901
+ for (const [_sym, controller] of runningControllers) {
902
+ controller.abort();
903
+ }
904
+ runningControllers.clear();
905
+ runningCount = 0;
906
+ }
907
+ const __parentRootSend = options?.["__rootBaseSend"];
908
+ const __parentRootCancel = options?.["__rootCancel"];
909
+ const __parentRootCancelAll = options?.["__rootCancelAll"];
910
+ const rootBaseSend = __parentRootSend ?? baseSend;
911
+ const rootCancel = __parentRootCancel ?? cancel;
912
+ const rootCancelAll = __parentRootCancelAll ?? cancelAll;
913
+ function subscribe(cbOrType, cb) {
914
+ if (cbOrType === "events" && cb) {
915
+ eventListeners.add(cb);
916
+ return () => {
917
+ eventListeners.delete(cb);
918
+ };
919
+ }
920
+ if (typeof cbOrType === "function") {
921
+ stateListeners.add(cbOrType);
922
+ return () => {
923
+ stateListeners.delete(cbOrType);
924
+ };
925
+ }
926
+ throw new Error("[hurum] Invalid subscribe arguments");
927
+ }
928
+ function dispose() {
929
+ if (disposed) return;
930
+ disposed = true;
931
+ cancelAll();
932
+ for (const mgr of nestedManagers) {
933
+ if (mgr.kind === "single") {
934
+ if (mgr.unsub) mgr.unsub();
935
+ if (mgr.instance) mgr.instance.dispose();
936
+ } else {
937
+ for (const [, entry] of mgr.instances) {
938
+ entry.unsub();
939
+ entry.instance.dispose();
940
+ }
941
+ mgr.instances.clear();
942
+ }
943
+ }
944
+ stateListeners.clear();
945
+ eventListeners.clear();
946
+ }
947
+ function subscribeToNestedChild(childInstance, logToEventLog) {
948
+ const unsub = childInstance.subscribe(() => {
949
+ if (!disposed) {
950
+ const prev = getCombinedState();
951
+ recomputeFromNestedChange();
952
+ const next = getCombinedState();
953
+ mwStateChange(prev, next);
954
+ notifyStateListeners();
955
+ }
956
+ });
957
+ const unsubEvents = childInstance.subscribe("events", (event) => {
958
+ if (!disposed && !isForwardingToChildren) {
959
+ if (logToEventLog) eventLog.push(event);
960
+ mwEvent(event, getCombinedState());
961
+ notifyEventListeners(event);
962
+ processRelay(event);
963
+ }
964
+ });
965
+ return () => {
966
+ unsub();
967
+ unsubEvents();
968
+ };
969
+ }
970
+ function initNestedStores() {
971
+ for (const [key, marker] of Object.entries(nestedFields)) {
972
+ const storeDef = marker.store;
973
+ const childDepsFactory = config.childDepsMap?.[key];
974
+ if (marker.kind === "single") {
975
+ const mgr = {
976
+ kind: "single",
977
+ key,
978
+ marker,
979
+ instance: null,
980
+ unsub: null
981
+ };
982
+ const childDeps = childDepsFactory ? childDepsFactory(deps) : void 0;
983
+ const childCreateOpts = { deps: childDeps };
984
+ childCreateOpts["__rootBaseSend"] = rootBaseSend;
985
+ childCreateOpts["__rootCancel"] = rootCancel;
986
+ childCreateOpts["__rootCancelAll"] = rootCancelAll;
987
+ const childInstance = storeDef.create(childCreateOpts);
988
+ const childConfig = storeDef.__config;
989
+ childInstance.send = createSendProxy(rootBaseSend, childConfig.intents);
990
+ childInstance.cancel = rootCancel;
991
+ childInstance.cancelAll = rootCancelAll;
992
+ const unsubChild = subscribeToNestedChild(childInstance, false);
993
+ mgr.instance = childInstance;
994
+ mgr.unsub = unsubChild;
995
+ nestedManagers.push(mgr);
996
+ scope[key] = childInstance;
997
+ } else if (marker.kind === "array") {
998
+ const mgr = {
999
+ kind: "array",
1000
+ key,
1001
+ marker,
1002
+ instances: /* @__PURE__ */ new Map()
1003
+ };
1004
+ nestedManagers.push(mgr);
1005
+ const initialArray = options?.initialState?.[key];
1006
+ if (Array.isArray(initialArray)) {
1007
+ for (let i = 0; i < initialArray.length; i++) {
1008
+ const item = initialArray[i];
1009
+ const id = item?.id;
1010
+ if (id == null) {
1011
+ if (IS_DEV) {
1012
+ console.warn(`[hurum] Nested.array item at index ${i} in "${key}" has no 'id' field. Skipping.`);
1013
+ }
1014
+ continue;
1015
+ }
1016
+ const instanceId = nextInstanceId();
1017
+ const childDeps = childDepsFactory ? childDepsFactory(deps) : void 0;
1018
+ const childInstance = storeDef.create({
1019
+ initialState: item,
1020
+ deps: childDeps
1021
+ });
1022
+ const meta = {
1023
+ source: { instanceId, index: i, parentKey: key }
1024
+ };
1025
+ const unsubChild = subscribeToNestedChild(childInstance, true);
1026
+ mgr.instances.set(String(id), {
1027
+ instance: childInstance,
1028
+ unsub: unsubChild
1029
+ });
1030
+ childInstance.__nestedMeta = meta;
1031
+ }
1032
+ }
1033
+ const arrayScope = {
1034
+ get(id) {
1035
+ return mgr.instances.get(id)?.instance;
1036
+ },
1037
+ values() {
1038
+ const arr = [];
1039
+ for (const [, entry] of mgr.instances) {
1040
+ arr.push(entry.instance);
1041
+ }
1042
+ return arr;
1043
+ },
1044
+ get size() {
1045
+ return mgr.instances.size;
1046
+ },
1047
+ [Symbol.iterator]() {
1048
+ const entries = mgr.instances.values();
1049
+ return {
1050
+ next() {
1051
+ const result = entries.next();
1052
+ if (result.done) return { done: true, value: void 0 };
1053
+ return { done: false, value: result.value.instance };
1054
+ }
1055
+ };
1056
+ }
1057
+ };
1058
+ scope[key] = arrayScope;
1059
+ } else if (marker.kind === "map") {
1060
+ const mgr = {
1061
+ kind: "map",
1062
+ key,
1063
+ marker,
1064
+ instances: /* @__PURE__ */ new Map()
1065
+ };
1066
+ nestedManagers.push(mgr);
1067
+ const initialMap = options?.initialState?.[key];
1068
+ if (initialMap && typeof initialMap === "object" && !Array.isArray(initialMap)) {
1069
+ for (const [mapKey, mapValue] of Object.entries(initialMap)) {
1070
+ const childDeps = childDepsFactory ? childDepsFactory(deps) : void 0;
1071
+ const childInstance = storeDef.create({
1072
+ initialState: mapValue,
1073
+ deps: childDeps
1074
+ });
1075
+ const unsubChild = subscribeToNestedChild(childInstance, true);
1076
+ mgr.instances.set(mapKey, {
1077
+ instance: childInstance,
1078
+ unsub: unsubChild
1079
+ });
1080
+ }
1081
+ }
1082
+ Object.defineProperty(scope, key, {
1083
+ get: () => {
1084
+ const map2 = /* @__PURE__ */ new Map();
1085
+ for (const [k, entry] of mgr.instances) {
1086
+ map2.set(k, entry.instance);
1087
+ }
1088
+ return map2;
1089
+ },
1090
+ enumerable: true,
1091
+ configurable: true
1092
+ });
1093
+ }
1094
+ }
1095
+ }
1096
+ function syncNestedArray(mgr, nextArray) {
1097
+ if (!Array.isArray(nextArray)) return;
1098
+ const storeDef = mgr.marker.store;
1099
+ const childDepsFactory = config.childDepsMap?.[mgr.key];
1100
+ const existingIds = new Set(mgr.instances.keys());
1101
+ const nextIds = /* @__PURE__ */ new Set();
1102
+ for (let i = 0; i < nextArray.length; i++) {
1103
+ const item = nextArray[i];
1104
+ const id = item?.id;
1105
+ if (id == null) continue;
1106
+ const idStr = String(id);
1107
+ nextIds.add(idStr);
1108
+ if (!existingIds.has(idStr)) {
1109
+ const instanceId = nextInstanceId();
1110
+ const childDeps = childDepsFactory ? childDepsFactory(deps) : void 0;
1111
+ const childInstance = storeDef.create({
1112
+ initialState: item,
1113
+ deps: childDeps
1114
+ });
1115
+ const meta = {
1116
+ source: { instanceId, index: i, parentKey: mgr.key }
1117
+ };
1118
+ childInstance.__nestedMeta = meta;
1119
+ const unsubChild = subscribeToNestedChild(childInstance, true);
1120
+ mgr.instances.set(idStr, {
1121
+ instance: childInstance,
1122
+ unsub: unsubChild
1123
+ });
1124
+ }
1125
+ }
1126
+ for (const id of existingIds) {
1127
+ if (!nextIds.has(id)) {
1128
+ const entry = mgr.instances.get(id);
1129
+ entry.unsub();
1130
+ entry.instance.dispose();
1131
+ mgr.instances.delete(id);
1132
+ }
1133
+ }
1134
+ }
1135
+ function syncNestedMap(mgr, nextMap) {
1136
+ if (!nextMap || typeof nextMap !== "object" || Array.isArray(nextMap)) return;
1137
+ const storeDef = mgr.marker.store;
1138
+ const childDepsFactory = config.childDepsMap?.[mgr.key];
1139
+ const existingKeys = new Set(mgr.instances.keys());
1140
+ const nextKeys = new Set(Object.keys(nextMap));
1141
+ for (const key of nextKeys) {
1142
+ if (!existingKeys.has(key)) {
1143
+ const childDeps = childDepsFactory ? childDepsFactory(deps) : void 0;
1144
+ const childInstance = storeDef.create({
1145
+ initialState: nextMap[key],
1146
+ deps: childDeps
1147
+ });
1148
+ const unsubChild = subscribeToNestedChild(childInstance, true);
1149
+ mgr.instances.set(key, {
1150
+ instance: childInstance,
1151
+ unsub: unsubChild
1152
+ });
1153
+ }
1154
+ }
1155
+ for (const key of existingKeys) {
1156
+ if (!nextKeys.has(key)) {
1157
+ const entry = mgr.instances.get(key);
1158
+ entry.unsub();
1159
+ entry.instance.dispose();
1160
+ mgr.instances.delete(key);
1161
+ }
1162
+ }
1163
+ }
1164
+ let syncingNested = false;
1165
+ function syncNestedFromRawState(changedKeys) {
1166
+ if (syncingNested) return;
1167
+ syncingNested = true;
1168
+ try {
1169
+ for (const mgr of nestedManagers) {
1170
+ if (mgr.kind !== "array" && mgr.kind !== "map") continue;
1171
+ if (changedKeys && !changedKeys.has(mgr.key)) continue;
1172
+ const currentRawValue = rawState[mgr.key];
1173
+ if (currentRawValue !== void 0) {
1174
+ if (mgr.kind === "array") {
1175
+ syncNestedArray(mgr, currentRawValue);
1176
+ } else if (mgr.kind === "map") {
1177
+ syncNestedMap(mgr, currentRawValue);
1178
+ }
1179
+ }
1180
+ }
1181
+ } finally {
1182
+ syncingNested = false;
1183
+ }
1184
+ }
1185
+ initNestedStores();
1186
+ buildNestedExecutorIndex();
1187
+ if (nestedManagers.length > 0 && config.computed && computedNodes.length > 0) {
1188
+ const combinedForInit = { ...rawState, ...getNestedStates() };
1189
+ const freshNodes = initializeComputedNodes(
1190
+ combinedForInit,
1191
+ config.computed
1192
+ );
1193
+ computedNodes.length = 0;
1194
+ computedNodes.push(...freshNodes);
1195
+ computedValues = {};
1196
+ for (const node of computedNodes) {
1197
+ computedValues[node.name] = node.value;
1198
+ }
1199
+ }
1200
+ const sendProxy = createSendProxy(baseSend, config.intents);
1201
+ const instance = {
1202
+ send: sendProxy,
1203
+ cancel,
1204
+ cancelAll,
1205
+ getState: getCombinedState,
1206
+ subscribe,
1207
+ dispose,
1208
+ selector(fn) {
1209
+ return createSelector(
1210
+ getCombinedState,
1211
+ subscribe,
1212
+ fn
1213
+ );
1214
+ },
1215
+ scope,
1216
+ // Internal — used by testing API and event forwarding
1217
+ __eventLog: eventLog,
1218
+ __stateSnapshots: stateSnapshots,
1219
+ __runningCount: () => runningCount,
1220
+ __rawState: () => rawState,
1221
+ __applyEvent: (event, opts) => applyEvent(event, opts),
1222
+ __executeCommand: executeCommand,
1223
+ __getHandleableCommands: () => nestedExecutorIndex
1224
+ };
1225
+ Object.defineProperty(instance, STORE_INSTANCE_BRAND, {
1226
+ value: true,
1227
+ writable: false,
1228
+ enumerable: false,
1229
+ configurable: false
1230
+ });
1231
+ if (config.middleware) {
1232
+ const storeRef = {
1233
+ getState: () => getCombinedState(),
1234
+ meta: {
1235
+ computedKeys: computedNodes.map((n) => n.name),
1236
+ nestedKeys: nestedManagers.map((m) => ({ key: m.key, kind: m.kind }))
1237
+ }
1238
+ };
1239
+ for (const mw of config.middleware) {
1240
+ mw.onAttach?.(storeRef);
1241
+ }
1242
+ }
1243
+ return instance;
1244
+ }
1245
+
1246
+ // src/middleware/logger.ts
1247
+ function logger(options) {
1248
+ const filter = options?.filter;
1249
+ return {
1250
+ name: "logger",
1251
+ onEvent: (event, state) => {
1252
+ if (filter && !filter(event)) return;
1253
+ console.group(`[Event] ${event.type}`);
1254
+ console.log(event);
1255
+ console.log("State:", state);
1256
+ console.groupEnd();
1257
+ },
1258
+ onStateChange: (prev, next) => {
1259
+ console.log("[State Change]", { prev, next });
1260
+ },
1261
+ onIntentStart: (intent, payload) => {
1262
+ console.log("[Intent Start]", intent, payload);
1263
+ },
1264
+ onIntentEnd: (intent, payload) => {
1265
+ console.log("[Intent End]", intent, payload);
1266
+ },
1267
+ onError: (error, context) => {
1268
+ console.error("[Error]", error, context);
1269
+ }
1270
+ };
1271
+ }
1272
+
1273
+ // src/middleware/persist.ts
1274
+ function pickKeys(state, keys) {
1275
+ const result = {};
1276
+ for (const key of keys) {
1277
+ if (key in state) {
1278
+ result[key] = state[key];
1279
+ }
1280
+ }
1281
+ return result;
1282
+ }
1283
+ function persist(options) {
1284
+ const {
1285
+ key,
1286
+ storage = typeof globalThis !== "undefined" && "localStorage" in globalThis ? globalThis.localStorage : void 0,
1287
+ serialize = JSON.stringify,
1288
+ deserialize = JSON.parse,
1289
+ pick
1290
+ } = options;
1291
+ const middleware = {
1292
+ name: "persist",
1293
+ onStateChange: (_prev, next) => {
1294
+ if (!storage) return;
1295
+ try {
1296
+ const toPersist = pick ? pickKeys(next, pick) : next;
1297
+ storage.setItem(key, serialize(toPersist));
1298
+ } catch {
1299
+ }
1300
+ }
1301
+ };
1302
+ function getPersistedState() {
1303
+ if (!storage) return null;
1304
+ try {
1305
+ const raw = storage.getItem(key);
1306
+ if (raw === null) return null;
1307
+ return deserialize(raw);
1308
+ } catch {
1309
+ return null;
1310
+ }
1311
+ }
1312
+ return { middleware, getPersistedState };
1313
+ }
1314
+
1315
+ // src/middleware/devtools.ts
1316
+ function devtools(options) {
1317
+ const name = options?.name ?? "Hurum Store";
1318
+ let devToolsInstance = null;
1319
+ let storeRef = null;
1320
+ const middleware = {
1321
+ name: "devtools",
1322
+ onEvent: (event, state) => {
1323
+ devToolsInstance?.send({ type: event.type, payload: event }, state);
1324
+ },
1325
+ onIntentStart: (_intent, payload) => {
1326
+ devToolsInstance?.send(
1327
+ { type: `[Intent] ${_intent.mode}`, payload },
1328
+ storeRef?.getState() ?? {}
1329
+ );
1330
+ },
1331
+ onError: (error, context) => {
1332
+ devToolsInstance?.send(
1333
+ { type: `[Error] ${error.message}`, payload: context },
1334
+ storeRef?.getState() ?? {}
1335
+ );
1336
+ }
1337
+ };
1338
+ function connect(store) {
1339
+ storeRef = store;
1340
+ const extension = typeof globalThis !== "undefined" ? globalThis.__REDUX_DEVTOOLS_EXTENSION__ : void 0;
1341
+ if (!extension) return;
1342
+ devToolsInstance = extension.connect({ name });
1343
+ devToolsInstance.init(store.getState());
1344
+ devToolsInstance.subscribe((message) => {
1345
+ if (message.type === "DISPATCH" && message.state) ;
1346
+ });
1347
+ }
1348
+ return { middleware, connect };
1349
+ }
1350
+
1351
+ // src/middleware/undo-redo.ts
1352
+ function undoRedo(options) {
1353
+ const maxHistory = options?.maxHistory ?? 50;
1354
+ const history = [];
1355
+ let position = -1;
1356
+ let tracking = true;
1357
+ const middleware = {
1358
+ name: "undo-redo",
1359
+ onStateChange: (_prev, next) => {
1360
+ if (!tracking) return;
1361
+ if (position < history.length - 1) {
1362
+ history.splice(position + 1);
1363
+ }
1364
+ history.push(structuredClone(next));
1365
+ position = history.length - 1;
1366
+ if (history.length > maxHistory) {
1367
+ const excess = history.length - maxHistory;
1368
+ history.splice(0, excess);
1369
+ position -= excess;
1370
+ }
1371
+ }
1372
+ };
1373
+ function undo() {
1374
+ if (position <= 0) return null;
1375
+ tracking = false;
1376
+ position--;
1377
+ const state = structuredClone(history[position]);
1378
+ tracking = true;
1379
+ return state;
1380
+ }
1381
+ function redo() {
1382
+ if (position >= history.length - 1) return null;
1383
+ tracking = false;
1384
+ position++;
1385
+ const state = structuredClone(history[position]);
1386
+ tracking = true;
1387
+ return state;
1388
+ }
1389
+ function canUndo() {
1390
+ return position > 0;
1391
+ }
1392
+ function canRedo() {
1393
+ return position < history.length - 1;
1394
+ }
1395
+ function getHistory() {
1396
+ return history;
1397
+ }
1398
+ function getPosition() {
1399
+ return position;
1400
+ }
1401
+ return { middleware, undo, redo, canUndo, canRedo, getHistory, getPosition };
1402
+ }
1403
+
1404
+ exports.CommandExecutor = CommandExecutor;
1405
+ exports.Event = Event;
1406
+ exports.Events = Events;
1407
+ exports.Intent = Intent;
1408
+ exports.Intents = Intents;
1409
+ exports.Nested = Nested;
1410
+ exports.Store = Store;
1411
+ exports.devtools = devtools;
1412
+ exports.isPreparedIntent = isPreparedIntent;
1413
+ exports.isSelector = isSelector;
1414
+ exports.isStoreDefinition = isStoreDefinition;
1415
+ exports.isStoreInstance = isStoreInstance;
1416
+ exports.logger = logger;
1417
+ exports.persist = persist;
1418
+ exports.structuralEqual = structuralEqual;
1419
+ exports.undoRedo = undoRedo;
1420
+ //# sourceMappingURL=index.cjs.map
1421
+ //# sourceMappingURL=index.cjs.map