@flexsurfer/reflex 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1097 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ clearGlobalInterceptors: () => clearGlobalInterceptors,
34
+ debounceAndDispatch: () => debounceAndDispatch,
35
+ defaultErrorHandler: () => defaultErrorHandler,
36
+ disableTracing: () => disableTracing,
37
+ dispatch: () => dispatch,
38
+ enableTracePrint: () => enableTracePrint,
39
+ enableTracing: () => enableTracing,
40
+ getGlobalInterceptors: () => getGlobalInterceptors,
41
+ initAppDb: () => initAppDb,
42
+ isDebugEnabled: () => isDebugEnabled,
43
+ regCoeffect: () => regCoeffect,
44
+ regEffect: () => regEffect,
45
+ regEvent: () => regEvent,
46
+ regEventErrorHandler: () => regEventErrorHandler,
47
+ regGlobalInterceptor: () => regGlobalInterceptor,
48
+ regSub: () => regSub,
49
+ registerTraceCb: () => registerTraceCb,
50
+ setDebugEnabled: () => setDebugEnabled,
51
+ throttleAndDispatch: () => throttleAndDispatch,
52
+ useSubscription: () => useSubscription
53
+ });
54
+ module.exports = __toCommonJS(src_exports);
55
+
56
+ // src/loggers.ts
57
+ var hasGroup = typeof console.group === "function";
58
+ var hasGroupEnd = typeof console.groupEnd === "function";
59
+ var defaultLoggers = {
60
+ log: console.log.bind(console),
61
+ warn: console.warn.bind(console),
62
+ error: console.error.bind(console),
63
+ debug: console.debug.bind(console),
64
+ group: hasGroup ? console.group.bind(console) : console.log.bind(console),
65
+ groupEnd: hasGroupEnd ? console.groupEnd.bind(console) : () => {
66
+ }
67
+ };
68
+ var currentLoggers = { ...defaultLoggers };
69
+ function consoleLog(level, ...args) {
70
+ if (!(level in currentLoggers)) {
71
+ throw new Error(`reflex: log called with unknown level: ${level}`);
72
+ }
73
+ currentLoggers[level](...args);
74
+ }
75
+
76
+ // src/registrar.ts
77
+ var kindToIdToHandler = {
78
+ event: {},
79
+ fx: {},
80
+ cofx: {},
81
+ sub: {},
82
+ subDeps: {},
83
+ error: {}
84
+ };
85
+ function getHandler(kind, id) {
86
+ const handler = kindToIdToHandler[kind][id];
87
+ if (!handler) {
88
+ consoleLog("error", `[reflex] no ${kind} handler registered for:`, id);
89
+ }
90
+ return handler;
91
+ }
92
+ function registerHandler(kind, id, handlerFn) {
93
+ if (kindToIdToHandler[kind][id]) {
94
+ consoleLog("warn", `[reflex] overwriting ${kind} handler for:`, id);
95
+ }
96
+ kindToIdToHandler[kind][id] = handlerFn;
97
+ return handlerFn;
98
+ }
99
+ function hasHandler(kind, id) {
100
+ return !!kindToIdToHandler[kind][id];
101
+ }
102
+ var reactionsRegistry = /* @__PURE__ */ new Map();
103
+ function getReaction(key) {
104
+ return reactionsRegistry.get(key);
105
+ }
106
+ function setReaction(key, reaction) {
107
+ reactionsRegistry.set(key, reaction);
108
+ }
109
+ var interceptorsRegistry = /* @__PURE__ */ new Map();
110
+ function getInterceptors(eventId) {
111
+ return interceptorsRegistry.get(eventId) || [];
112
+ }
113
+ function setInterceptors(eventId, interceptors) {
114
+ interceptorsRegistry.set(eventId, interceptors);
115
+ }
116
+
117
+ // src/schedule.ts
118
+ function scheduleAfterRender(f) {
119
+ if (typeof requestAnimationFrame !== "undefined") {
120
+ requestAnimationFrame(() => {
121
+ Promise.resolve().then(f);
122
+ });
123
+ } else {
124
+ setTimeout(f, 16);
125
+ }
126
+ }
127
+ function scheduleNextTick(f) {
128
+ if (typeof globalThis.setImmediate === "function") {
129
+ globalThis.setImmediate(f);
130
+ return;
131
+ }
132
+ if (typeof MessageChannel !== "undefined") {
133
+ const { port1, port2 } = new MessageChannel();
134
+ port1.onmessage = () => f();
135
+ port2.postMessage(void 0);
136
+ return;
137
+ }
138
+ setTimeout(f, 0);
139
+ }
140
+
141
+ // src/db.ts
142
+ var appDb = {};
143
+ var reactionsQueue = /* @__PURE__ */ new Set();
144
+ var reactionsScheduled = false;
145
+ function initAppDb(value) {
146
+ appDb = value;
147
+ }
148
+ function getAppDb() {
149
+ return appDb;
150
+ }
151
+ function updateAppDbWithPatches(newDb, patches) {
152
+ if (patches.length > 0) {
153
+ appDb = newDb;
154
+ for (const patch of patches) {
155
+ const pathSegments = patch.path;
156
+ if (pathSegments.length > 0) {
157
+ const rootKey = pathSegments[0];
158
+ const subVectorKey = JSON.stringify([rootKey]);
159
+ const reaction = getReaction(subVectorKey);
160
+ if (reaction) {
161
+ if (!reaction.isRoot) {
162
+ consoleLog("error", `[reflex] updateAppDb: root reaction id ${subVectorKey} registered with a computed function. This is not allowed.`);
163
+ continue;
164
+ }
165
+ reactionsQueue.add(subVectorKey);
166
+ }
167
+ }
168
+ }
169
+ if (reactionsQueue.size > 0 && !reactionsScheduled) {
170
+ reactionsScheduled = true;
171
+ scheduleAfterRender(() => {
172
+ for (const subVectorKey of reactionsQueue) {
173
+ const reaction = getReaction(subVectorKey);
174
+ if (reaction) {
175
+ reaction.markDirty();
176
+ }
177
+ }
178
+ reactionsQueue.clear();
179
+ reactionsScheduled = false;
180
+ });
181
+ }
182
+ }
183
+ }
184
+
185
+ // src/interceptor.ts
186
+ function isInterceptor(m) {
187
+ if (typeof m !== "object" || m === null)
188
+ return false;
189
+ const keys = new Set(Object.keys(m));
190
+ if (!keys.has("id"))
191
+ return false;
192
+ if (!keys.has("before") && !keys.has("after"))
193
+ return false;
194
+ return true;
195
+ }
196
+ function exceptionToExInfo(e, interceptor, direction) {
197
+ const ex = new Error(`Interceptor Exception: ${e.message}`);
198
+ ex.data = { direction, interceptor: interceptor.id, originalError: e };
199
+ ex.cause = e;
200
+ return ex;
201
+ }
202
+ function mergeExData(e, ...ms) {
203
+ const ex = new Error(e.message);
204
+ ex.data = Object.assign({}, e.data, ...ms);
205
+ ex.cause = e.cause;
206
+ return ex;
207
+ }
208
+ function invokeInterceptorFn(context, interceptor, direction) {
209
+ const fn = interceptor[direction];
210
+ if (!fn)
211
+ return context;
212
+ if (context.originalException) {
213
+ return fn(context);
214
+ }
215
+ try {
216
+ return fn(context);
217
+ } catch (e) {
218
+ throw exceptionToExInfo(e, interceptor, direction);
219
+ }
220
+ }
221
+ function invokeInterceptors(context, direction) {
222
+ let ctx = { ...context };
223
+ while (ctx.queue.length > 0) {
224
+ const [next, ...rest] = ctx.queue;
225
+ ctx = invokeInterceptorFn(
226
+ {
227
+ ...ctx,
228
+ queue: rest,
229
+ stack: direction === "before" ? [...ctx.stack, next] : ctx.stack
230
+ },
231
+ next,
232
+ direction
233
+ );
234
+ }
235
+ return ctx;
236
+ }
237
+ function changeDirection(context) {
238
+ return {
239
+ ...context,
240
+ queue: [...context.stack].reverse(),
241
+ stack: []
242
+ };
243
+ }
244
+ function createContext(eventV, interceptors) {
245
+ const coeffects = {
246
+ event: eventV,
247
+ draftDb: {}
248
+ };
249
+ return {
250
+ coeffects,
251
+ effects: [],
252
+ queue: [...interceptors],
253
+ stack: [],
254
+ newDb: {},
255
+ patches: [],
256
+ originalException: false
257
+ };
258
+ }
259
+ function executeInterceptors(ctx) {
260
+ const ctxAfterBeforePhase = invokeInterceptors(ctx, "before");
261
+ const ctxChangedDirection = changeDirection(ctxAfterBeforePhase);
262
+ return invokeInterceptors(ctxChangedDirection, "after");
263
+ }
264
+ function execute(eventV, interceptors) {
265
+ const ctx = createContext(eventV, interceptors);
266
+ const errorHandler = getHandler("error", "event-handler");
267
+ if (!errorHandler) {
268
+ return executeInterceptors({ ...ctx, originalException: true });
269
+ }
270
+ try {
271
+ return executeInterceptors(ctx);
272
+ } catch (e) {
273
+ const reFrameError = mergeExData(e, { eventV });
274
+ errorHandler(e.cause || e, reFrameError);
275
+ return ctx;
276
+ }
277
+ }
278
+
279
+ // src/cofx.ts
280
+ var KIND = "cofx";
281
+ function regCoeffect(id, handler) {
282
+ registerHandler(KIND, id, handler);
283
+ }
284
+ function getInjectCofxInterceptor(id, value) {
285
+ return {
286
+ id: `inject-${id}`,
287
+ before: (context) => {
288
+ const handler = getHandler(KIND, id);
289
+ if (handler) {
290
+ try {
291
+ context.coeffects = handler({ ...context.coeffects }, value);
292
+ } catch (error) {
293
+ consoleLog("error", `[reflex] Error in :${id} coeffect handler:`, error);
294
+ }
295
+ } else {
296
+ consoleLog("error", "[reflex] No cofx handler registered for", id);
297
+ }
298
+ return context;
299
+ }
300
+ };
301
+ }
302
+ regCoeffect("now", (coeffects) => ({
303
+ ...coeffects,
304
+ now: Date.now()
305
+ }));
306
+ regCoeffect("random", (coeffects) => ({
307
+ ...coeffects,
308
+ random: Math.random()
309
+ }));
310
+
311
+ // src/router.ts
312
+ var laterFns = {
313
+ flush: scheduleAfterRender,
314
+ yield: scheduleNextTick
315
+ };
316
+ var EventQueue = class {
317
+ constructor(eventHandler) {
318
+ this.eventHandler = eventHandler;
319
+ }
320
+ fsmState = "idle";
321
+ queue = [];
322
+ push(event) {
323
+ this.fsmTrigger("add-event", event);
324
+ }
325
+ purge() {
326
+ this.queue = [];
327
+ }
328
+ fsmTrigger(trigger, arg) {
329
+ let newState;
330
+ let actionFn;
331
+ switch (`${this.fsmState}:${trigger}`) {
332
+ case "idle:add-event":
333
+ newState = "scheduled";
334
+ actionFn = () => {
335
+ this.addEvent(arg);
336
+ this.runNextTick();
337
+ };
338
+ break;
339
+ case "scheduled:add-event":
340
+ newState = "scheduled";
341
+ actionFn = () => this.addEvent(arg);
342
+ break;
343
+ case "scheduled:run-queue":
344
+ newState = "running";
345
+ actionFn = () => this.runQueue();
346
+ break;
347
+ case "running:add-event":
348
+ newState = "running";
349
+ actionFn = () => this.addEvent(arg);
350
+ break;
351
+ case "running:pause":
352
+ newState = "paused";
353
+ actionFn = () => this.pause(arg);
354
+ break;
355
+ case "running:exception":
356
+ newState = "idle";
357
+ actionFn = () => this.exception(arg);
358
+ break;
359
+ case "running:finish-run":
360
+ if (this.queue.length === 0) {
361
+ newState = "idle";
362
+ } else {
363
+ newState = "scheduled";
364
+ actionFn = () => this.runNextTick();
365
+ }
366
+ break;
367
+ case "paused:add-event":
368
+ newState = "paused";
369
+ actionFn = () => this.addEvent(arg);
370
+ break;
371
+ case "paused:resume":
372
+ newState = "running";
373
+ actionFn = () => this.resume();
374
+ break;
375
+ default:
376
+ consoleLog("error", `[reflex] router state transition not found. ${this.fsmState} ${trigger}`);
377
+ return;
378
+ }
379
+ this.fsmState = newState;
380
+ if (actionFn)
381
+ actionFn();
382
+ }
383
+ addEvent(event) {
384
+ this.queue.push(event);
385
+ }
386
+ processFirstEvent() {
387
+ const event = this.queue[0];
388
+ try {
389
+ this.eventHandler(event);
390
+ this.queue.shift();
391
+ } catch (ex) {
392
+ this.fsmTrigger("exception", ex);
393
+ }
394
+ }
395
+ runNextTick() {
396
+ laterFns.yield(() => this.fsmTrigger("run-queue"));
397
+ }
398
+ runQueue() {
399
+ let n = this.queue.length;
400
+ while (n > 0) {
401
+ const nextEvent = this.queue[0];
402
+ const metaKeys = nextEvent.meta ? Object.keys(nextEvent.meta) : [];
403
+ const laterKey = metaKeys.find((k) => laterFns[k]);
404
+ if (laterKey) {
405
+ this.fsmTrigger("pause", laterFns[laterKey]);
406
+ return;
407
+ }
408
+ this.processFirstEvent();
409
+ n -= 1;
410
+ }
411
+ this.fsmTrigger("finish-run");
412
+ }
413
+ exception(ex) {
414
+ this.purge();
415
+ consoleLog("error", "[reflex] event processing exception:", ex);
416
+ }
417
+ pause(laterFn) {
418
+ laterFn(() => this.fsmTrigger("resume"));
419
+ }
420
+ resume() {
421
+ this.processFirstEvent();
422
+ this.runQueue();
423
+ }
424
+ /**
425
+ * Get current state for debugging
426
+ */
427
+ getState() {
428
+ return this.fsmState;
429
+ }
430
+ /**
431
+ * Get queue length for debugging
432
+ */
433
+ getQueueLength() {
434
+ return this.queue.length;
435
+ }
436
+ };
437
+ var eventQueue = new EventQueue(handle);
438
+ function isValidEventVector(value) {
439
+ return Array.isArray(value) && value.length > 0;
440
+ }
441
+ function dispatch(event) {
442
+ if (!isValidEventVector(event)) {
443
+ consoleLog("error", "[reflex] invalid dispatch event vector.");
444
+ return;
445
+ }
446
+ eventQueue.push(event);
447
+ }
448
+
449
+ // src/fx.ts
450
+ var KIND2 = "fx";
451
+ function regEffect(id, handler) {
452
+ registerHandler(KIND2, id, handler);
453
+ }
454
+ var doFxInterceptor = {
455
+ id: "do-fx",
456
+ after: (context) => {
457
+ if (context.newDb && context.patches) {
458
+ updateAppDbWithPatches(context.newDb, context.patches);
459
+ }
460
+ const effects = context.effects;
461
+ if (!Array.isArray(effects)) {
462
+ consoleLog("warn", `[reflex] effects expects a vector, but was given ${typeof effects}`);
463
+ return context;
464
+ }
465
+ effects.filter((effect) => Array.isArray(effect) && effect.length === 2).forEach(([key, val]) => {
466
+ const effectFn = getHandler(KIND2, key);
467
+ if (effectFn) {
468
+ try {
469
+ effectFn(val);
470
+ } catch (error) {
471
+ consoleLog("error", `[reflex] error in effects for ${key}:`, error);
472
+ }
473
+ } else {
474
+ consoleLog("warn", `[reflex] in 'effects' found ${key} which has no associated handler. Ignoring.`);
475
+ }
476
+ });
477
+ return context;
478
+ }
479
+ };
480
+ function dispatchLater(effect) {
481
+ const { ms, dispatch: eventToDispatch } = effect;
482
+ if (!Array.isArray(eventToDispatch) || typeof ms !== "number") {
483
+ consoleLog("error", "[reflex] ignoring bad dispatch-later value:", effect);
484
+ return;
485
+ }
486
+ if (ms < 0) {
487
+ consoleLog("warn", "[reflex] dispatch-later effect with negative delay:", ms);
488
+ }
489
+ setTimeout(() => dispatch(eventToDispatch), Math.max(0, ms));
490
+ }
491
+ regEffect("dispatch-later", (value) => {
492
+ dispatchLater(value);
493
+ });
494
+ regEffect("dispatch", (value) => {
495
+ if (!Array.isArray(value)) {
496
+ consoleLog("error", "[reflex] ignoring bad dispatch value. Expected a vector, but got:", value);
497
+ return;
498
+ }
499
+ dispatch(value);
500
+ });
501
+
502
+ // src/events.ts
503
+ var import_immer = require("immer");
504
+
505
+ // src/settings.ts
506
+ var store = {
507
+ globalInterceptors: [],
508
+ debugEnabled: true
509
+ // Default to true, can be configured via setDebugEnabled
510
+ };
511
+ function replaceGlobalInterceptor(globalInterceptors, interceptor) {
512
+ return globalInterceptors.reduce((ret, existingInterceptor) => {
513
+ if (interceptor.id === existingInterceptor.id) {
514
+ if (store.debugEnabled) {
515
+ consoleLog("warn", "[reflex] replacing duplicate global interceptor id:", interceptor.id);
516
+ }
517
+ return [...ret, interceptor];
518
+ } else {
519
+ return [...ret, existingInterceptor];
520
+ }
521
+ }, []);
522
+ }
523
+ function regGlobalInterceptor(interceptor) {
524
+ const { id } = interceptor;
525
+ const ids = store.globalInterceptors.map((i) => i.id);
526
+ if (ids.includes(id)) {
527
+ store.globalInterceptors = replaceGlobalInterceptor(store.globalInterceptors, interceptor);
528
+ } else {
529
+ store.globalInterceptors = [...store.globalInterceptors, interceptor];
530
+ }
531
+ }
532
+ function getGlobalInterceptors() {
533
+ return [...store.globalInterceptors];
534
+ }
535
+ function clearGlobalInterceptors(id) {
536
+ if (id === void 0) {
537
+ store.globalInterceptors = [];
538
+ } else {
539
+ store.globalInterceptors = store.globalInterceptors.filter((interceptor) => interceptor.id !== id);
540
+ }
541
+ }
542
+ function setDebugEnabled(enabled) {
543
+ store.debugEnabled = enabled;
544
+ }
545
+ function isDebugEnabled() {
546
+ return store.debugEnabled;
547
+ }
548
+
549
+ // src/trace.ts
550
+ var nextId = 1;
551
+ var traces = [];
552
+ var currentTrace = null;
553
+ var traceEnabled = false;
554
+ var traceCbs = /* @__PURE__ */ new Map();
555
+ var debounceTimer = null;
556
+ var DEBOUNCE_TIME = 50;
557
+ function enableTracing() {
558
+ traceEnabled = true;
559
+ }
560
+ function disableTracing() {
561
+ traceEnabled = false;
562
+ resetTracing();
563
+ }
564
+ function resetTracing() {
565
+ nextId = 1;
566
+ traces = [];
567
+ currentTrace = null;
568
+ if (debounceTimer) {
569
+ clearTimeout(debounceTimer);
570
+ debounceTimer = null;
571
+ }
572
+ }
573
+ function registerTraceCb(key, cb) {
574
+ if (!traceEnabled) {
575
+ console.warn(
576
+ "Tracing is not enabled; call enableTracing() before registering callbacks"
577
+ );
578
+ return;
579
+ }
580
+ traceCbs.set(key, cb);
581
+ }
582
+ function scheduleFlush() {
583
+ if (debounceTimer)
584
+ return;
585
+ debounceTimer = setTimeout(() => {
586
+ const batch = traces.slice();
587
+ traces = [];
588
+ debounceTimer = null;
589
+ for (const cb of traceCbs.values()) {
590
+ try {
591
+ cb(batch);
592
+ } catch (e) {
593
+ console.error("Error in trace callback", e);
594
+ }
595
+ }
596
+ }, DEBOUNCE_TIME);
597
+ }
598
+ function startTrace(opts) {
599
+ const parentId = opts.childOf ?? currentTrace?.id ?? null;
600
+ const trace = {
601
+ id: nextId++,
602
+ operation: opts.operation,
603
+ opType: opts.opType,
604
+ tags: opts.tags ?? {},
605
+ childOf: parentId ?? void 0,
606
+ start: Date.now()
607
+ };
608
+ return trace;
609
+ }
610
+ function finishTrace(trace) {
611
+ if (!traceEnabled)
612
+ return;
613
+ trace.end = Date.now();
614
+ trace.duration = trace.end - trace.start;
615
+ traces.push(trace);
616
+ scheduleFlush();
617
+ }
618
+ function withTrace(opts, fn) {
619
+ if (!traceEnabled) {
620
+ return fn();
621
+ }
622
+ const parent = currentTrace;
623
+ currentTrace = startTrace(opts);
624
+ try {
625
+ return fn();
626
+ } finally {
627
+ finishTrace(currentTrace);
628
+ currentTrace = parent;
629
+ }
630
+ }
631
+ function mergeTrace(update) {
632
+ if (!traceEnabled || !currentTrace) {
633
+ return;
634
+ }
635
+ if (update.tags) {
636
+ currentTrace.tags = { ...currentTrace.tags, ...update.tags };
637
+ }
638
+ for (const k of Object.keys(update)) {
639
+ if (k !== "tags") {
640
+ currentTrace[k] = update[k];
641
+ }
642
+ }
643
+ }
644
+ function enableTracePrint() {
645
+ registerTraceCb("reflex-default-tracer", (traces2) => {
646
+ console.log("%c[reflex] [trace] ", "font-weight: bold; color: blue;", traces2);
647
+ });
648
+ }
649
+
650
+ // src/events.ts
651
+ var KIND3 = "event";
652
+ function regEvent(id, handler, cofxOrInterceptors, interceptors) {
653
+ registerHandler(KIND3, id, handler);
654
+ registerInterceptors(id, cofxOrInterceptors, interceptors);
655
+ }
656
+ function isCofxArray(arr) {
657
+ return arr.length > 0 && Array.isArray(arr[0]);
658
+ }
659
+ function registerInterceptors(id, cofxOrInterceptors, interceptors) {
660
+ let cofx;
661
+ let finalInterceptors;
662
+ if (cofxOrInterceptors) {
663
+ if (isCofxArray(cofxOrInterceptors)) {
664
+ cofx = cofxOrInterceptors;
665
+ finalInterceptors = interceptors;
666
+ } else {
667
+ cofx = void 0;
668
+ finalInterceptors = cofxOrInterceptors;
669
+ }
670
+ }
671
+ const cofxInterceptors = [];
672
+ if (cofx) {
673
+ for (const cofxSpec of cofx) {
674
+ if (cofxSpec.length === 1) {
675
+ cofxInterceptors.push(getInjectCofxInterceptor(cofxSpec[0]));
676
+ } else if (cofxSpec.length === 2) {
677
+ cofxInterceptors.push(getInjectCofxInterceptor(cofxSpec[0], cofxSpec[1]));
678
+ } else {
679
+ consoleLog("warn", "[reflex] invalid cofx specification:", cofxSpec);
680
+ }
681
+ }
682
+ }
683
+ const validatedInterceptors = [];
684
+ if (finalInterceptors) {
685
+ for (const interceptorCandidate of finalInterceptors) {
686
+ if (isInterceptor(interceptorCandidate)) {
687
+ validatedInterceptors.push(interceptorCandidate);
688
+ } else {
689
+ consoleLog("error", "[reflex] invalid interceptor provided for event:", id, "interceptor:", interceptorCandidate);
690
+ }
691
+ }
692
+ }
693
+ const allInterceptors = [...cofxInterceptors, ...validatedInterceptors];
694
+ if (allInterceptors.length > 0) {
695
+ setInterceptors(id, allInterceptors);
696
+ }
697
+ }
698
+ (0, import_immer.enablePatches)();
699
+ function eventHandlerInterceptor(handler) {
700
+ return {
701
+ id: "fx-handler",
702
+ before(context) {
703
+ const coeffects = context.coeffects;
704
+ const event = coeffects.event;
705
+ const params = event.slice(1);
706
+ let effects = [];
707
+ const [newDb, patches] = (0, import_immer.produceWithPatches)(
708
+ getAppDb(),
709
+ (draftDb) => {
710
+ coeffects.draftDb = draftDb;
711
+ effects = handler({ ...coeffects }, ...params) || [];
712
+ }
713
+ );
714
+ context.newDb = newDb;
715
+ context.patches = patches;
716
+ if (!Array.isArray(effects)) {
717
+ consoleLog("warn", `[reflex] effects expects a vector, but was given ${typeof effects}`);
718
+ } else {
719
+ context.effects = [...context.effects || [], ...effects];
720
+ }
721
+ return context;
722
+ }
723
+ };
724
+ }
725
+ var injectGlobalInterceptors = {
726
+ id: "inject-global-interceptors",
727
+ before(context) {
728
+ const globals = getGlobalInterceptors();
729
+ context.queue = [...globals, ...context.queue];
730
+ return context;
731
+ }
732
+ };
733
+ function handle(eventV) {
734
+ const eventId = eventV[0];
735
+ const handler = getHandler(KIND3, eventId);
736
+ if (!handler) {
737
+ consoleLog("error", `[reflex] no event handler registered for:`, eventId);
738
+ return;
739
+ }
740
+ const customInterceptors = getInterceptors(eventId);
741
+ const interceptors = [
742
+ doFxInterceptor,
743
+ injectGlobalInterceptors,
744
+ ...customInterceptors,
745
+ eventHandlerInterceptor(handler)
746
+ ];
747
+ withTrace(
748
+ { operation: eventId, opType: KIND3, tags: { event: eventV } },
749
+ () => {
750
+ mergeTrace({ tags: { "app-db-before": getAppDb() } });
751
+ execute(eventV, interceptors);
752
+ mergeTrace({ tags: { "app-db-after": getAppDb() } });
753
+ }
754
+ );
755
+ }
756
+ function regEventErrorHandler(handler) {
757
+ registerHandler("error", "event-handler", handler);
758
+ }
759
+ function defaultErrorHandler(originalError, reFrameError) {
760
+ consoleLog("error", "[reflex] Interceptor Exception:", {
761
+ originalError,
762
+ reFrameError,
763
+ data: reFrameError.data
764
+ });
765
+ throw originalError;
766
+ }
767
+ regEventErrorHandler(defaultErrorHandler);
768
+
769
+ // src/reaction.ts
770
+ var import_fast_deep_equal = __toESM(require("fast-deep-equal"), 1);
771
+ var Reaction = class _Reaction {
772
+ id = "";
773
+ computeFn;
774
+ deps;
775
+ dependents = /* @__PURE__ */ new Set();
776
+ watchers = [];
777
+ dirty = false;
778
+ scheduled = false;
779
+ value = void 0;
780
+ version = 0;
781
+ depsVersions = [];
782
+ subVector;
783
+ componentName;
784
+ constructor(computeFn, deps) {
785
+ this.computeFn = computeFn;
786
+ this.deps = deps;
787
+ }
788
+ static create(fn, deps) {
789
+ return new _Reaction(fn, deps);
790
+ }
791
+ getValue() {
792
+ this.ensureDirty();
793
+ this.recomputeIfNeeded(false);
794
+ return this.value;
795
+ }
796
+ getDepValue(notifyWatchers = true) {
797
+ this.recomputeIfNeeded(notifyWatchers);
798
+ return [this.value, this.version];
799
+ }
800
+ watch(callback) {
801
+ const idx = this.watchers.indexOf(callback);
802
+ if (idx === -1) {
803
+ this.watchers.push(callback);
804
+ if (this.deps) {
805
+ for (const d of this.deps)
806
+ d.ensureAliveWith(this);
807
+ }
808
+ }
809
+ }
810
+ unwatch(fn) {
811
+ const idx = this.watchers.indexOf(fn);
812
+ if (idx !== -1) {
813
+ this.watchers.splice(idx, 1);
814
+ if (this.watchers.length === 0) {
815
+ this.disposeIfUnused();
816
+ }
817
+ }
818
+ }
819
+ markDirty() {
820
+ this.dirty = true;
821
+ for (const c of this.dependents)
822
+ c.markDirty();
823
+ if (!this.isAlive) {
824
+ return;
825
+ }
826
+ this.scheduleRecompute();
827
+ }
828
+ scheduleRecompute() {
829
+ if (this.scheduled)
830
+ return;
831
+ this.scheduled = true;
832
+ queueMicrotask(() => {
833
+ this.scheduled = false;
834
+ this.recomputeIfNeeded();
835
+ });
836
+ }
837
+ recomputeIfNeeded(notifyWatchers = true) {
838
+ if (!this.dirty)
839
+ return;
840
+ try {
841
+ let changed = false;
842
+ withTrace(
843
+ { operation: this.subVector?.[0] ?? "", opType: "sub/run", tags: { queryV: this.subVector, reaction: this.id } },
844
+ () => {
845
+ if (this.isRoot) {
846
+ changed = true;
847
+ this.value = this.computeFn();
848
+ } else {
849
+ const depValues = this.deps?.map((d) => d.getDepValue(notifyWatchers)) ?? [];
850
+ const values = depValues.map(([value]) => value);
851
+ const currentVersions = depValues.map(([, version]) => version);
852
+ const versionsChanged = !(0, import_fast_deep_equal.default)(currentVersions, this.depsVersions);
853
+ if (this.value === void 0 || versionsChanged) {
854
+ let newVal = this.computeFn(...values);
855
+ changed = !(0, import_fast_deep_equal.default)(newVal, this.value);
856
+ if (changed) {
857
+ this.value = newVal;
858
+ }
859
+ this.depsVersions = currentVersions;
860
+ }
861
+ }
862
+ mergeTrace({ tags: { "cached?": !changed, "version": this.version } });
863
+ }
864
+ );
865
+ this.dirty = false;
866
+ if (changed) {
867
+ this.version++;
868
+ }
869
+ if (notifyWatchers && changed && this.watchers.length > 0) {
870
+ for (const w of this.watchers) {
871
+ try {
872
+ withTrace(
873
+ {
874
+ opType: "render",
875
+ operation: this.componentName ?? "component",
876
+ tags: this.componentName ? { componentName: this.componentName } : {}
877
+ },
878
+ () => {
879
+ w(this.value);
880
+ }
881
+ );
882
+ } catch (error) {
883
+ consoleLog("error", "[reflex] Error in reaction watcher:", error);
884
+ }
885
+ }
886
+ }
887
+ } catch (error) {
888
+ consoleLog("error", `[reflex] Error in reaction computation ${this.id}:`, error);
889
+ throw error;
890
+ }
891
+ }
892
+ ensureDirty() {
893
+ this.dirty = true;
894
+ if (this.deps) {
895
+ for (const d of this.deps)
896
+ d.ensureDirty();
897
+ }
898
+ }
899
+ ensureAliveWith(child) {
900
+ if (this.value === void 0) {
901
+ this.dirty = true;
902
+ }
903
+ this.dependents.add(child);
904
+ if (this.deps) {
905
+ for (const d of this.deps)
906
+ d.ensureAliveWith(this);
907
+ }
908
+ }
909
+ disposeIfUnused() {
910
+ if (this.isAlive)
911
+ return;
912
+ this.value = void 0;
913
+ this.version = 0;
914
+ this.depsVersions = [];
915
+ this.dirty = false;
916
+ this.scheduled = false;
917
+ withTrace(
918
+ {
919
+ operation: this.subVector?.[0] ?? "",
920
+ opType: "sub/dispose",
921
+ tags: {
922
+ subVector: this.subVector,
923
+ reaction: this.id
924
+ }
925
+ },
926
+ () => {
927
+ if (this.deps) {
928
+ for (const d of this.deps) {
929
+ d.dependents.delete(this);
930
+ d.disposeIfUnused();
931
+ }
932
+ }
933
+ }
934
+ );
935
+ }
936
+ setId(id) {
937
+ this.id = id;
938
+ }
939
+ getId() {
940
+ return this.id;
941
+ }
942
+ getSubVector() {
943
+ return this.subVector;
944
+ }
945
+ setSubVector(subVector) {
946
+ this.subVector = subVector;
947
+ }
948
+ get isAlive() {
949
+ return this.hasWatchers || this.hasDependents;
950
+ }
951
+ get hasWatchers() {
952
+ return this.watchers.length > 0;
953
+ }
954
+ get hasDependents() {
955
+ return this.dependents.size > 0;
956
+ }
957
+ get isDirty() {
958
+ return this.dirty;
959
+ }
960
+ get isRoot() {
961
+ return this.deps === void 0 || this.deps.length === 0;
962
+ }
963
+ setComponentName(componentName) {
964
+ this.componentName = componentName;
965
+ }
966
+ };
967
+
968
+ // src/subs.ts
969
+ var KIND4 = "sub";
970
+ var KIND_DEPS = "subDeps";
971
+ function regSub(id, computeFn, depsFn) {
972
+ if (hasHandler(KIND4, id)) {
973
+ consoleLog("warn", `[reflex] Overriding. Subscription '${id}' already registered.`);
974
+ }
975
+ if (!computeFn) {
976
+ registerHandler(KIND4, id, () => getAppDb()[id]);
977
+ registerHandler(KIND_DEPS, id, () => []);
978
+ } else {
979
+ if (!depsFn) {
980
+ consoleLog("error", `[reflex] Subscription '${id}' has computeFn but missing depsFn. Computed subscriptions must specify their dependencies.`);
981
+ return;
982
+ }
983
+ registerHandler(KIND4, id, computeFn);
984
+ registerHandler(KIND_DEPS, id, depsFn);
985
+ }
986
+ }
987
+ function getOrCreateReaction(subVector) {
988
+ const subId = subVector[0];
989
+ if (!hasHandler(KIND4, subId)) {
990
+ consoleLog("error", `[reflex] no sub handler registered for: ${subId}`);
991
+ return null;
992
+ }
993
+ withTrace({ operation: subVector[0], opType: KIND4, tags: { queryV: subVector } }, () => {
994
+ });
995
+ const computeFn = getHandler(KIND4, subId);
996
+ const subVectorKey = JSON.stringify(subVector);
997
+ const existingReaction = getReaction(subVectorKey);
998
+ if (existingReaction) {
999
+ mergeTrace({ tags: { "cached?": true, reaction: existingReaction.getId() } });
1000
+ return existingReaction;
1001
+ }
1002
+ mergeTrace({ tags: { "cached?": false } });
1003
+ const params = subVector.length > 1 ? subVector.slice(1) : [];
1004
+ const depsFn = getHandler(KIND_DEPS, subId);
1005
+ const depsVectors = depsFn(...params);
1006
+ const depsReactions = depsVectors.map((depVector) => {
1007
+ return getOrCreateReaction(depVector);
1008
+ });
1009
+ const reaction = Reaction.create(
1010
+ (...depValues) => {
1011
+ if (params.length > 0) {
1012
+ return computeFn(...depValues, ...params);
1013
+ } else {
1014
+ return computeFn(...depValues);
1015
+ }
1016
+ },
1017
+ depsReactions
1018
+ );
1019
+ mergeTrace({ reaction: reaction.getId() });
1020
+ reaction.setId(subVectorKey);
1021
+ reaction.setSubVector(subVector);
1022
+ setReaction(subVectorKey, reaction);
1023
+ return reaction;
1024
+ }
1025
+
1026
+ // src/debounce.ts
1027
+ var timeout = /* @__PURE__ */ new Map();
1028
+ function clear(eventKey) {
1029
+ const eventTimeout = timeout.get(eventKey);
1030
+ if (eventTimeout) {
1031
+ clearTimeout(eventTimeout);
1032
+ timeout.delete(eventKey);
1033
+ }
1034
+ }
1035
+ function debounceAndDispatch(event, durationMs) {
1036
+ const eventKey = event[0];
1037
+ clear(eventKey);
1038
+ const timeoutId = setTimeout(() => {
1039
+ timeout.delete(eventKey);
1040
+ dispatch(event);
1041
+ }, durationMs);
1042
+ timeout.set(eventKey, timeoutId);
1043
+ }
1044
+ var throttle = /* @__PURE__ */ new Map();
1045
+ function throttleAndDispatch(event, durationMs) {
1046
+ const eventKey = event[0];
1047
+ if (!throttle.get(eventKey)) {
1048
+ throttle.set(eventKey, true);
1049
+ setTimeout(() => {
1050
+ throttle.delete(eventKey);
1051
+ }, durationMs);
1052
+ dispatch(event);
1053
+ }
1054
+ }
1055
+
1056
+ // src/hook.ts
1057
+ var import_react = require("react");
1058
+ function useSubscription(subVector, componentName = "react component") {
1059
+ const [val, setVal] = (0, import_react.useState)(() => {
1060
+ const reaction = getOrCreateReaction(subVector);
1061
+ return reaction ? reaction.getValue() : void 0;
1062
+ });
1063
+ (0, import_react.useEffect)(() => {
1064
+ const reaction = getOrCreateReaction(subVector);
1065
+ if (!reaction)
1066
+ return;
1067
+ reaction.setComponentName(componentName);
1068
+ reaction.watch(setVal);
1069
+ return () => {
1070
+ reaction.unwatch(setVal);
1071
+ };
1072
+ }, []);
1073
+ return val;
1074
+ }
1075
+ // Annotate the CommonJS export names for ESM import in node:
1076
+ 0 && (module.exports = {
1077
+ clearGlobalInterceptors,
1078
+ debounceAndDispatch,
1079
+ defaultErrorHandler,
1080
+ disableTracing,
1081
+ dispatch,
1082
+ enableTracePrint,
1083
+ enableTracing,
1084
+ getGlobalInterceptors,
1085
+ initAppDb,
1086
+ isDebugEnabled,
1087
+ regCoeffect,
1088
+ regEffect,
1089
+ regEvent,
1090
+ regEventErrorHandler,
1091
+ regGlobalInterceptor,
1092
+ regSub,
1093
+ registerTraceCb,
1094
+ setDebugEnabled,
1095
+ throttleAndDispatch,
1096
+ useSubscription
1097
+ });