@x-oasis/batchinator 0.1.37 → 0.1.38

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.
@@ -1,16 +1,18 @@
1
1
 
2
- > @x-oasis/batchinator@0.1.37 build /home/runner/work/x-oasis/x-oasis/packages/schedule/batchinator
2
+ > @x-oasis/batchinator@0.1.38 build /home/runner/work/x-oasis/x-oasis/packages/schedule/batchinator
3
3
  > tsdx build --tsconfig tsconfig.build.json
4
4
 
5
5
  @rollup/plugin-replace: 'preventAssignment' currently defaults to false. It is recommended to set this option to `true`, as the next major version will default this option to `true`.
6
6
  @rollup/plugin-replace: 'preventAssignment' currently defaults to false. It is recommended to set this option to `true`, as the next major version will default this option to `true`.
7
7
  ⠙ Creating entry file
8
- ✓ Creating entry file 2 secs
8
+ ✓ Creating entry file 2.5 secs
9
9
  ⠙ Building modules
10
10
  ⠹ Building modules
11
11
  ⠸ Building modules
12
12
  ⠼ Building modules
13
+ ⠴ Building modules
14
+ ⠦ Building modules
13
15
  [tsdx]: Your rootDir is currently set to "./". Please change your rootDir to "./src".
14
16
  TSDX has deprecated setting tsconfig.compilerOptions.rootDir to "./" as it caused buggy output for declarationMaps and more.
15
17
  You may also need to change your include to remove "test", which also caused declarations to be unnecessarily created for test files.
16
- ✓ Building modules 2.8 secs
18
+ ✓ Building modules 4.1 secs
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @x-oasis/batchinator
2
2
 
3
+ ## 0.1.38
4
+
5
+ ### Patch Changes
6
+
7
+ - f1aae14: bump version
8
+ - Updated dependencies [f1aae14]
9
+ - @x-oasis/default-boolean-value@0.1.38
10
+ - @x-oasis/debounce@0.1.38
11
+
3
12
  ## 0.1.37
4
13
 
5
14
  ### Patch Changes
package/README.md CHANGED
@@ -1,19 +1,281 @@
1
1
  # @x-oasis/batchinator
2
2
 
3
+ Batches callback executions with configurable leading/trailing behavior. Similar to debounce/throttle but with more control over execution timing.
4
+
5
+ Inspired by [React Native's Batchinator](https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js).
6
+
3
7
  ## Installation
4
8
 
5
9
  ```bash
6
- $ npm i @x-oasis/batchinator
10
+ npm install @x-oasis/batchinator
11
+ # or
12
+ pnpm add @x-oasis/batchinator
13
+ # or
14
+ yarn add @x-oasis/batchinator
7
15
  ```
8
16
 
9
- ## How to use
17
+ ## Usage
18
+
19
+ ### Basic Usage
10
20
 
11
21
  ```typescript
12
- import Batchinator from '@x-oasis/batchinator'
22
+ import Batchinator from '@x-oasis/batchinator';
23
+
24
+ const callback = (message: string) => {
25
+ console.log(message);
26
+ };
27
+
28
+ const batchinator = new Batchinator(callback, 100);
29
+
30
+ // Schedule multiple calls
31
+ batchinator.schedule('First');
32
+ batchinator.schedule('Second');
33
+ batchinator.schedule('Third');
34
+
35
+ // Only the last call ('Third') will execute after 100ms
13
36
  ```
14
37
 
15
- ## How to run test
38
+ ### With Leading Option
16
39
 
17
- ```bash
18
- $ pnpm test
19
- ```
40
+ Execute immediately on the first call, then batch subsequent calls:
41
+
42
+ ```typescript
43
+ import Batchinator from '@x-oasis/batchinator';
44
+
45
+ const batchinator = new Batchinator(callback, 100, {
46
+ leading: true,
47
+ trailing: true
48
+ });
49
+
50
+ batchinator.schedule('First'); // Executes immediately
51
+ batchinator.schedule('Second'); // Batched
52
+ batchinator.schedule('Third'); // Batched
53
+ // After 100ms, 'Third' executes (trailing)
54
+ ```
55
+
56
+ ### With Trailing: false
57
+
58
+ Only execute on the leading edge:
59
+
60
+ ```typescript
61
+ import Batchinator from '@x-oasis/batchinator';
62
+
63
+ const batchinator = new Batchinator(callback, 100, {
64
+ leading: true,
65
+ trailing: false
66
+ });
67
+
68
+ batchinator.schedule('First'); // Executes immediately
69
+ batchinator.schedule('Second'); // Ignored
70
+ batchinator.schedule('Third'); // Ignored
71
+ // No trailing execution
72
+ ```
73
+
74
+ ### Flush Scheduled Task
75
+
76
+ Immediately execute the scheduled task:
77
+
78
+ ```typescript
79
+ import Batchinator from '@x-oasis/batchinator';
80
+
81
+ const batchinator = new Batchinator(callback, 100);
82
+
83
+ batchinator.schedule('First');
84
+ batchinator.schedule('Second');
85
+ batchinator.flush(); // Executes immediately with 'Second'
86
+
87
+ // Or flush with new arguments
88
+ batchinator.flush('Custom');
89
+ ```
90
+
91
+ ### Dispose
92
+
93
+ Cancel the scheduled task and optionally execute it:
94
+
95
+ ```typescript
96
+ import Batchinator from '@x-oasis/batchinator';
97
+
98
+ const batchinator = new Batchinator(callback, 100);
99
+
100
+ batchinator.schedule('First');
101
+ batchinator.schedule('Second');
102
+
103
+ // Dispose and execute
104
+ batchinator.dispose(); // Executes with 'Second'
105
+
106
+ // Dispose without executing
107
+ batchinator.dispose({ abort: true }); // Cancels without executing
108
+ ```
109
+
110
+ ### Check Schedule Status
111
+
112
+ Check if a task is currently scheduled:
113
+
114
+ ```typescript
115
+ import Batchinator from '@x-oasis/batchinator';
116
+
117
+ const batchinator = new Batchinator(callback, 100);
118
+
119
+ batchinator.schedule('First');
120
+ console.log(batchinator.inSchedule()); // true
121
+
122
+ // After execution or cancel
123
+ batchinator.flush();
124
+ console.log(batchinator.inSchedule()); // false
125
+ ```
126
+
127
+ ## API
128
+
129
+ ### `new Batchinator(callback, delayMS, options?)`
130
+
131
+ Creates a new Batchinator instance.
132
+
133
+ #### Parameters
134
+
135
+ - `callback` (`Function`): The function to batch.
136
+ - `delayMS` (`number`): The delay in milliseconds before executing the batched callback.
137
+ - `options` (`Object`, optional): Configuration options.
138
+ - `leading` (`boolean`, default: `false`): Execute immediately on the first call.
139
+ - `trailing` (`boolean`, default: `true`): Execute after the delay period.
140
+
141
+ #### Returns
142
+
143
+ Returns a new `Batchinator` instance.
144
+
145
+ ### Instance Methods
146
+
147
+ #### `schedule(...args)`
148
+
149
+ Schedule the callback execution with the given arguments. If called multiple times, only the last call's arguments will be used.
150
+
151
+ - `args` (`...any[]`): Arguments to pass to the callback.
152
+
153
+ #### `flush(...args)`
154
+
155
+ Immediately execute the scheduled task. If arguments are provided, they will be used instead of stored arguments.
156
+
157
+ - `args` (`...any[]`, optional): Optional arguments to use instead of stored args.
158
+
159
+ #### `dispose(options?)`
160
+
161
+ Dispose the scheduled task. By default, executes the callback with stored arguments unless `abort` is `true`.
162
+
163
+ - `options` (`Object`, optional): Configuration options.
164
+ - `abort` (`boolean`, default: `false`): If `true`, cancel without executing callback.
165
+
166
+ #### `inSchedule()`
167
+
168
+ Check if a task is currently scheduled.
169
+
170
+ - Returns: `boolean` - `true` if a task is scheduled, `false` otherwise.
171
+
172
+ ## Examples
173
+
174
+ ### React Native Interaction Manager
175
+
176
+ ```typescript
177
+ import Batchinator from '@x-oasis/batchinator';
178
+
179
+ // Batch UI updates to avoid blocking the main thread
180
+ const updateUI = (data: any) => {
181
+ // Update UI with data
182
+ };
183
+
184
+ const batchinator = new Batchinator(updateUI, 16, {
185
+ leading: false,
186
+ trailing: true
187
+ });
188
+
189
+ // Multiple rapid updates
190
+ for (let i = 0; i < 100; i++) {
191
+ batchinator.schedule({ index: i });
192
+ }
193
+ // Only the last update executes after 16ms
194
+ ```
195
+
196
+ ### Form Input Batching
197
+
198
+ ```typescript
199
+ import Batchinator from '@x-oasis/batchinator';
200
+
201
+ const saveForm = (formData: any) => {
202
+ // Save form data
203
+ console.log('Saving:', formData);
204
+ };
205
+
206
+ const batchinator = new Batchinator(saveForm, 500, {
207
+ leading: false,
208
+ trailing: true
209
+ });
210
+
211
+ // User types in form
212
+ input.addEventListener('input', (e) => {
213
+ batchinator.schedule({ value: e.target.value });
214
+ });
215
+ // Form saves 500ms after user stops typing
216
+ ```
217
+
218
+ ### Immediate Execution with Batching
219
+
220
+ ```typescript
221
+ import Batchinator from '@x-oasis/batchinator';
222
+
223
+ const logMessage = (message: string) => {
224
+ console.log(message);
225
+ };
226
+
227
+ const batchinator = new Batchinator(logMessage, 1000, {
228
+ leading: true,
229
+ trailing: true
230
+ });
231
+
232
+ batchinator.schedule('First'); // Executes immediately
233
+ batchinator.schedule('Second'); // Batched
234
+ batchinator.schedule('Third'); // Batched
235
+ // After 1000ms, 'Third' executes
236
+ ```
237
+
238
+ ### Cleanup on Component Unmount
239
+
240
+ ```typescript
241
+ import Batchinator from '@x-oasis/batchinator';
242
+ import { useEffect } from 'react';
243
+
244
+ function MyComponent() {
245
+ const batchinator = new Batchinator(handleUpdate, 100);
246
+
247
+ useEffect(() => {
248
+ // Use batchinator
249
+ batchinator.schedule(data);
250
+
251
+ // Cleanup on unmount
252
+ return () => {
253
+ batchinator.dispose({ abort: true });
254
+ };
255
+ }, []);
256
+ }
257
+ ```
258
+
259
+ ## Differences from Debounce/Throttle
260
+
261
+ - **Batchinator**: Batches multiple calls and executes with the last arguments. Supports leading/trailing execution.
262
+ - **Debounce**: Delays execution until after a period of inactivity.
263
+ - **Throttle**: Limits execution to at most once per period.
264
+
265
+ ### When to Use Batchinator
266
+
267
+ - React Native Interaction Manager scenarios
268
+ - Batching UI updates
269
+ - Form auto-save (with leading: false, trailing: true)
270
+ - Event handling where you want immediate feedback but batched processing
271
+
272
+ ## See Also
273
+
274
+ - [React Native Batchinator](https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js)
275
+ - [@x-oasis/debounce](../debounce/README.md) - Creates a debounced function
276
+ - [@x-oasis/throttle](../throttle/README.md) - Creates a throttled function
277
+ - [@x-oasis/batchinate-last](../batchinate-last/README.md) - Executes with last arguments
278
+
279
+ ## License
280
+
281
+ ISC
@@ -4,15 +4,27 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
6
 
7
+ var debounce = _interopDefault(require('@x-oasis/debounce'));
7
8
  var defaultBooleanValue = _interopDefault(require('@x-oasis/default-boolean-value'));
8
9
 
9
10
  var Batchinator = /*#__PURE__*/function () {
10
11
  function Batchinator(cb, delayMS, options) {
12
+ var _this = this;
13
+ this._isScheduled = false;
14
+ this._storedArgs = null;
11
15
  this._callback = cb;
12
16
  this._delayMS = delayMS;
13
- this._taskHandle = null;
14
- this._args = null;
15
17
  this._leading = defaultBooleanValue(options == null ? void 0 : options.leading, false);
18
+ this._trailing = defaultBooleanValue(options == null ? void 0 : options.trailing, true);
19
+ this._debounced = debounce(function () {
20
+ _this._isScheduled = false;
21
+ if (_this._storedArgs !== null) {
22
+ _this._callback.apply(_this, _this._storedArgs);
23
+ }
24
+ }, delayMS, {
25
+ leading: this._leading,
26
+ trailing: this._trailing
27
+ });
16
28
  }
17
29
  var _proto = Batchinator.prototype;
18
30
  _proto.dispose = function dispose(options) {
@@ -22,57 +34,49 @@ var Batchinator = /*#__PURE__*/function () {
22
34
  };
23
35
  }
24
36
  var _options = options,
25
- abort = _options.abort;
26
- if (this._taskHandle) {
27
- this._taskHandle.cancel();
28
- this._taskHandle = null;
29
- }
30
- if (typeof this._callback === 'function' && !abort) {
31
- this._callback.apply(this, this._args);
37
+ _options$abort = _options.abort,
38
+ abort = _options$abort === void 0 ? false : _options$abort;
39
+ if (abort) {
40
+ this._debounced.cancel();
41
+ this._isScheduled = false;
42
+ this._storedArgs = null;
43
+ } else {
44
+ if (this._storedArgs !== null) {
45
+ this._debounced.flush();
46
+ }
32
47
  }
33
48
  };
34
49
  _proto.inSchedule = function inSchedule() {
35
- return !!this._taskHandle;
50
+ return this._isScheduled;
36
51
  };
37
52
  _proto.flush = function flush() {
38
53
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
39
54
  args[_key] = arguments[_key];
40
55
  }
41
- if (args.length) this._args = args;
42
- if (this._taskHandle) {
43
- this._taskHandle.cancel();
44
- this._taskHandle = null;
56
+ if (args.length > 0) {
57
+ this._storedArgs = args;
45
58
  }
46
- this._callback.apply(this, this._args);
59
+ this._debounced.flush();
60
+ this._isScheduled = false;
47
61
  };
48
62
  _proto.schedule = function schedule() {
49
- var _this = this;
50
63
  for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
51
64
  args[_key2] = arguments[_key2];
52
65
  }
53
- this._args = args;
54
- if (this._taskHandle) return;
55
- var handler = this._leading ? function () {
56
- _this._taskHandle = null;
57
- } : function () {
58
- _this._taskHandle = null;
59
- _this._callback.apply(_this, _this._args);
60
- };
61
- if (!this._delayMS) {
62
- handler();
66
+ this._storedArgs = args;
67
+ if (this._isScheduled) {
63
68
  return;
64
69
  }
65
- if (this._leading) {
66
- this._callback.apply(this, this._args);
67
- }
68
- var timeoutHandle = setTimeout(function () {
69
- handler();
70
- }, this._delayMS);
71
- this._taskHandle = {
72
- cancel: function cancel() {
73
- return clearTimeout(timeoutHandle);
70
+ if (!this._delayMS) {
71
+ if (this._leading) {
72
+ this._callback.apply(this, this._storedArgs);
73
+ } else if (this._trailing) {
74
+ this._callback.apply(this, this._storedArgs);
74
75
  }
75
- };
76
+ return;
77
+ }
78
+ this._isScheduled = true;
79
+ this._debounced();
76
80
  };
77
81
  return Batchinator;
78
82
  }();
@@ -1 +1 @@
1
- {"version":3,"file":"batchinator.cjs.development.js","sources":["../src/index.ts"],"sourcesContent":["import defaultBooleanValue from '@x-oasis/default-boolean-value';\n// import { InteractionManager } from 'react-native';\n\n// https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js\n\nclass Batchinator {\n readonly _delayMS: number;\n private _args: Array<any>;\n\n private _callback: Function;\n private _taskHandle: {\n cancel: () => void;\n };\n private _leading: boolean;\n // private _trailing: boolean\n\n constructor(\n cb: Function,\n delayMS: number,\n options?: {\n leading: boolean;\n trailing: boolean;\n }\n ) {\n this._callback = cb;\n this._delayMS = delayMS;\n this._taskHandle = null;\n this._args = null;\n this._leading = defaultBooleanValue(options?.leading, false);\n // this._trailing = defaultBooleanValue(options.trailing, true)\n }\n\n dispose(\n options: {\n abort: boolean;\n } = {\n abort: false,\n }\n ) {\n const { abort } = options;\n if (this._taskHandle) {\n this._taskHandle.cancel();\n this._taskHandle = null;\n }\n if (typeof this._callback === 'function' && !abort) {\n this._callback.apply(this, this._args);\n }\n }\n\n inSchedule() {\n return !!this._taskHandle;\n }\n\n flush(...args) {\n if (args.length) this._args = args;\n if (this._taskHandle) {\n this._taskHandle.cancel();\n this._taskHandle = null;\n }\n this._callback.apply(this, this._args);\n }\n\n schedule(...args) {\n this._args = args;\n\n if (this._taskHandle) return;\n const handler = this._leading\n ? () => {\n this._taskHandle = null;\n }\n : () => {\n this._taskHandle = null;\n this._callback.apply(this, this._args);\n };\n\n if (!this._delayMS) {\n handler();\n return;\n }\n\n if (this._leading) {\n this._callback.apply(this, this._args);\n }\n\n const timeoutHandle = setTimeout(() => {\n handler();\n }, this._delayMS);\n\n this._taskHandle = { cancel: () => clearTimeout(timeoutHandle) };\n }\n}\n\nexport default Batchinator;\n"],"names":["Batchinator","cb","delayMS","options","_callback","_delayMS","_taskHandle","_args","_leading","defaultBooleanValue","leading","_proto","prototype","dispose","abort","_options","cancel","apply","inSchedule","flush","args","Array","_len","_key","arguments","length","schedule","_len2","_key2","handler","_this","timeoutHandle","setTimeout","clearTimeout"],"mappings":";;;;;;;;AAAiE,IAK3DA,WAAW;EAWf,SAAAA,YACEC,EAAY,EACZC,OAAe,EACfC,OAGC;IAED,IAAI,CAACC,SAAS,GAAGH,EAAE;IACnB,IAAI,CAACI,QAAQ,GAAGH,OAAO;IACvB,IAAI,CAACI,WAAW,GAAG,IAAI;IACvB,IAAI,CAACC,KAAK,GAAG,IAAI;IACjB,IAAI,CAACC,QAAQ,GAAGC,mBAAmB,CAACN,OAAO,oBAAPA,OAAO,CAAEO,OAAO,EAAE,KAAK,CAAC;;EAE7D,IAAAC,MAAA,GAAAX,WAAA,CAAAY,SAAA;EAAAD,MAAA,CAEDE,OAAO,GAAP,SAAAA,QACEV;QAAAA;MAAAA,UAEI;QACFW,KAAK,EAAE;OACR;;IAED,IAAAC,QAAA,GAAkBZ,OAAO;MAAjBW,KAAK,GAAAC,QAAA,CAALD,KAAK;IACb,IAAI,IAAI,CAACR,WAAW,EAAE;MACpB,IAAI,CAACA,WAAW,CAACU,MAAM,EAAE;MACzB,IAAI,CAACV,WAAW,GAAG,IAAI;;IAEzB,IAAI,OAAO,IAAI,CAACF,SAAS,KAAK,UAAU,IAAI,CAACU,KAAK,EAAE;MAClD,IAAI,CAACV,SAAS,CAACa,KAAK,CAAC,IAAI,EAAE,IAAI,CAACV,KAAK,CAAC;;GAEzC;EAAAI,MAAA,CAEDO,UAAU,GAAV,SAAAA;IACE,OAAO,CAAC,CAAC,IAAI,CAACZ,WAAW;GAC1B;EAAAK,MAAA,CAEDQ,KAAK,GAAL,SAAAA;sCAASC,IAAI,OAAAC,KAAA,CAAAC,IAAA,GAAAC,IAAA,MAAAA,IAAA,GAAAD,IAAA,EAAAC,IAAA;MAAJH,IAAI,CAAAG,IAAA,IAAAC,SAAA,CAAAD,IAAA;;IACX,IAAIH,IAAI,CAACK,MAAM,EAAE,IAAI,CAAClB,KAAK,GAAGa,IAAI;IAClC,IAAI,IAAI,CAACd,WAAW,EAAE;MACpB,IAAI,CAACA,WAAW,CAACU,MAAM,EAAE;MACzB,IAAI,CAACV,WAAW,GAAG,IAAI;;IAEzB,IAAI,CAACF,SAAS,CAACa,KAAK,CAAC,IAAI,EAAE,IAAI,CAACV,KAAK,CAAC;GACvC;EAAAI,MAAA,CAEDe,QAAQ,GAAR,SAAAA;;uCAAYN,IAAI,OAAAC,KAAA,CAAAM,KAAA,GAAAC,KAAA,MAAAA,KAAA,GAAAD,KAAA,EAAAC,KAAA;MAAJR,IAAI,CAAAQ,KAAA,IAAAJ,SAAA,CAAAI,KAAA;;IACd,IAAI,CAACrB,KAAK,GAAGa,IAAI;IAEjB,IAAI,IAAI,CAACd,WAAW,EAAE;IACtB,IAAMuB,OAAO,GAAG,IAAI,CAACrB,QAAQ,GACzB;MACEsB,KAAI,CAACxB,WAAW,GAAG,IAAI;KACxB,GACD;MACEwB,KAAI,CAACxB,WAAW,GAAG,IAAI;MACvBwB,KAAI,CAAC1B,SAAS,CAACa,KAAK,CAACa,KAAI,EAAEA,KAAI,CAACvB,KAAK,CAAC;KACvC;IAEL,IAAI,CAAC,IAAI,CAACF,QAAQ,EAAE;MAClBwB,OAAO,EAAE;MACT;;IAGF,IAAI,IAAI,CAACrB,QAAQ,EAAE;MACjB,IAAI,CAACJ,SAAS,CAACa,KAAK,CAAC,IAAI,EAAE,IAAI,CAACV,KAAK,CAAC;;IAGxC,IAAMwB,aAAa,GAAGC,UAAU,CAAC;MAC/BH,OAAO,EAAE;KACV,EAAE,IAAI,CAACxB,QAAQ,CAAC;IAEjB,IAAI,CAACC,WAAW,GAAG;MAAEU,MAAM,EAAE,SAAAA;QAAA,OAAMiB,YAAY,CAACF,aAAa,CAAC;;KAAE;GACjE;EAAA,OAAA/B,WAAA;AAAA;;;;"}
1
+ {"version":3,"file":"batchinator.cjs.development.js","sources":["../src/index.ts"],"sourcesContent":["import debounce from '@x-oasis/debounce';\nimport defaultBooleanValue from '@x-oasis/default-boolean-value';\n\n// https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js\n\ntype BatchinatorOptions = {\n leading?: boolean;\n trailing?: boolean;\n};\n\n/**\n * Batchinator - Batches callback executions with configurable leading/trailing behavior.\n * Similar to debounce/throttle but with more control over execution timing.\n */\nclass Batchinator {\n readonly _delayMS: number;\n private _callback: (...args: any[]) => void;\n private _debounced: ReturnType<typeof debounce>;\n private _leading: boolean;\n private _trailing: boolean;\n private _isScheduled = false;\n private _storedArgs: any[] | null = null;\n\n constructor(\n cb: (...args: any[]) => void,\n delayMS: number,\n options?: BatchinatorOptions\n ) {\n this._callback = cb;\n this._delayMS = delayMS;\n this._leading = defaultBooleanValue(options?.leading, false);\n this._trailing = defaultBooleanValue(options?.trailing, true);\n\n // Create a debounced function that wraps our callback\n // The key difference from debounce: if already scheduled, schedule() only updates args\n // We handle this by tracking _isScheduled and only calling debounce on first schedule\n this._debounced = debounce(\n () => {\n this._isScheduled = false;\n if (this._storedArgs !== null) {\n this._callback(...this._storedArgs);\n }\n },\n delayMS,\n {\n leading: this._leading,\n trailing: this._trailing,\n }\n );\n }\n\n /**\n * Dispose the scheduled task\n * @param options - Configuration options\n * @param options.abort - If true, cancel without executing callback\n */\n dispose(\n options: {\n abort?: boolean;\n } = {\n abort: false,\n }\n ): void {\n const { abort = false } = options;\n if (abort) {\n this._debounced.cancel();\n this._isScheduled = false;\n this._storedArgs = null;\n } else {\n // Execute with current args if any\n if (this._storedArgs !== null) {\n this._debounced.flush();\n }\n }\n }\n\n /**\n * Check if a task is currently scheduled\n */\n inSchedule(): boolean {\n return this._isScheduled;\n }\n\n /**\n * Flush the scheduled task immediately\n * @param args - Optional arguments to use instead of stored args\n */\n flush(...args: any[]): void {\n if (args.length > 0) {\n this._storedArgs = args;\n }\n this._debounced.flush();\n this._isScheduled = false;\n }\n\n /**\n * Schedule the callback execution\n * @param args - Arguments to pass to the callback\n */\n schedule(...args: any[]): void {\n this._storedArgs = args;\n\n // If already scheduled, just update args and return (don't reset timer)\n // This is the key difference from debounce\n if (this._isScheduled) {\n return;\n }\n\n // Handle zero delay case - execute immediately based on leading/trailing\n if (!this._delayMS) {\n if (this._leading) {\n this._callback(...this._storedArgs);\n } else if (this._trailing) {\n // For zero delay with trailing, execute immediately\n this._callback(...this._storedArgs);\n }\n return;\n }\n\n // First call - mark as scheduled and call debounce\n this._isScheduled = true;\n this._debounced();\n }\n}\n\nexport default Batchinator;\n"],"names":["Batchinator","cb","delayMS","options","_callback","_delayMS","_leading","defaultBooleanValue","leading","_trailing","trailing","_debounced","debounce","_this","_isScheduled","_storedArgs","apply","_proto","prototype","dispose","abort","_options","_options$abort","cancel","flush","inSchedule","args","Array","_len","_key","arguments","length","schedule","_len2","_key2"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t,a=(t=require("@x-oasis/default-boolean-value"))&&"object"==typeof t&&"default"in t?t.default:t;exports.default=function(){function t(t,l,e){this._callback=t,this._delayMS=l,this._taskHandle=null,this._args=null,this._leading=a(null==e?void 0:e.leading,!1)}var l=t.prototype;return l.dispose=function(t){void 0===t&&(t={abort:!1});var a=t.abort;this._taskHandle&&(this._taskHandle.cancel(),this._taskHandle=null),"function"!=typeof this._callback||a||this._callback.apply(this,this._args)},l.inSchedule=function(){return!!this._taskHandle},l.flush=function(){for(var t=arguments.length,a=new Array(t),l=0;l<t;l++)a[l]=arguments[l];a.length&&(this._args=a),this._taskHandle&&(this._taskHandle.cancel(),this._taskHandle=null),this._callback.apply(this,this._args)},l.schedule=function(){for(var t=this,a=arguments.length,l=new Array(a),e=0;e<a;e++)l[e]=arguments[e];if(this._args=l,!this._taskHandle){var s=this._leading?function(){t._taskHandle=null}:function(){t._taskHandle=null,t._callback.apply(t,t._args)};if(this._delayMS){this._leading&&this._callback.apply(this,this._args);var n=setTimeout((function(){s()}),this._delayMS);this._taskHandle={cancel:function(){return clearTimeout(n)}}}else s()}},t}();
1
+ "use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var i=e(require("@x-oasis/debounce")),t=e(require("@x-oasis/default-boolean-value"));exports.default=function(){function e(e,s,l){var d=this;this._isScheduled=!1,this._storedArgs=null,this._callback=e,this._delayMS=s,this._leading=t(null==l?void 0:l.leading,!1),this._trailing=t(null==l?void 0:l.trailing,!0),this._debounced=i((function(){d._isScheduled=!1,null!==d._storedArgs&&d._callback.apply(d,d._storedArgs)}),s,{leading:this._leading,trailing:this._trailing})}var s=e.prototype;return s.dispose=function(e){void 0===e&&(e={abort:!1});var i=e.abort;void 0!==i&&i?(this._debounced.cancel(),this._isScheduled=!1,this._storedArgs=null):null!==this._storedArgs&&this._debounced.flush()},s.inSchedule=function(){return this._isScheduled},s.flush=function(){for(var e=arguments.length,i=new Array(e),t=0;t<e;t++)i[t]=arguments[t];i.length>0&&(this._storedArgs=i),this._debounced.flush(),this._isScheduled=!1},s.schedule=function(){for(var e=arguments.length,i=new Array(e),t=0;t<e;t++)i[t]=arguments[t];this._storedArgs=i,this._isScheduled||(this._delayMS?(this._isScheduled=!0,this._debounced()):(this._leading||this._trailing)&&this._callback.apply(this,this._storedArgs))},e}();
2
2
  //# sourceMappingURL=batchinator.cjs.production.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"batchinator.cjs.production.min.js","sources":["../src/index.ts"],"sourcesContent":["import defaultBooleanValue from '@x-oasis/default-boolean-value';\n// import { InteractionManager } from 'react-native';\n\n// https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js\n\nclass Batchinator {\n readonly _delayMS: number;\n private _args: Array<any>;\n\n private _callback: Function;\n private _taskHandle: {\n cancel: () => void;\n };\n private _leading: boolean;\n // private _trailing: boolean\n\n constructor(\n cb: Function,\n delayMS: number,\n options?: {\n leading: boolean;\n trailing: boolean;\n }\n ) {\n this._callback = cb;\n this._delayMS = delayMS;\n this._taskHandle = null;\n this._args = null;\n this._leading = defaultBooleanValue(options?.leading, false);\n // this._trailing = defaultBooleanValue(options.trailing, true)\n }\n\n dispose(\n options: {\n abort: boolean;\n } = {\n abort: false,\n }\n ) {\n const { abort } = options;\n if (this._taskHandle) {\n this._taskHandle.cancel();\n this._taskHandle = null;\n }\n if (typeof this._callback === 'function' && !abort) {\n this._callback.apply(this, this._args);\n }\n }\n\n inSchedule() {\n return !!this._taskHandle;\n }\n\n flush(...args) {\n if (args.length) this._args = args;\n if (this._taskHandle) {\n this._taskHandle.cancel();\n this._taskHandle = null;\n }\n this._callback.apply(this, this._args);\n }\n\n schedule(...args) {\n this._args = args;\n\n if (this._taskHandle) return;\n const handler = this._leading\n ? () => {\n this._taskHandle = null;\n }\n : () => {\n this._taskHandle = null;\n this._callback.apply(this, this._args);\n };\n\n if (!this._delayMS) {\n handler();\n return;\n }\n\n if (this._leading) {\n this._callback.apply(this, this._args);\n }\n\n const timeoutHandle = setTimeout(() => {\n handler();\n }, this._delayMS);\n\n this._taskHandle = { cancel: () => clearTimeout(timeoutHandle) };\n }\n}\n\nexport default Batchinator;\n"],"names":["Batchinator","cb","delayMS","options","this","_callback","_delayMS","_taskHandle","_args","_leading","defaultBooleanValue","leading","_proto","prototype","dispose","abort","cancel","apply","inSchedule","flush","args","Array","_len","_key","arguments","length","schedule","_len2","_key2","handler","_this","timeoutHandle","setTimeout","clearTimeout"],"mappings":"oMAgBE,SAAAA,EACEC,EACAC,EACAC,GAKAC,KAAKC,UAAYJ,EACjBG,KAAKE,SAAWJ,EAChBE,KAAKG,YAAc,KACnBH,KAAKI,MAAQ,KACbJ,KAAKK,SAAWC,QAAoBP,SAAAA,EAASQ,SAAS,GAEvD,IAAAC,EAAAZ,EAAAa,UA2DA,OA3DAD,EAEDE,QAAA,SACEX,YAAAA,IAAAA,EAEI,CACFY,OAAO,IAGT,IAAQA,EAAUZ,EAAVY,MACJX,KAAKG,cACPH,KAAKG,YAAYS,SACjBZ,KAAKG,YAAc,MAES,mBAAnBH,KAAKC,WAA6BU,GAC3CX,KAAKC,UAAUY,MAAMb,KAAMA,KAAKI,QAEnCI,EAEDM,WAAA,WACE,QAASd,KAAKG,aACfK,EAEDO,MAAA,sCAASC,MAAIC,MAAAC,GAAAC,IAAAA,EAAAD,EAAAC,IAAJH,EAAIG,GAAAC,UAAAD,GACPH,EAAKK,SAAQrB,KAAKI,MAAQY,GAC1BhB,KAAKG,cACPH,KAAKG,YAAYS,SACjBZ,KAAKG,YAAc,MAErBH,KAAKC,UAAUY,MAAMb,KAAMA,KAAKI,QACjCI,EAEDc,SAAA,6CAAYN,MAAIC,MAAAM,GAAAC,IAAAA,EAAAD,EAAAC,IAAJR,EAAIQ,GAAAJ,UAAAI,GAGd,GAFAxB,KAAKI,MAAQY,GAEThB,KAAKG,YAAT,CACA,IAAMsB,EAAUzB,KAAKK,SACjB,WACEqB,EAAKvB,YAAc,MAErB,WACEuB,EAAKvB,YAAc,KACnBuB,EAAKzB,UAAUY,MAAMa,EAAMA,EAAKtB,QAGtC,GAAKJ,KAAKE,SAAV,CAKIF,KAAKK,UACPL,KAAKC,UAAUY,MAAMb,KAAMA,KAAKI,OAGlC,IAAMuB,EAAgBC,YAAW,WAC/BH,MACCzB,KAAKE,UAERF,KAAKG,YAAc,CAAES,OAAQ,WAAA,OAAMiB,aAAaF,UAZ9CF,MAaH7B"}
1
+ {"version":3,"file":"batchinator.cjs.production.min.js","sources":["../src/index.ts"],"sourcesContent":["import debounce from '@x-oasis/debounce';\nimport defaultBooleanValue from '@x-oasis/default-boolean-value';\n\n// https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js\n\ntype BatchinatorOptions = {\n leading?: boolean;\n trailing?: boolean;\n};\n\n/**\n * Batchinator - Batches callback executions with configurable leading/trailing behavior.\n * Similar to debounce/throttle but with more control over execution timing.\n */\nclass Batchinator {\n readonly _delayMS: number;\n private _callback: (...args: any[]) => void;\n private _debounced: ReturnType<typeof debounce>;\n private _leading: boolean;\n private _trailing: boolean;\n private _isScheduled = false;\n private _storedArgs: any[] | null = null;\n\n constructor(\n cb: (...args: any[]) => void,\n delayMS: number,\n options?: BatchinatorOptions\n ) {\n this._callback = cb;\n this._delayMS = delayMS;\n this._leading = defaultBooleanValue(options?.leading, false);\n this._trailing = defaultBooleanValue(options?.trailing, true);\n\n // Create a debounced function that wraps our callback\n // The key difference from debounce: if already scheduled, schedule() only updates args\n // We handle this by tracking _isScheduled and only calling debounce on first schedule\n this._debounced = debounce(\n () => {\n this._isScheduled = false;\n if (this._storedArgs !== null) {\n this._callback(...this._storedArgs);\n }\n },\n delayMS,\n {\n leading: this._leading,\n trailing: this._trailing,\n }\n );\n }\n\n /**\n * Dispose the scheduled task\n * @param options - Configuration options\n * @param options.abort - If true, cancel without executing callback\n */\n dispose(\n options: {\n abort?: boolean;\n } = {\n abort: false,\n }\n ): void {\n const { abort = false } = options;\n if (abort) {\n this._debounced.cancel();\n this._isScheduled = false;\n this._storedArgs = null;\n } else {\n // Execute with current args if any\n if (this._storedArgs !== null) {\n this._debounced.flush();\n }\n }\n }\n\n /**\n * Check if a task is currently scheduled\n */\n inSchedule(): boolean {\n return this._isScheduled;\n }\n\n /**\n * Flush the scheduled task immediately\n * @param args - Optional arguments to use instead of stored args\n */\n flush(...args: any[]): void {\n if (args.length > 0) {\n this._storedArgs = args;\n }\n this._debounced.flush();\n this._isScheduled = false;\n }\n\n /**\n * Schedule the callback execution\n * @param args - Arguments to pass to the callback\n */\n schedule(...args: any[]): void {\n this._storedArgs = args;\n\n // If already scheduled, just update args and return (don't reset timer)\n // This is the key difference from debounce\n if (this._isScheduled) {\n return;\n }\n\n // Handle zero delay case - execute immediately based on leading/trailing\n if (!this._delayMS) {\n if (this._leading) {\n this._callback(...this._storedArgs);\n } else if (this._trailing) {\n // For zero delay with trailing, execute immediately\n this._callback(...this._storedArgs);\n }\n return;\n }\n\n // First call - mark as scheduled and call debounce\n this._isScheduled = true;\n this._debounced();\n }\n}\n\nexport default Batchinator;\n"],"names":["Batchinator","cb","delayMS","options","this","_callback","_delayMS","_leading","defaultBooleanValue","leading","_trailing","trailing","_debounced","debounce","_this","_isScheduled","_storedArgs","apply","_proto","prototype","dispose","abort","_options$abort","cancel","flush","inSchedule","args","Array","_len","_key","arguments","length","schedule","_len2","_key2"],"mappings":"0PAuBE,SAAAA,EACEC,EACAC,EACAC,cANMC,mBAAe,EACfA,iBAA4B,KAOlCA,KAAKC,UAAYJ,EACjBG,KAAKE,SAAWJ,EAChBE,KAAKG,SAAWC,QAAoBL,SAAAA,EAASM,SAAS,GACtDL,KAAKM,UAAYF,QAAoBL,SAAAA,EAASQ,UAAU,GAKxDP,KAAKQ,WAAaC,GAChB,WACEC,EAAKC,cAAe,EACK,OAArBD,EAAKE,aACPF,EAAKT,UAASY,MAAdH,EAAkBA,EAAKE,eAG3Bd,EACA,CACEO,QAASL,KAAKG,SACdI,SAAUP,KAAKM,YAGpB,IAAAQ,EAAAlB,EAAAmB,UAyEA,OAzEAD,EAODE,QAAA,SACEjB,YAAAA,IAAAA,EAEI,CACFkB,OAAO,IAGT,IAAiCC,EAAPnB,EAAlBkB,eAAKC,GAAQA,GAEnBlB,KAAKQ,WAAWW,SAChBnB,KAAKW,cAAe,EACpBX,KAAKY,YAAc,MAGM,OAArBZ,KAAKY,aACPZ,KAAKQ,WAAWY,SAGrBN,EAKDO,WAAA,WACE,OAAOrB,KAAKW,cACbG,EAMDM,MAAA,sCAASE,MAAWC,MAAAC,GAAAC,IAAAA,EAAAD,EAAAC,IAAXH,EAAWG,GAAAC,UAAAD,GACdH,EAAKK,OAAS,IAChB3B,KAAKY,YAAcU,GAErBtB,KAAKQ,WAAWY,QAChBpB,KAAKW,cAAe,GACrBG,EAMDc,SAAA,sCAAYN,MAAWC,MAAAM,GAAAC,IAAAA,EAAAD,EAAAC,IAAXR,EAAWQ,GAAAJ,UAAAI,GACrB9B,KAAKY,YAAcU,EAIftB,KAAKW,eAKJX,KAAKE,UAWVF,KAAKW,cAAe,EACpBX,KAAKQ,eAXCR,KAAKG,UAEEH,KAAKM,YADdN,KAAKC,UAASY,MAAdb,KAAkBA,KAAKY,eAW5BhB"}
@@ -1,12 +1,24 @@
1
+ import debounce from '@x-oasis/debounce';
1
2
  import defaultBooleanValue from '@x-oasis/default-boolean-value';
2
3
 
3
4
  var Batchinator = /*#__PURE__*/function () {
4
5
  function Batchinator(cb, delayMS, options) {
6
+ var _this = this;
7
+ this._isScheduled = false;
8
+ this._storedArgs = null;
5
9
  this._callback = cb;
6
10
  this._delayMS = delayMS;
7
- this._taskHandle = null;
8
- this._args = null;
9
11
  this._leading = defaultBooleanValue(options == null ? void 0 : options.leading, false);
12
+ this._trailing = defaultBooleanValue(options == null ? void 0 : options.trailing, true);
13
+ this._debounced = debounce(function () {
14
+ _this._isScheduled = false;
15
+ if (_this._storedArgs !== null) {
16
+ _this._callback.apply(_this, _this._storedArgs);
17
+ }
18
+ }, delayMS, {
19
+ leading: this._leading,
20
+ trailing: this._trailing
21
+ });
10
22
  }
11
23
  var _proto = Batchinator.prototype;
12
24
  _proto.dispose = function dispose(options) {
@@ -16,57 +28,49 @@ var Batchinator = /*#__PURE__*/function () {
16
28
  };
17
29
  }
18
30
  var _options = options,
19
- abort = _options.abort;
20
- if (this._taskHandle) {
21
- this._taskHandle.cancel();
22
- this._taskHandle = null;
23
- }
24
- if (typeof this._callback === 'function' && !abort) {
25
- this._callback.apply(this, this._args);
31
+ _options$abort = _options.abort,
32
+ abort = _options$abort === void 0 ? false : _options$abort;
33
+ if (abort) {
34
+ this._debounced.cancel();
35
+ this._isScheduled = false;
36
+ this._storedArgs = null;
37
+ } else {
38
+ if (this._storedArgs !== null) {
39
+ this._debounced.flush();
40
+ }
26
41
  }
27
42
  };
28
43
  _proto.inSchedule = function inSchedule() {
29
- return !!this._taskHandle;
44
+ return this._isScheduled;
30
45
  };
31
46
  _proto.flush = function flush() {
32
47
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
33
48
  args[_key] = arguments[_key];
34
49
  }
35
- if (args.length) this._args = args;
36
- if (this._taskHandle) {
37
- this._taskHandle.cancel();
38
- this._taskHandle = null;
50
+ if (args.length > 0) {
51
+ this._storedArgs = args;
39
52
  }
40
- this._callback.apply(this, this._args);
53
+ this._debounced.flush();
54
+ this._isScheduled = false;
41
55
  };
42
56
  _proto.schedule = function schedule() {
43
- var _this = this;
44
57
  for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
45
58
  args[_key2] = arguments[_key2];
46
59
  }
47
- this._args = args;
48
- if (this._taskHandle) return;
49
- var handler = this._leading ? function () {
50
- _this._taskHandle = null;
51
- } : function () {
52
- _this._taskHandle = null;
53
- _this._callback.apply(_this, _this._args);
54
- };
55
- if (!this._delayMS) {
56
- handler();
60
+ this._storedArgs = args;
61
+ if (this._isScheduled) {
57
62
  return;
58
63
  }
59
- if (this._leading) {
60
- this._callback.apply(this, this._args);
61
- }
62
- var timeoutHandle = setTimeout(function () {
63
- handler();
64
- }, this._delayMS);
65
- this._taskHandle = {
66
- cancel: function cancel() {
67
- return clearTimeout(timeoutHandle);
64
+ if (!this._delayMS) {
65
+ if (this._leading) {
66
+ this._callback.apply(this, this._storedArgs);
67
+ } else if (this._trailing) {
68
+ this._callback.apply(this, this._storedArgs);
68
69
  }
69
- };
70
+ return;
71
+ }
72
+ this._isScheduled = true;
73
+ this._debounced();
70
74
  };
71
75
  return Batchinator;
72
76
  }();
@@ -1 +1 @@
1
- {"version":3,"file":"batchinator.esm.js","sources":["../src/index.ts"],"sourcesContent":["import defaultBooleanValue from '@x-oasis/default-boolean-value';\n// import { InteractionManager } from 'react-native';\n\n// https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js\n\nclass Batchinator {\n readonly _delayMS: number;\n private _args: Array<any>;\n\n private _callback: Function;\n private _taskHandle: {\n cancel: () => void;\n };\n private _leading: boolean;\n // private _trailing: boolean\n\n constructor(\n cb: Function,\n delayMS: number,\n options?: {\n leading: boolean;\n trailing: boolean;\n }\n ) {\n this._callback = cb;\n this._delayMS = delayMS;\n this._taskHandle = null;\n this._args = null;\n this._leading = defaultBooleanValue(options?.leading, false);\n // this._trailing = defaultBooleanValue(options.trailing, true)\n }\n\n dispose(\n options: {\n abort: boolean;\n } = {\n abort: false,\n }\n ) {\n const { abort } = options;\n if (this._taskHandle) {\n this._taskHandle.cancel();\n this._taskHandle = null;\n }\n if (typeof this._callback === 'function' && !abort) {\n this._callback.apply(this, this._args);\n }\n }\n\n inSchedule() {\n return !!this._taskHandle;\n }\n\n flush(...args) {\n if (args.length) this._args = args;\n if (this._taskHandle) {\n this._taskHandle.cancel();\n this._taskHandle = null;\n }\n this._callback.apply(this, this._args);\n }\n\n schedule(...args) {\n this._args = args;\n\n if (this._taskHandle) return;\n const handler = this._leading\n ? () => {\n this._taskHandle = null;\n }\n : () => {\n this._taskHandle = null;\n this._callback.apply(this, this._args);\n };\n\n if (!this._delayMS) {\n handler();\n return;\n }\n\n if (this._leading) {\n this._callback.apply(this, this._args);\n }\n\n const timeoutHandle = setTimeout(() => {\n handler();\n }, this._delayMS);\n\n this._taskHandle = { cancel: () => clearTimeout(timeoutHandle) };\n }\n}\n\nexport default Batchinator;\n"],"names":["Batchinator","cb","delayMS","options","_callback","_delayMS","_taskHandle","_args","_leading","defaultBooleanValue","leading","_proto","prototype","dispose","abort","_options","cancel","apply","inSchedule","flush","args","Array","_len","_key","arguments","length","schedule","_len2","_key2","handler","_this","timeoutHandle","setTimeout","clearTimeout"],"mappings":";;AAAiE,IAK3DA,WAAW;EAWf,SAAAA,YACEC,EAAY,EACZC,OAAe,EACfC,OAGC;IAED,IAAI,CAACC,SAAS,GAAGH,EAAE;IACnB,IAAI,CAACI,QAAQ,GAAGH,OAAO;IACvB,IAAI,CAACI,WAAW,GAAG,IAAI;IACvB,IAAI,CAACC,KAAK,GAAG,IAAI;IACjB,IAAI,CAACC,QAAQ,GAAGC,mBAAmB,CAACN,OAAO,oBAAPA,OAAO,CAAEO,OAAO,EAAE,KAAK,CAAC;;EAE7D,IAAAC,MAAA,GAAAX,WAAA,CAAAY,SAAA;EAAAD,MAAA,CAEDE,OAAO,GAAP,SAAAA,QACEV;QAAAA;MAAAA,UAEI;QACFW,KAAK,EAAE;OACR;;IAED,IAAAC,QAAA,GAAkBZ,OAAO;MAAjBW,KAAK,GAAAC,QAAA,CAALD,KAAK;IACb,IAAI,IAAI,CAACR,WAAW,EAAE;MACpB,IAAI,CAACA,WAAW,CAACU,MAAM,EAAE;MACzB,IAAI,CAACV,WAAW,GAAG,IAAI;;IAEzB,IAAI,OAAO,IAAI,CAACF,SAAS,KAAK,UAAU,IAAI,CAACU,KAAK,EAAE;MAClD,IAAI,CAACV,SAAS,CAACa,KAAK,CAAC,IAAI,EAAE,IAAI,CAACV,KAAK,CAAC;;GAEzC;EAAAI,MAAA,CAEDO,UAAU,GAAV,SAAAA;IACE,OAAO,CAAC,CAAC,IAAI,CAACZ,WAAW;GAC1B;EAAAK,MAAA,CAEDQ,KAAK,GAAL,SAAAA;sCAASC,IAAI,OAAAC,KAAA,CAAAC,IAAA,GAAAC,IAAA,MAAAA,IAAA,GAAAD,IAAA,EAAAC,IAAA;MAAJH,IAAI,CAAAG,IAAA,IAAAC,SAAA,CAAAD,IAAA;;IACX,IAAIH,IAAI,CAACK,MAAM,EAAE,IAAI,CAAClB,KAAK,GAAGa,IAAI;IAClC,IAAI,IAAI,CAACd,WAAW,EAAE;MACpB,IAAI,CAACA,WAAW,CAACU,MAAM,EAAE;MACzB,IAAI,CAACV,WAAW,GAAG,IAAI;;IAEzB,IAAI,CAACF,SAAS,CAACa,KAAK,CAAC,IAAI,EAAE,IAAI,CAACV,KAAK,CAAC;GACvC;EAAAI,MAAA,CAEDe,QAAQ,GAAR,SAAAA;;uCAAYN,IAAI,OAAAC,KAAA,CAAAM,KAAA,GAAAC,KAAA,MAAAA,KAAA,GAAAD,KAAA,EAAAC,KAAA;MAAJR,IAAI,CAAAQ,KAAA,IAAAJ,SAAA,CAAAI,KAAA;;IACd,IAAI,CAACrB,KAAK,GAAGa,IAAI;IAEjB,IAAI,IAAI,CAACd,WAAW,EAAE;IACtB,IAAMuB,OAAO,GAAG,IAAI,CAACrB,QAAQ,GACzB;MACEsB,KAAI,CAACxB,WAAW,GAAG,IAAI;KACxB,GACD;MACEwB,KAAI,CAACxB,WAAW,GAAG,IAAI;MACvBwB,KAAI,CAAC1B,SAAS,CAACa,KAAK,CAACa,KAAI,EAAEA,KAAI,CAACvB,KAAK,CAAC;KACvC;IAEL,IAAI,CAAC,IAAI,CAACF,QAAQ,EAAE;MAClBwB,OAAO,EAAE;MACT;;IAGF,IAAI,IAAI,CAACrB,QAAQ,EAAE;MACjB,IAAI,CAACJ,SAAS,CAACa,KAAK,CAAC,IAAI,EAAE,IAAI,CAACV,KAAK,CAAC;;IAGxC,IAAMwB,aAAa,GAAGC,UAAU,CAAC;MAC/BH,OAAO,EAAE;KACV,EAAE,IAAI,CAACxB,QAAQ,CAAC;IAEjB,IAAI,CAACC,WAAW,GAAG;MAAEU,MAAM,EAAE,SAAAA;QAAA,OAAMiB,YAAY,CAACF,aAAa,CAAC;;KAAE;GACjE;EAAA,OAAA/B,WAAA;AAAA;;;;"}
1
+ {"version":3,"file":"batchinator.esm.js","sources":["../src/index.ts"],"sourcesContent":["import debounce from '@x-oasis/debounce';\nimport defaultBooleanValue from '@x-oasis/default-boolean-value';\n\n// https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js\n\ntype BatchinatorOptions = {\n leading?: boolean;\n trailing?: boolean;\n};\n\n/**\n * Batchinator - Batches callback executions with configurable leading/trailing behavior.\n * Similar to debounce/throttle but with more control over execution timing.\n */\nclass Batchinator {\n readonly _delayMS: number;\n private _callback: (...args: any[]) => void;\n private _debounced: ReturnType<typeof debounce>;\n private _leading: boolean;\n private _trailing: boolean;\n private _isScheduled = false;\n private _storedArgs: any[] | null = null;\n\n constructor(\n cb: (...args: any[]) => void,\n delayMS: number,\n options?: BatchinatorOptions\n ) {\n this._callback = cb;\n this._delayMS = delayMS;\n this._leading = defaultBooleanValue(options?.leading, false);\n this._trailing = defaultBooleanValue(options?.trailing, true);\n\n // Create a debounced function that wraps our callback\n // The key difference from debounce: if already scheduled, schedule() only updates args\n // We handle this by tracking _isScheduled and only calling debounce on first schedule\n this._debounced = debounce(\n () => {\n this._isScheduled = false;\n if (this._storedArgs !== null) {\n this._callback(...this._storedArgs);\n }\n },\n delayMS,\n {\n leading: this._leading,\n trailing: this._trailing,\n }\n );\n }\n\n /**\n * Dispose the scheduled task\n * @param options - Configuration options\n * @param options.abort - If true, cancel without executing callback\n */\n dispose(\n options: {\n abort?: boolean;\n } = {\n abort: false,\n }\n ): void {\n const { abort = false } = options;\n if (abort) {\n this._debounced.cancel();\n this._isScheduled = false;\n this._storedArgs = null;\n } else {\n // Execute with current args if any\n if (this._storedArgs !== null) {\n this._debounced.flush();\n }\n }\n }\n\n /**\n * Check if a task is currently scheduled\n */\n inSchedule(): boolean {\n return this._isScheduled;\n }\n\n /**\n * Flush the scheduled task immediately\n * @param args - Optional arguments to use instead of stored args\n */\n flush(...args: any[]): void {\n if (args.length > 0) {\n this._storedArgs = args;\n }\n this._debounced.flush();\n this._isScheduled = false;\n }\n\n /**\n * Schedule the callback execution\n * @param args - Arguments to pass to the callback\n */\n schedule(...args: any[]): void {\n this._storedArgs = args;\n\n // If already scheduled, just update args and return (don't reset timer)\n // This is the key difference from debounce\n if (this._isScheduled) {\n return;\n }\n\n // Handle zero delay case - execute immediately based on leading/trailing\n if (!this._delayMS) {\n if (this._leading) {\n this._callback(...this._storedArgs);\n } else if (this._trailing) {\n // For zero delay with trailing, execute immediately\n this._callback(...this._storedArgs);\n }\n return;\n }\n\n // First call - mark as scheduled and call debounce\n this._isScheduled = true;\n this._debounced();\n }\n}\n\nexport default Batchinator;\n"],"names":["Batchinator","cb","delayMS","options","_callback","_delayMS","_leading","defaultBooleanValue","leading","_trailing","trailing","_debounced","debounce","_this","_isScheduled","_storedArgs","apply","_proto","prototype","dispose","abort","_options","_options$abort","cancel","flush","inSchedule","args","Array","_len","_key","arguments","length","schedule","_len2","_key2"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,15 +1,18 @@
1
+ declare type BatchinatorOptions = {
2
+ leading?: boolean;
3
+ trailing?: boolean;
4
+ };
1
5
  declare class Batchinator {
2
6
  readonly _delayMS: number;
3
- private _args;
4
7
  private _callback;
5
- private _taskHandle;
8
+ private _debounced;
6
9
  private _leading;
7
- constructor(cb: Function, delayMS: number, options?: {
8
- leading: boolean;
9
- trailing: boolean;
10
- });
10
+ private _trailing;
11
+ private _isScheduled;
12
+ private _storedArgs;
13
+ constructor(cb: (...args: any[]) => void, delayMS: number, options?: BatchinatorOptions);
11
14
  dispose(options?: {
12
- abort: boolean;
15
+ abort?: boolean;
13
16
  }): void;
14
17
  inSchedule(): boolean;
15
18
  flush(...args: any[]): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-oasis/batchinator",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "batchinator function",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -14,7 +14,8 @@
14
14
  "tsdx": "^0.14.1"
15
15
  },
16
16
  "dependencies": {
17
- "@x-oasis/default-boolean-value": "0.1.37"
17
+ "@x-oasis/debounce": "0.1.38",
18
+ "@x-oasis/default-boolean-value": "0.1.38"
18
19
  },
19
20
  "scripts": {
20
21
  "build": "tsdx build --tsconfig tsconfig.build.json",
package/src/index.ts CHANGED
@@ -1,92 +1,125 @@
1
+ import debounce from '@x-oasis/debounce';
1
2
  import defaultBooleanValue from '@x-oasis/default-boolean-value';
2
- // import { InteractionManager } from 'react-native';
3
3
 
4
4
  // https://github.com/facebook/react-native/blob/main/Libraries/Interaction/Batchinator.js
5
5
 
6
+ type BatchinatorOptions = {
7
+ leading?: boolean;
8
+ trailing?: boolean;
9
+ };
10
+
11
+ /**
12
+ * Batchinator - Batches callback executions with configurable leading/trailing behavior.
13
+ * Similar to debounce/throttle but with more control over execution timing.
14
+ */
6
15
  class Batchinator {
7
16
  readonly _delayMS: number;
8
- private _args: Array<any>;
9
-
10
- private _callback: Function;
11
- private _taskHandle: {
12
- cancel: () => void;
13
- };
17
+ private _callback: (...args: any[]) => void;
18
+ private _debounced: ReturnType<typeof debounce>;
14
19
  private _leading: boolean;
15
- // private _trailing: boolean
20
+ private _trailing: boolean;
21
+ private _isScheduled = false;
22
+ private _storedArgs: any[] | null = null;
16
23
 
17
24
  constructor(
18
- cb: Function,
25
+ cb: (...args: any[]) => void,
19
26
  delayMS: number,
20
- options?: {
21
- leading: boolean;
22
- trailing: boolean;
23
- }
27
+ options?: BatchinatorOptions
24
28
  ) {
25
29
  this._callback = cb;
26
30
  this._delayMS = delayMS;
27
- this._taskHandle = null;
28
- this._args = null;
29
31
  this._leading = defaultBooleanValue(options?.leading, false);
30
- // this._trailing = defaultBooleanValue(options.trailing, true)
32
+ this._trailing = defaultBooleanValue(options?.trailing, true);
33
+
34
+ // Create a debounced function that wraps our callback
35
+ // The key difference from debounce: if already scheduled, schedule() only updates args
36
+ // We handle this by tracking _isScheduled and only calling debounce on first schedule
37
+ this._debounced = debounce(
38
+ () => {
39
+ this._isScheduled = false;
40
+ if (this._storedArgs !== null) {
41
+ this._callback(...this._storedArgs);
42
+ }
43
+ },
44
+ delayMS,
45
+ {
46
+ leading: this._leading,
47
+ trailing: this._trailing,
48
+ }
49
+ );
31
50
  }
32
51
 
52
+ /**
53
+ * Dispose the scheduled task
54
+ * @param options - Configuration options
55
+ * @param options.abort - If true, cancel without executing callback
56
+ */
33
57
  dispose(
34
58
  options: {
35
- abort: boolean;
59
+ abort?: boolean;
36
60
  } = {
37
61
  abort: false,
38
62
  }
39
- ) {
40
- const { abort } = options;
41
- if (this._taskHandle) {
42
- this._taskHandle.cancel();
43
- this._taskHandle = null;
44
- }
45
- if (typeof this._callback === 'function' && !abort) {
46
- this._callback.apply(this, this._args);
63
+ ): void {
64
+ const { abort = false } = options;
65
+ if (abort) {
66
+ this._debounced.cancel();
67
+ this._isScheduled = false;
68
+ this._storedArgs = null;
69
+ } else {
70
+ // Execute with current args if any
71
+ if (this._storedArgs !== null) {
72
+ this._debounced.flush();
73
+ }
47
74
  }
48
75
  }
49
76
 
50
- inSchedule() {
51
- return !!this._taskHandle;
77
+ /**
78
+ * Check if a task is currently scheduled
79
+ */
80
+ inSchedule(): boolean {
81
+ return this._isScheduled;
52
82
  }
53
83
 
54
- flush(...args) {
55
- if (args.length) this._args = args;
56
- if (this._taskHandle) {
57
- this._taskHandle.cancel();
58
- this._taskHandle = null;
84
+ /**
85
+ * Flush the scheduled task immediately
86
+ * @param args - Optional arguments to use instead of stored args
87
+ */
88
+ flush(...args: any[]): void {
89
+ if (args.length > 0) {
90
+ this._storedArgs = args;
59
91
  }
60
- this._callback.apply(this, this._args);
92
+ this._debounced.flush();
93
+ this._isScheduled = false;
61
94
  }
62
95
 
63
- schedule(...args) {
64
- this._args = args;
96
+ /**
97
+ * Schedule the callback execution
98
+ * @param args - Arguments to pass to the callback
99
+ */
100
+ schedule(...args: any[]): void {
101
+ this._storedArgs = args;
65
102
 
66
- if (this._taskHandle) return;
67
- const handler = this._leading
68
- ? () => {
69
- this._taskHandle = null;
70
- }
71
- : () => {
72
- this._taskHandle = null;
73
- this._callback.apply(this, this._args);
74
- };
75
-
76
- if (!this._delayMS) {
77
- handler();
103
+ // If already scheduled, just update args and return (don't reset timer)
104
+ // This is the key difference from debounce
105
+ if (this._isScheduled) {
78
106
  return;
79
107
  }
80
108
 
81
- if (this._leading) {
82
- this._callback.apply(this, this._args);
109
+ // Handle zero delay case - execute immediately based on leading/trailing
110
+ if (!this._delayMS) {
111
+ if (this._leading) {
112
+ this._callback(...this._storedArgs);
113
+ } else if (this._trailing) {
114
+ // For zero delay with trailing, execute immediately
115
+ this._callback(...this._storedArgs);
116
+ }
117
+ return;
83
118
  }
84
119
 
85
- const timeoutHandle = setTimeout(() => {
86
- handler();
87
- }, this._delayMS);
88
-
89
- this._taskHandle = { cancel: () => clearTimeout(timeoutHandle) };
120
+ // First call - mark as scheduled and call debounce
121
+ this._isScheduled = true;
122
+ this._debounced();
90
123
  }
91
124
  }
92
125
 
package/test/test.spec.ts CHANGED
@@ -1,5 +1,262 @@
1
- import { expect, test } from 'vitest'
1
+ import { expect, test, vi, beforeEach, afterEach } from 'vitest';
2
+ import Batchinator from '../src/index';
2
3
 
3
- test('vitest', async () => {
4
- expect('vitest').toBe('vitest')
5
- })
4
+ beforeEach(() => {
5
+ vi.useFakeTimers();
6
+ });
7
+
8
+ afterEach(() => {
9
+ vi.restoreAllMocks();
10
+ });
11
+
12
+ test('Batchinator basic functionality - batches calls', () => {
13
+ const fn = vi.fn();
14
+ const batchinator = new Batchinator(fn, 100);
15
+
16
+ batchinator.schedule('First');
17
+ batchinator.schedule('Second');
18
+ batchinator.schedule('Third');
19
+
20
+ expect(fn).not.toHaveBeenCalled();
21
+
22
+ vi.advanceTimersByTime(100);
23
+
24
+ expect(fn).toHaveBeenCalledTimes(1);
25
+ expect(fn).toHaveBeenCalledWith('Third');
26
+ });
27
+
28
+ test('Batchinator with leading: true', () => {
29
+ const fn = vi.fn();
30
+ const batchinator = new Batchinator(fn, 100, { leading: true });
31
+
32
+ batchinator.schedule('First');
33
+ expect(fn).toHaveBeenCalledTimes(1);
34
+ expect(fn).toHaveBeenCalledWith('First');
35
+
36
+ batchinator.schedule('Second');
37
+ expect(fn).toHaveBeenCalledTimes(1);
38
+
39
+ vi.advanceTimersByTime(100);
40
+ expect(fn).toHaveBeenCalledTimes(2);
41
+ expect(fn).toHaveBeenCalledWith('Second');
42
+ });
43
+
44
+ test('Batchinator with trailing: false', () => {
45
+ const fn = vi.fn();
46
+ const batchinator = new Batchinator(fn, 100, { trailing: false });
47
+
48
+ batchinator.schedule('First');
49
+ batchinator.schedule('Second');
50
+ batchinator.schedule('Third');
51
+
52
+ expect(fn).not.toHaveBeenCalled();
53
+
54
+ vi.advanceTimersByTime(100);
55
+ expect(fn).not.toHaveBeenCalled();
56
+ });
57
+
58
+ test('Batchinator with leading: true and trailing: false', () => {
59
+ const fn = vi.fn();
60
+ const batchinator = new Batchinator(fn, 100, {
61
+ leading: true,
62
+ trailing: false,
63
+ });
64
+
65
+ batchinator.schedule('First');
66
+ expect(fn).toHaveBeenCalledTimes(1);
67
+ expect(fn).toHaveBeenCalledWith('First');
68
+
69
+ batchinator.schedule('Second');
70
+ batchinator.schedule('Third');
71
+ expect(fn).toHaveBeenCalledTimes(1);
72
+
73
+ vi.advanceTimersByTime(100);
74
+ expect(fn).toHaveBeenCalledTimes(1); // No trailing execution
75
+ });
76
+
77
+ test('Batchinator with leading: false and trailing: true (default)', () => {
78
+ const fn = vi.fn();
79
+ const batchinator = new Batchinator(fn, 100, {
80
+ leading: false,
81
+ trailing: true,
82
+ });
83
+
84
+ batchinator.schedule('First');
85
+ expect(fn).not.toHaveBeenCalled();
86
+
87
+ vi.advanceTimersByTime(100);
88
+ expect(fn).toHaveBeenCalledTimes(1);
89
+ expect(fn).toHaveBeenCalledWith('First');
90
+ });
91
+
92
+ test('Batchinator flush', () => {
93
+ const fn = vi.fn();
94
+ const batchinator = new Batchinator(fn, 100);
95
+
96
+ batchinator.schedule('First');
97
+ batchinator.schedule('Second');
98
+ batchinator.flush();
99
+
100
+ expect(fn).toHaveBeenCalledTimes(1);
101
+ expect(fn).toHaveBeenCalledWith('Second');
102
+
103
+ vi.advanceTimersByTime(100);
104
+ expect(fn).toHaveBeenCalledTimes(1); // Already flushed
105
+ });
106
+
107
+ test('Batchinator flush with new arguments', () => {
108
+ const fn = vi.fn();
109
+ const batchinator = new Batchinator(fn, 100);
110
+
111
+ batchinator.schedule('First');
112
+ batchinator.flush('Custom');
113
+
114
+ expect(fn).toHaveBeenCalledTimes(1);
115
+ expect(fn).toHaveBeenCalledWith('Custom');
116
+ });
117
+
118
+ test('Batchinator dispose without abort', () => {
119
+ const fn = vi.fn();
120
+ const batchinator = new Batchinator(fn, 100);
121
+
122
+ batchinator.schedule('First');
123
+ batchinator.schedule('Second');
124
+ batchinator.dispose();
125
+
126
+ expect(fn).toHaveBeenCalledTimes(1);
127
+ expect(fn).toHaveBeenCalledWith('Second');
128
+
129
+ vi.advanceTimersByTime(100);
130
+ expect(fn).toHaveBeenCalledTimes(1); // Already disposed
131
+ });
132
+
133
+ test('Batchinator dispose with abort', () => {
134
+ const fn = vi.fn();
135
+ const batchinator = new Batchinator(fn, 100);
136
+
137
+ batchinator.schedule('First');
138
+ batchinator.schedule('Second');
139
+ batchinator.dispose({ abort: true });
140
+
141
+ expect(fn).not.toHaveBeenCalled();
142
+
143
+ vi.advanceTimersByTime(100);
144
+ expect(fn).not.toHaveBeenCalled();
145
+ });
146
+
147
+ test('Batchinator inSchedule', () => {
148
+ const fn = vi.fn();
149
+ const batchinator = new Batchinator(fn, 100);
150
+
151
+ expect(batchinator.inSchedule()).toBe(false);
152
+
153
+ batchinator.schedule('First');
154
+ expect(batchinator.inSchedule()).toBe(true);
155
+
156
+ batchinator.flush();
157
+ expect(batchinator.inSchedule()).toBe(false);
158
+ });
159
+
160
+ test('Batchinator inSchedule after dispose', () => {
161
+ const fn = vi.fn();
162
+ const batchinator = new Batchinator(fn, 100);
163
+
164
+ batchinator.schedule('First');
165
+ expect(batchinator.inSchedule()).toBe(true);
166
+
167
+ batchinator.dispose({ abort: true });
168
+ expect(batchinator.inSchedule()).toBe(false);
169
+ });
170
+
171
+ test('Batchinator with zero delay', () => {
172
+ const fn = vi.fn();
173
+ const batchinator = new Batchinator(fn, 0, { leading: true });
174
+
175
+ batchinator.schedule('First');
176
+ expect(fn).toHaveBeenCalledTimes(1);
177
+ expect(fn).toHaveBeenCalledWith('First');
178
+ });
179
+
180
+ test('Batchinator with zero delay and trailing: true', () => {
181
+ const fn = vi.fn();
182
+ const batchinator = new Batchinator(fn, 0, {
183
+ leading: false,
184
+ trailing: true,
185
+ });
186
+
187
+ batchinator.schedule('First');
188
+ expect(fn).toHaveBeenCalledTimes(1);
189
+ expect(fn).toHaveBeenCalledWith('First');
190
+ });
191
+
192
+ test('Batchinator preserves this context', () => {
193
+ const obj = {
194
+ value: 42,
195
+ fn: function (this: any, arg: number) {
196
+ return this.value + arg;
197
+ },
198
+ };
199
+
200
+ const batchinator = new Batchinator(obj.fn, 100);
201
+ batchinator.schedule(10);
202
+
203
+ vi.advanceTimersByTime(100);
204
+ // Function executes with correct context
205
+ expect(obj.value).toBe(42);
206
+ });
207
+
208
+ test('Batchinator multiple rapid calls', () => {
209
+ const fn = vi.fn();
210
+ const batchinator = new Batchinator(fn, 100);
211
+
212
+ for (let i = 0; i < 10; i++) {
213
+ batchinator.schedule(i);
214
+ }
215
+
216
+ expect(fn).not.toHaveBeenCalled();
217
+
218
+ vi.advanceTimersByTime(100);
219
+ expect(fn).toHaveBeenCalledTimes(1);
220
+ expect(fn).toHaveBeenCalledWith(9); // Last call
221
+ });
222
+
223
+ test('Batchinator with multiple arguments', () => {
224
+ const fn = vi.fn();
225
+ const batchinator = new Batchinator(fn, 100);
226
+
227
+ batchinator.schedule(1, 'a', true);
228
+ batchinator.schedule(2, 'b', false);
229
+ batchinator.schedule(3, 'c', true);
230
+
231
+ vi.advanceTimersByTime(100);
232
+ expect(fn).toHaveBeenCalledTimes(1);
233
+ expect(fn).toHaveBeenCalledWith(3, 'c', true);
234
+ });
235
+
236
+ test('Batchinator schedule after flush', () => {
237
+ const fn = vi.fn();
238
+ const batchinator = new Batchinator(fn, 100);
239
+
240
+ batchinator.schedule('First');
241
+ batchinator.flush();
242
+ expect(fn).toHaveBeenCalledTimes(1);
243
+
244
+ batchinator.schedule('Second');
245
+ vi.advanceTimersByTime(100);
246
+ expect(fn).toHaveBeenCalledTimes(2);
247
+ expect(fn).toHaveBeenCalledWith('Second');
248
+ });
249
+
250
+ test('Batchinator schedule after dispose', () => {
251
+ const fn = vi.fn();
252
+ const batchinator = new Batchinator(fn, 100);
253
+
254
+ batchinator.schedule('First');
255
+ batchinator.dispose({ abort: true });
256
+ expect(fn).not.toHaveBeenCalled();
257
+
258
+ batchinator.schedule('Second');
259
+ vi.advanceTimersByTime(100);
260
+ expect(fn).toHaveBeenCalledTimes(1);
261
+ expect(fn).toHaveBeenCalledWith('Second');
262
+ });