@rplx/core 0.2.0 → 0.2.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.d.mts CHANGED
@@ -79,6 +79,12 @@ interface StoreConfig<State, Cofx = {}> {
79
79
  enabled?: boolean;
80
80
  debounceTime?: number;
81
81
  };
82
+ /**
83
+ * Optional scheduler for batching state updates
84
+ * Defaults to requestAnimationFrame
85
+ * @internal - For testing purposes only
86
+ */
87
+ __scheduler?: (callback: FrameRequestCallback) => number;
82
88
  }
83
89
  interface QueuedEvent {
84
90
  eventKey: string;
package/dist/index.d.ts CHANGED
@@ -79,6 +79,12 @@ interface StoreConfig<State, Cofx = {}> {
79
79
  enabled?: boolean;
80
80
  debounceTime?: number;
81
81
  };
82
+ /**
83
+ * Optional scheduler for batching state updates
84
+ * Defaults to requestAnimationFrame
85
+ * @internal - For testing purposes only
86
+ */
87
+ __scheduler?: (callback: FrameRequestCallback) => number;
82
88
  }
83
89
  interface QueuedEvent {
84
90
  eventKey: string;
package/dist/index.js CHANGED
@@ -77,6 +77,8 @@ function createRegistrar(options) {
77
77
  } else {
78
78
  console.warn(`re-frame: can't clear ${kind} handler for ${id}. Handler not found.`);
79
79
  }
80
+ } else {
81
+ console.warn(`re-frame: can't clear ${kind} handler for ${id}. Handler not found.`);
80
82
  }
81
83
  }
82
84
  }
@@ -84,13 +86,13 @@ function createRegistrar(options) {
84
86
  }
85
87
 
86
88
  // src/modules/state.ts
87
- function createStateManager(initialState, onStateChange, onStateChangeForSubscriptions) {
89
+ function createStateManager(initialState, onStateChange, onStateChangeForSubscriptions, scheduler = requestAnimationFrame) {
88
90
  let state = initialState;
89
91
  const stateChanges = [];
90
92
  let rafId = null;
91
93
  const scheduleNotification = () => {
92
94
  if (rafId !== null) return;
93
- rafId = requestAnimationFrame(() => {
95
+ rafId = scheduler(() => {
94
96
  rafId = null;
95
97
  if (stateChanges.length > 0) {
96
98
  const latestState = stateChanges[stateChanges.length - 1];
@@ -375,21 +377,21 @@ function createEffectExecutor(deps) {
375
377
  registerEffect("db", (newState) => {
376
378
  stateManager.setState(newState);
377
379
  });
378
- registerEffect("dispatch", async (config) => {
380
+ registerEffect("dispatch", (config) => {
379
381
  if (!config || typeof config.event !== "string") {
380
382
  console.error("re-frame: ignoring bad :dispatch value. Expected {event: string, payload: any}, but got:", config);
381
383
  return;
382
384
  }
383
- await dispatch(config.event, config.payload);
385
+ dispatch(config.event, config.payload);
384
386
  });
385
- registerEffect("dispatch-n", async (configs) => {
387
+ registerEffect("dispatch-n", (configs) => {
386
388
  if (!Array.isArray(configs)) {
387
389
  console.error("re-frame: ignoring bad :dispatch-n value. Expected an array, but got:", configs);
388
390
  return;
389
391
  }
390
392
  for (const config of configs) {
391
393
  if (config && config.event) {
392
- await dispatch(config.event, config.payload);
394
+ dispatch(config.event, config.payload);
393
395
  }
394
396
  }
395
397
  });
@@ -676,33 +678,54 @@ function createRouter(deps) {
676
678
  const { eventManager } = deps;
677
679
  const eventQueue = [];
678
680
  let isProcessing = false;
681
+ let processingPromise = null;
679
682
  async function processEvent(eventKey, payload) {
680
683
  await eventManager.handleEvent(eventKey, payload);
681
684
  }
682
685
  async function processQueue() {
683
686
  if (isProcessing) return;
684
687
  isProcessing = true;
685
- try {
686
- while (eventQueue.length > 0) {
687
- const event = eventQueue.shift();
688
- await processEvent(event.eventKey, event.payload);
688
+ const promise = (async () => {
689
+ try {
690
+ while (eventQueue.length > 0) {
691
+ const event = eventQueue.shift();
692
+ try {
693
+ await processEvent(event.eventKey, event.payload);
694
+ event.resolve();
695
+ } catch (error) {
696
+ event.reject(error instanceof Error ? error : new Error(String(error)));
697
+ }
698
+ }
699
+ } finally {
700
+ isProcessing = false;
701
+ processingPromise = null;
689
702
  }
690
- } finally {
691
- isProcessing = false;
692
- }
703
+ })();
704
+ processingPromise = promise;
705
+ await promise;
693
706
  }
694
707
  function dispatch(eventKey, payload) {
695
708
  return new Promise((resolve, reject) => {
696
- eventQueue.push({ eventKey, payload });
709
+ eventQueue.push({
710
+ eventKey,
711
+ payload,
712
+ resolve,
713
+ reject
714
+ });
697
715
  if (!isProcessing) {
698
- processQueue().then(resolve).catch(reject);
699
- } else {
700
- resolve();
716
+ processQueue().catch((error) => {
717
+ console.error("Unexpected error in processQueue:", error);
718
+ });
701
719
  }
702
720
  });
703
721
  }
704
722
  async function flush() {
705
- await processQueue();
723
+ if (processingPromise) {
724
+ await processingPromise;
725
+ }
726
+ if (eventQueue.length > 0) {
727
+ await processQueue();
728
+ }
706
729
  }
707
730
  return {
708
731
  dispatch,
@@ -898,7 +921,9 @@ function createStore(config) {
898
921
  if (subscriptionManagerRef) {
899
922
  subscriptionManagerRef.notifyListeners(state);
900
923
  }
901
- }
924
+ },
925
+ config.__scheduler ?? requestAnimationFrame
926
+ // Use scheduler if provided, otherwise default to RAF
902
927
  );
903
928
  const subscriptionManager = createSubscriptionManager({
904
929
  stateManager,
@@ -1075,7 +1100,8 @@ function debug() {
1075
1100
  return context;
1076
1101
  },
1077
1102
  after: (context) => {
1078
- console.log("New State:", context.coeffects.db);
1103
+ const newState = context.effects.db !== void 0 ? context.effects.db : context.coeffects.db;
1104
+ console.log("New State:", newState);
1079
1105
  console.log("Effects:", context.effects);
1080
1106
  console.groupEnd();
1081
1107
  return context;
@@ -1086,7 +1112,8 @@ function after(fn) {
1086
1112
  return {
1087
1113
  id: "after",
1088
1114
  after: (context) => {
1089
- fn(context.coeffects.db, context.effects);
1115
+ const newState = context.effects.db !== void 0 ? context.effects.db : context.coeffects.db;
1116
+ fn(newState, context.effects);
1090
1117
  return context;
1091
1118
  }
1092
1119
  };
@@ -1109,7 +1136,8 @@ function validate(schema) {
1109
1136
  return {
1110
1137
  id: "validate",
1111
1138
  after: (context) => {
1112
- const result = schema(context.coeffects.db);
1139
+ const stateToValidate = context.effects.db !== void 0 ? context.effects.db : context.coeffects.db;
1140
+ const result = schema(stateToValidate);
1113
1141
  if (result !== true) {
1114
1142
  console.error("State validation failed:", result);
1115
1143
  }
package/dist/index.mjs CHANGED
@@ -42,6 +42,8 @@ function createRegistrar(options) {
42
42
  } else {
43
43
  console.warn(`re-frame: can't clear ${kind} handler for ${id}. Handler not found.`);
44
44
  }
45
+ } else {
46
+ console.warn(`re-frame: can't clear ${kind} handler for ${id}. Handler not found.`);
45
47
  }
46
48
  }
47
49
  }
@@ -49,13 +51,13 @@ function createRegistrar(options) {
49
51
  }
50
52
 
51
53
  // src/modules/state.ts
52
- function createStateManager(initialState, onStateChange, onStateChangeForSubscriptions) {
54
+ function createStateManager(initialState, onStateChange, onStateChangeForSubscriptions, scheduler = requestAnimationFrame) {
53
55
  let state = initialState;
54
56
  const stateChanges = [];
55
57
  let rafId = null;
56
58
  const scheduleNotification = () => {
57
59
  if (rafId !== null) return;
58
- rafId = requestAnimationFrame(() => {
60
+ rafId = scheduler(() => {
59
61
  rafId = null;
60
62
  if (stateChanges.length > 0) {
61
63
  const latestState = stateChanges[stateChanges.length - 1];
@@ -340,21 +342,21 @@ function createEffectExecutor(deps) {
340
342
  registerEffect("db", (newState) => {
341
343
  stateManager.setState(newState);
342
344
  });
343
- registerEffect("dispatch", async (config) => {
345
+ registerEffect("dispatch", (config) => {
344
346
  if (!config || typeof config.event !== "string") {
345
347
  console.error("re-frame: ignoring bad :dispatch value. Expected {event: string, payload: any}, but got:", config);
346
348
  return;
347
349
  }
348
- await dispatch(config.event, config.payload);
350
+ dispatch(config.event, config.payload);
349
351
  });
350
- registerEffect("dispatch-n", async (configs) => {
352
+ registerEffect("dispatch-n", (configs) => {
351
353
  if (!Array.isArray(configs)) {
352
354
  console.error("re-frame: ignoring bad :dispatch-n value. Expected an array, but got:", configs);
353
355
  return;
354
356
  }
355
357
  for (const config of configs) {
356
358
  if (config && config.event) {
357
- await dispatch(config.event, config.payload);
359
+ dispatch(config.event, config.payload);
358
360
  }
359
361
  }
360
362
  });
@@ -641,33 +643,54 @@ function createRouter(deps) {
641
643
  const { eventManager } = deps;
642
644
  const eventQueue = [];
643
645
  let isProcessing = false;
646
+ let processingPromise = null;
644
647
  async function processEvent(eventKey, payload) {
645
648
  await eventManager.handleEvent(eventKey, payload);
646
649
  }
647
650
  async function processQueue() {
648
651
  if (isProcessing) return;
649
652
  isProcessing = true;
650
- try {
651
- while (eventQueue.length > 0) {
652
- const event = eventQueue.shift();
653
- await processEvent(event.eventKey, event.payload);
653
+ const promise = (async () => {
654
+ try {
655
+ while (eventQueue.length > 0) {
656
+ const event = eventQueue.shift();
657
+ try {
658
+ await processEvent(event.eventKey, event.payload);
659
+ event.resolve();
660
+ } catch (error) {
661
+ event.reject(error instanceof Error ? error : new Error(String(error)));
662
+ }
663
+ }
664
+ } finally {
665
+ isProcessing = false;
666
+ processingPromise = null;
654
667
  }
655
- } finally {
656
- isProcessing = false;
657
- }
668
+ })();
669
+ processingPromise = promise;
670
+ await promise;
658
671
  }
659
672
  function dispatch(eventKey, payload) {
660
673
  return new Promise((resolve, reject) => {
661
- eventQueue.push({ eventKey, payload });
674
+ eventQueue.push({
675
+ eventKey,
676
+ payload,
677
+ resolve,
678
+ reject
679
+ });
662
680
  if (!isProcessing) {
663
- processQueue().then(resolve).catch(reject);
664
- } else {
665
- resolve();
681
+ processQueue().catch((error) => {
682
+ console.error("Unexpected error in processQueue:", error);
683
+ });
666
684
  }
667
685
  });
668
686
  }
669
687
  async function flush() {
670
- await processQueue();
688
+ if (processingPromise) {
689
+ await processingPromise;
690
+ }
691
+ if (eventQueue.length > 0) {
692
+ await processQueue();
693
+ }
671
694
  }
672
695
  return {
673
696
  dispatch,
@@ -863,7 +886,9 @@ function createStore(config) {
863
886
  if (subscriptionManagerRef) {
864
887
  subscriptionManagerRef.notifyListeners(state);
865
888
  }
866
- }
889
+ },
890
+ config.__scheduler ?? requestAnimationFrame
891
+ // Use scheduler if provided, otherwise default to RAF
867
892
  );
868
893
  const subscriptionManager = createSubscriptionManager({
869
894
  stateManager,
@@ -1040,7 +1065,8 @@ function debug() {
1040
1065
  return context;
1041
1066
  },
1042
1067
  after: (context) => {
1043
- console.log("New State:", context.coeffects.db);
1068
+ const newState = context.effects.db !== void 0 ? context.effects.db : context.coeffects.db;
1069
+ console.log("New State:", newState);
1044
1070
  console.log("Effects:", context.effects);
1045
1071
  console.groupEnd();
1046
1072
  return context;
@@ -1051,7 +1077,8 @@ function after(fn) {
1051
1077
  return {
1052
1078
  id: "after",
1053
1079
  after: (context) => {
1054
- fn(context.coeffects.db, context.effects);
1080
+ const newState = context.effects.db !== void 0 ? context.effects.db : context.coeffects.db;
1081
+ fn(newState, context.effects);
1055
1082
  return context;
1056
1083
  }
1057
1084
  };
@@ -1074,7 +1101,8 @@ function validate(schema) {
1074
1101
  return {
1075
1102
  id: "validate",
1076
1103
  after: (context) => {
1077
- const result = schema(context.coeffects.db);
1104
+ const stateToValidate = context.effects.db !== void 0 ? context.effects.db : context.coeffects.db;
1105
+ const result = schema(stateToValidate);
1078
1106
  if (result !== true) {
1079
1107
  console.error("State validation failed:", result);
1080
1108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rplx/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A re-frame inspired state management library for TypeScript",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,9 +19,18 @@
19
19
  "scripts": {
20
20
  "build": "tsup src/index.ts --format cjs,esm --dts",
21
21
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
- "clean": "rm -rf dist"
22
+ "clean": "rm -rf dist",
23
+ "test": "jest",
24
+ "test:watch": "jest --watch",
25
+ "test:coverage": "jest --coverage",
26
+ "test:types": "jest __tests__/type-tests"
23
27
  },
24
28
  "devDependencies": {
29
+ "@jest/test-sequencer": "^30.2.0",
30
+ "@types/jest": "^30.0.0",
31
+ "expect-type": "^1.3.0",
32
+ "jest": "^30.2.0",
33
+ "ts-jest": "^29.4.6",
25
34
  "tsup": "^8.0.0",
26
35
  "typescript": "^5.2.2"
27
36
  },
@@ -34,13 +43,13 @@
34
43
  "license": "MIT",
35
44
  "repository": {
36
45
  "type": "git",
37
- "url": "https://github.com/anonymeye/ripple.git",
38
- "directory": "packages/ripple"
46
+ "url": "https://github.com/anonymeye/ripplex.git",
47
+ "directory": "packages/ripplex"
39
48
  },
40
49
  "bugs": {
41
- "url": "https://github.com/anonymeye/ripple/issues"
50
+ "url": "https://github.com/anonymeye/ripplex/issues"
42
51
  },
43
- "homepage": "https://github.com/anonymeye/ripple#readme",
52
+ "homepage": "https://github.com/anonymeye/ripplex#readme",
44
53
  "publishConfig": {
45
54
  "access": "public"
46
55
  }