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