defered-branch 1.2.2 β†’ 2.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/README.md CHANGED
@@ -1,111 +1,280 @@
1
- # defered-branch 🚦
2
-
3
- A tiny TypeScript/JavaScript utility for deferred branching logic.
4
- It provides a small, chainable API to register conditional branches and two kinds of "no-match" handlers: one that runs immediately when no branch was matched, and another that runs later when you call `run()`.
1
+ # Defered Branch 🌿
5
2
 
6
3
  For more awesome packages, check out [my homepageπŸ’›](https://baendlorel.github.io/?repoType=npm)
7
4
 
8
- ## Highlights ✨
5
+ > Sometimes we need to judge something with multiple cases, but the main job should be done later. So we have Defered Branch! ✨
6
+
7
+ A TypeScript/JavaScript library that provides elegant deferred conditional branching with two powerful variants: **defered-branch** for immediate execution and **defered-branch-dynamic** for reusable logic patterns.
8
+
9
+ ## πŸš€ Features
9
10
 
10
- - Chainable API for registering branches
11
- - Only the last matched branch is executed
12
- - Immediate `nomatch` handler (runs instantly when no branch matched)
13
- - Deferred `deferedNomatch` handler (runs when `run()` is invoked)
14
- - TypeScript declarations included
11
+ - 🎯 **Two Execution Models**: Choose between immediate execution or dynamic reusable logic
12
+ - πŸ”’ **Type Safe**: Full TypeScript support with proper type inference
13
+ - πŸͺΆ **Lightweight**: Zero dependencies, minimal footprint
14
+ - 🧩 **Flexible**: Works with any function signature and return types
15
+ - πŸ“¦ **Type Annotation**: Full type annotation supported for the exported functions. You can customize handler's type of branch/condition/nomatch.
15
16
 
16
- ## Installation πŸ“¦
17
+ ## πŸ“¦ Installation
17
18
 
18
19
  ```bash
19
- pnpm add defered-branch
20
- # or
21
20
  npm install defered-branch
21
+ # or
22
+ pnpm add defered-branch
22
23
  ```
23
24
 
24
- ## Export / Import πŸš€
25
+ ## 🌟 When to Use Which?
26
+
27
+ ### `deferedBranch` - One-time Immediate Execution πŸƒβ€β™‚οΈ
28
+
29
+ Perfect for scenarios where you need to evaluate conditions **once** and execute immediately:
30
+
31
+ - One-time conditional flows
32
+ - Static condition evaluation
25
33
 
26
- The package exports a factory named `deferedBranch` (note the spelling):
34
+ ### `deferedBranchDynamic` - Reusable Logic Patterns πŸ”„
27
35
 
28
- ```ts
36
+ Ideal when you need to **reuse** the same branching logic multiple times with different inputs:
37
+
38
+ - Event handlers with varying states
39
+ - Repeated conditional logic with dynamic data
40
+ - State-dependent operations that change over time
41
+
42
+ ## πŸ“š Usage Examples
43
+
44
+ ### Basic deferedBranch Usage
45
+
46
+ ```typescript
29
47
  import { deferedBranch } from 'defered-branch';
48
+
49
+ // Steps to use:
50
+ // 1. Create instance
51
+ const branch = deferedBranch();
52
+
53
+ // 2. Add conditional branches
54
+ branch
55
+ .add(user.role === 'admin', () => 'Access granted to admin panel')
56
+ .add(user.role === 'user', () => 'Access granted to user dashboard')
57
+ .add(user.role === 'guest', () => 'Limited access granted');
58
+
59
+ // 3. Handle no-match case (optional)
60
+ branch.nomatch(() => 'Access denied');
61
+
62
+ // some other logic... like validate other arguments
63
+
64
+ // 4. Execute and get result
65
+ const result = branch.run();
66
+ console.log(result); // Output based on user.role
30
67
  ```
31
68
 
32
- ## Basic Usage
69
+ ### Advanced deferedBranch with Parameters
33
70
 
34
- ```ts
35
- import { deferedBranch } from 'defered-branch';
71
+ ```typescript
72
+ const calculator = deferedBranch<(a: number, b: number) => number>();
36
73
 
37
- // register branches - only the last matched branch will run
38
- const b = deferedBranch()
39
- .add(false, () => 'A')
40
- .add(true, () => 'B');
74
+ calculator
75
+ .add(operation === 'add', (a, b) => a + b)
76
+ .add(operation === 'multiply', (a, b) => a * b)
77
+ .add(operation === 'divide', (a, b) => a / b)
78
+ .nomatch(() => {
79
+ throw new Error('Unknown operation');
80
+ });
41
81
 
42
- console.log(b.run()); // 'B'
82
+ const result = calculator.run(10, 5); // Returns calculated result
43
83
  ```
44
84
 
45
- ## nomatch vs deferedNomatch
85
+ ### deferedBranchDynamic for Reusable Logic
86
+
87
+ ```typescript
88
+ import { deferedBranchDynamic } from 'defered-branch';
89
+
90
+ // Steps to use:
91
+ // 1. Create instance
92
+ const statusHandler = deferedBranchDynamic<(message: string) => void>();
93
+
94
+ // 2. Add predicate-based branches
95
+ statusHandler
96
+ .add(
97
+ () => server.status === 'online',
98
+ (msg) => logger.info(`βœ… ${msg}`)
99
+ )
100
+ .add(
101
+ () => server.status === 'maintenance',
102
+ (msg) => logger.warn(`⚠️ ${msg}`)
103
+ )
104
+ .add(
105
+ () => server.status === 'offline',
106
+ (msg) => logger.error(`❌ ${msg}`)
107
+ );
108
+
109
+ // 3. Add fallback handler
110
+ statusHandler.nomatch((msg) => logger.debug(`πŸ€” Unknown status: ${msg}`));
111
+
112
+ // 4. Use repeatedly with different conditions
113
+ statusHandler.predicate(); // Evaluates current server.status
114
+ statusHandler.run('Server health check completed');
115
+
116
+ // Later, when server.status changes...
117
+ statusHandler.predicate(); // Re-evaluates with new status
118
+ statusHandler.run('Status updated');
119
+ ```
46
120
 
47
- There are two ways to handle the case when no branch matches:
121
+ ### Real-world Example: Theme Switching
122
+
123
+ ```typescript
124
+ const themeHandler = deferedBranchDynamic<() => void>();
125
+
126
+ themeHandler
127
+ .add(
128
+ () => preferences.theme === 'dark',
129
+ () => applyDarkTheme()
130
+ )
131
+ .add(
132
+ () => preferences.theme === 'light',
133
+ () => applyLightTheme()
134
+ )
135
+ .add(
136
+ () => preferences.theme === 'auto',
137
+ () => applySystemTheme()
138
+ )
139
+ .nomatch(() => {
140
+ // usually handles parameter validations
141
+ throw new TypeError('Invalid theme preference');
142
+ });
143
+
144
+ // Reuse whenever preferences change
145
+ function onConfigChange() {
146
+ themeHandler.predicate();
147
+ xxxHandler.predicate();
148
+ yyyHandler.predicate();
149
+
150
+ // some other logic...create some objects..
151
+ // some other logic...run some funtions...
152
+
153
+ themeHandler.run();
154
+ xxxHandler.run();
155
+ yyyHandler.run();
156
+ }
157
+ ```
48
158
 
49
- - `nomatch(handler: Fn)` β€” runs the handler immediately if no branch has been matched yet. Note: if `nomatch` runs the handler immediately, later calls to `add(true, ...)` will not retroactively affect that call.
50
- - `deferedNomatch(handler: Fn)` β€” registers a handler that will be executed when `run()` is called and no branch matched.
159
+ ## πŸ”§ API Reference
51
160
 
52
- Examples:
161
+ ### deferedBranch()
53
162
 
54
- ```ts
55
- import { deferedBranch } from 'defered-branch';
163
+ Creates a new deferred branch instance for immediate execution scenarios.
56
164
 
57
- // nomatch runs immediately when invoked if no branch matched
58
- deferedBranch()
59
- .add(false, () => 'x')
60
- .nomatch(() => console.log('immediate nomatch')) // prints now
61
- .add(true, () => console.log('will not be printed')); // ignored for the already-called nomatch
165
+ #### Methods
62
166
 
63
- // deferedNomatch runs when run() is called
64
- const b2 = deferedBranch()
65
- .add(false, () => 'x')
66
- .deferedNomatch(() => 'deferred fallback');
167
+ ##### `.add(condition: boolean, branch: Function): DeferBranch`
67
168
 
68
- console.log(b2.run()); // 'deferred fallback'
69
- ```
169
+ - Adds a conditional branch that executes if condition is `true`
170
+ - First-match wins: the first truthy branch will be selected; later entries are ignored
171
+ - Returns the instance for chaining
172
+
173
+ ##### `.nomatch(handler: Function): DeferBranch`
70
174
 
71
- ## API Reference πŸ“š
175
+ - Sets an immediate fallback handler
176
+ - **Executes instantly** if no previous branch matched
177
+ - **Ignores** later matched branches
72
178
 
73
- All functions accept a `Fn` type: `type Fn = (...args: unknown[]) => unknown`.
179
+ ##### `.run(...args): any`
74
180
 
75
- ### `deferedBranch(): DeferBranch`
181
+ - Executes the matched branch or fallback handler
182
+ - Passes arguments to the executed function
183
+ - Returns the function's return value or `undefined`
76
184
 
77
- Create a new `DeferBranch` instance.
185
+ ### deferedBranchDynamic()
78
186
 
79
- ### `add(condition: boolean, branch: Fn): DeferBranch`
187
+ Creates a new dynamic deferred branch instance for reusable logic patterns.
80
188
 
81
- Register a branch. If `condition` is true the provided `branch` becomes the current matched branch. Later `add(true, ...)` calls will override the matched branch.
189
+ #### Methods
82
190
 
83
- Throws `TypeError` if `branch` is not a function.
191
+ ##### `.add(condition: Predicate, branch: Function): DeferBranchDynamic`
84
192
 
85
- ### `nomatch(handler: Fn): DeferBranch`
193
+ - Adds a predicate-based conditional branch
194
+ - `condition` is a function that returns boolean
195
+ - First-match wins: the first truthy branch will be selected; later entries are ignored
86
196
 
87
- If no branch has been matched when this is called, the `handler` is invoked immediately. This is useful for side-effects that should run right away when a branch is absent. Returns the instance for chaining. Throws `TypeError` when `handler` is not a function.
197
+ ##### `.nomatch(handler: Function): DeferBranchDynamic`
88
198
 
89
- ### `deferedNomatch(handler: Fn): DeferBranch`
199
+ - Sets a fallback handler for when no predicates match
200
+ - Executes during `.predicate()` evaluation
201
+
202
+ ## 🧾 Type Annotation
203
+
204
+ The factory functions `deferedBranch` and `deferedBranchDynamic` are generic helpers. You can provide explicit type parameters to describe the branch function signature and the optional `nomatch` handler.
205
+
206
+ Below is an example using `deferedBranch`:
207
+
208
+ ```typescript
209
+ import { deferedBranch } from 'defered-branch';
210
+
211
+ // Here we declare that branches accept a message (string) and return void.
212
+ // The factory signature is: deferedBranch<BranchFn, NoMatchFn>()
213
+ const handler = deferedBranch<(msg: string) => void, () => void>();
214
+
215
+ handler
216
+ .add(
217
+ process.env.STATUS === 'ok',
218
+ (msg) => console.log('OK:', msg) // this is restricted by `(msg: string) => void`
219
+ )
220
+ .add(
221
+ process.env.STATUS === 'warn',
222
+ (msg) => console.warn('WARN:', msg) // this is restricted by `(msg: string) => void`
223
+ )
224
+ .nomatch(
225
+ () => console.debug('unknown') // this is restricted by `() => void`
226
+ );
227
+
228
+ handler.run('hello world'); // input args for `run` are restricted by `(msg: string) => void`
229
+ ```
230
+
231
+ Notes:
232
+
233
+ - If you omit generic parameters TypeScript will fall back to `AnyFn` and inference is weaker.
234
+ - `BranchFn` defines the parameter types that `.run(...)` accepts and what each branch receives.
235
+ - `NoMatchFn` defines the signature of the `nomatch`/`deferedNomatch` handler.
236
+
237
+ Example: calculator with typed branches
238
+
239
+ ```typescript
240
+ const calc = deferedBranchDynamic<(a: number, b: number) => number, () => never>();
241
+
242
+ calc
243
+ .add(
244
+ () => operation === 'add',
245
+ (a, b) => a + b
246
+ )
247
+ .add(
248
+ () => operation === 'mul',
249
+ (a, b) => a * b
250
+ )
251
+ .nomatch(() => {
252
+ throw new Error('unknown operation');
253
+ });
254
+
255
+ calc.predicate();
256
+ const result = calc.run(2, 3); // typed as number | undefined
257
+ ```
90
258
 
91
- Register a handler to be executed by `run()` if no branch matched. Unlike `nomatch`, this does not run immediately β€” it defers execution until `run()`.
259
+ ##### `.predicate(...args): void`
92
260
 
93
- ### `run(...args: unknown): unknown`
261
+ - Evaluates all conditions to find the first match
262
+ - Call this before `.run()` to update the matched branch
263
+ - Essential for dynamic behavior
94
264
 
95
- Execute the matched branch and return its value. If no branch matched but a `deferedNomatch` handler was set, returns the handler's return value. Returns `undefined` if neither exists.
265
+ ##### `.run(...args): any`
96
266
 
97
- `args` will be passed to the matched branch or the `deferedNomatch` handler when they are invoked.
267
+ - Executes the currently matched branch
268
+ - Returns the function's return value or `undefined`
98
269
 
99
- ## Types & Distribution
270
+ ## πŸ“„ License
100
271
 
101
- - Types are included (`"types": "./dist/index.d.ts"` in package.json).
102
- - The package entrypoint is an ESM module at `./dist/index.mjs`.
272
+ MIT License - see [LICENSE](LICENSE) file for details.
103
273
 
104
- ## Error Handling ⚠️
274
+ ## 🀝 Contributing
105
275
 
106
- - `TypeError` is thrown if a non-function is passed as a branch or handler.
276
+ Contributions are welcome! Please feel free to submit issues and pull requests.
107
277
 
108
- ## Author & License
278
+ ---
109
279
 
110
- - Author: Kasukabe Tsumugi (<futami16237@gmail.com>)
111
- - License: MIT
280
+ Made with ❀️ by [Kasukabe Tsumugi](https://github.com/baendlorel) (´q‒ α΅• ‒q`) β™‘
package/dist/index.d.ts CHANGED
@@ -1,49 +1,134 @@
1
- declare class DeferBranch {
1
+ declare class DeferBranch<BranchFn extends AnyFn, NoMatchFn extends AnyFn> {
2
2
  private _branch;
3
3
  private _nomatch;
4
4
  /**
5
5
  * Add a new entry
6
- * - will **override** the previous matched branch
6
+ * - **First truthy first served**.
7
7
  * @param condition the condition to match
8
8
  * @param branch the branch to run when matched
9
9
  * @returns this
10
10
  */
11
- add(condition: boolean, branch: Fn): DeferBranch;
11
+ add(condition: boolean, branch: BranchFn): this;
12
12
  /**
13
- * When no branch matched, run this handler
14
- * - This will be called **instantly** and **ignores later matched branches**
13
+ * Register a handler to be called when no branch matched
14
+ * - This will be called **instantly and ignores later matched branches**
15
15
  * @param handler handle the exhausted case
16
16
  * @returns this
17
17
  */
18
- nomatch(handler: Fn): DeferBranch;
18
+ nomatch(handler: AnyFn): this;
19
19
  /**
20
- * When no branch matched, run this handler
21
- * - Will be called when `run()` is called
20
+ * If some branch matched, return its returnValue
21
+ * - if no branch matched, return the nomatch handler's returnValue
22
+ * - if no branch matched and no nomatch handler, return undefined
23
+ *
24
+ * > Note: Although args are for the matched branch, they will also be passed to nomatch handler
25
+ * @param args arguments to pass to the matched branch or nomatch handler
26
+ */
27
+ run(...args: Parameters<BranchFn>): ReturnType<BranchFn> | ReturnType<NoMatchFn> | undefined;
28
+ }
29
+
30
+ declare class DeferBranchDynamic<BranchFn extends AnyFn, NoMatchFn extends AnyFn, ConditionFn extends AnyFn = Predicate<BranchFn>> {
31
+ /**
32
+ * & Compress conditions and branches into a single array since they are paired
33
+ */
34
+ private _condranches;
35
+ private _branch;
36
+ private _nomatch;
37
+ /**
38
+ * Add a new entry
39
+ * - **First truthy first served**.
40
+ * @param condition the condition to match
41
+ * @param branch the branch to run when matched
42
+ * @returns this
43
+ */
44
+ add(condition: ConditionFn, branch: BranchFn): this;
45
+ /**
46
+ * When calling `this.predicate` and no branch matched, run this handler
22
47
  * @param handler handle the exhausted case
23
48
  * @returns this
24
49
  */
25
- deferedNomatch(handler: Fn): DeferBranch;
50
+ nomatch(handler: AnyFn): this;
51
+ /**
52
+ * Run the condition to find the first matched branch
53
+ */
54
+ predicate(...args: Parameters<BranchFn>): void;
26
55
  /**
27
56
  * If some branch matched, return its returnValue
28
- * - if no branch matched, return the nomatch handler returnValue
29
- * - if no branch matched and no nomatch handler, return undefined
30
- * @param args arguments to pass to the matched branch or nomatch handler
57
+ * @param args arguments to pass to the matched branch
31
58
  */
32
- run(...args: unknown[]): unknown;
59
+ run(...args: Parameters<BranchFn>): ReturnType<BranchFn> | undefined;
33
60
  }
61
+
62
+ /**
63
+ * ## Steps to use
64
+ * 1. const a = deferedBranch()
65
+ * 2. add branches by a.add(condition, branch)
66
+ * 3. (optional) add defered nomatch handler, or add and run at the same time by a.nomatch(handler)
67
+ * 4. ...run some other logic
68
+ * 5. run this branch by a.run();
69
+ *
70
+ * ## Type Annotation (optional)
71
+ * ```ts
72
+ * type BranchFn = (a: number) => void;
73
+ * type NoMatchFn = () => void;
74
+ * const a = deferedBranch<BranchFn, NoMatchFn>();
75
+ *
76
+ * // then we have restrictions:
77
+ * a.add(true, someFn satisfies BranchFn);
78
+ * a.nomatch(otherFn satisfies NoMatchFn);
79
+ *
80
+ * a.run(); // input args are restricted to Parameters<BranchFn>
81
+ * ```
82
+ *
83
+ * ## About
84
+ * @package DeferedBranch
85
+ * @author Kasukabe Tsumugi <futami16237@gmail.com>
86
+ * @version 2.1.0 (Last Update: 2025.09.21 08:03:55.124)
87
+ * @license MIT
88
+ * @link https://github.com/baendlorel/defered-branch
89
+ * @link https://baendlorel.github.io/ Welcome to my site!
90
+ * @description Sometimes we need to judge something with multiple cases, but the main job should be done later. So we have Defered Branch!
91
+ * @copyright Copyright (c) 2025 Kasukabe Tsumugi. All rights reserved.
92
+ */
93
+ declare const deferedBranch: <BranchFn extends AnyFn = AnyFn, NoMatchFn extends AnyFn = AnyFn>() => DeferBranch<BranchFn, NoMatchFn>;
34
94
  /**
95
+ * ## Steps to use
96
+ * 1. const a = deferedBranchDynamic()
97
+ * 2. add branches by a.add(condition, branch)
98
+ * 3. add nomatch handler by a.nomatch(handler)
99
+ * 4. call a.predicate(...) to find the matched branch
100
+ * 5. ...run some other logic
101
+ * 6. run this branch by a.run();
102
+ *
103
+ * ## Type Annotation (optional)
104
+ * ```ts
105
+ * type BranchFn = (a: number) => void;
106
+ * type NoMatchFn = () => void;
107
+ * // optional, you can customize the condition function type
108
+ * type ConditionFn = Predicate<BranchFn>;
109
+ * const a = deferedBranch<BranchFn, NoMatchFn, ConditionFn>();
110
+ *
111
+ * // then we have restrictions:
112
+ * a.add(someFn statisfies ConditionFn, secondFn satisfies BranchFn);
113
+ * a.nomatch(thirdFn satisfies NoMatchFn);
114
+ *
115
+ * a.run(); // input args are restricted to Parameters<BranchFn>
116
+ * ```
117
+ *
35
118
  * ## About
36
119
  * @package DeferedBranch
37
120
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
38
- * @version 1.2.2 (Last Update: 2025.09.19 08:09:05.967)
121
+ * @version 2.1.0 (Last Update: 2025.09.21 08:03:55.124)
39
122
  * @license MIT
40
123
  * @link https://github.com/baendlorel/defered-branch
124
+ * @link https://baendlorel.github.io/ Welcome to my site!
41
125
  * @description Sometimes we need to judge something with multiple cases, but the main job should be done later. So we have Defered Branch!
42
126
  * @copyright Copyright (c) 2025 Kasukabe Tsumugi. All rights reserved.
43
127
  */
44
- declare const deferedBranch: () => DeferBranch;
128
+ declare const deferedBranchDynamic: <BranchFn extends AnyFn = AnyFn, NoMatchFn extends AnyFn = AnyFn, ConditionFn extends AnyFn = Predicate<BranchFn>>() => DeferBranchDynamic<BranchFn, NoMatchFn, ConditionFn>;
45
129
 
46
- export { deferedBranch };
130
+ export { deferedBranch, deferedBranchDynamic };
47
131
 
48
132
  // # from: src/global.d.ts
49
- type Fn = (...args: any[]) => any;
133
+ type AnyFn = (...args: any[]) => any;
134
+ type Predicate<Fn extends AnyFn> = (...args: Parameters<Fn>) => boolean;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- class t{t=null;o=null;add(t,n){if("function"!=typeof n)throw new TypeError("Branch must be a function");return t&&(this.t=n),this}nomatch(t){if("function"!=typeof t)throw new TypeError("Branch must be a function");return this.t||t(),this}deferedNomatch(t){if("function"!=typeof t)throw new TypeError("Branch must be a function");return this.o=t,this}run(...t){return this.t?this.t(...t):this.o?this.o(...t):void 0}}const n=()=>new t;export{n as deferedBranch};
1
+ class n{t=null;h=null;add(n,t){if("function"!=typeof t)throw new TypeError("DeferBranch: branch must be a function");return n&&!this.t&&(this.t=t),this}nomatch(n){if("function"!=typeof n)throw new TypeError("DeferBranch: branch must be a function");return this.t||n(),this}run(...n){return this.t?this.t.apply(null,n):this.h?this.h.apply(null,n):void 0}}class t{i=[];t=null;h=null;add(n,t){if("function"!=typeof n)throw new TypeError("DeferBranch: condition must be a function");if("function"!=typeof t)throw new TypeError("DeferBranch: branch must be a function");return this.i.push(n,t),this}nomatch(n){if("function"!=typeof n)throw new TypeError("DeferBranch: handler must be a function");return this.h=n,this}predicate(...n){const t=this.i.length;for(let n=0;n<t;n+=2)if(this.i[n]())return void(this.t=this.i[n+1]);this.h?.apply(this,n)}run(...n){return this.t?.apply(this,n)}}const r=()=>new n,e=()=>new t;export{r as deferedBranch,e as deferedBranchDynamic};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "defered-branch",
3
- "version": "1.2.2",
3
+ "version": "2.1.0",
4
4
  "author": {
5
5
  "name": "Kasukabe Tsumugi",
6
6
  "email": "futami16237@gmail.com"