@keshavsoft/api-tree 2.0.0 → 6.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.
Files changed (36) hide show
  1. package/README.md +52 -134
  2. package/docs/api.html +123 -0
  3. package/docs/examples.html +100 -0
  4. package/docs/how-it-works.html +69 -0
  5. package/docs/index.html +63 -266
  6. package/docs/style.css +272 -0
  7. package/docs/validation.html +88 -0
  8. package/package.json +7 -3
  9. package/src/index.d.ts +1 -5
  10. package/src/index.js +2 -2
  11. package/src/v3/index.js +34 -0
  12. package/src/v3/internal-working/buildTree.js +35 -0
  13. package/src/v4/blueprint/api.json +13 -0
  14. package/src/v4/blueprint/source.json +228 -0
  15. package/src/v4/index.js +29 -0
  16. package/src/v4/internal-working/buildTree.js +35 -0
  17. package/src/v4/internal-working/guards/index.js +15 -0
  18. package/src/v4/internal-working/guards/isFunction.js +9 -0
  19. package/src/v4/internal-working/guards/isObject.js +9 -0
  20. package/src/v4/internal-working/guards/isStringArray.js +15 -0
  21. package/src/v5/blueprint/api.json +13 -0
  22. package/src/v5/blueprint/source.json +228 -0
  23. package/src/v5/engine/guards/index.js +15 -0
  24. package/src/v5/engine/guards/isFunction.js +9 -0
  25. package/src/v5/engine/guards/isObject.js +9 -0
  26. package/src/v5/engine/guards/isStringArray.js +15 -0
  27. package/src/v5/engine/run.js +35 -0
  28. package/src/v5/index.js +29 -0
  29. package/src/v6/blueprint/api.json +13 -0
  30. package/src/v6/blueprint/source.json +228 -0
  31. package/src/v6/engine/guards/index.js +15 -0
  32. package/src/v6/engine/guards/isFunction.js +9 -0
  33. package/src/v6/engine/guards/isObject.js +9 -0
  34. package/src/v6/engine/guards/isStringArray.js +15 -0
  35. package/src/v6/engine/run.js +35 -0
  36. package/src/v6/index.js +29 -0
package/README.md CHANGED
@@ -1,62 +1,65 @@
1
- # @keshavsoft/api-tree
1
+ # api-tree
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@keshavsoft/api-tree.svg)](https://www.npmjs.com/package/@keshavsoft/api-tree)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
- [![Node.js CI](https://img.shields.io/badge/node-%3E%3D20.10-brightgreen.svg)]()
6
-
7
- > A declarative, ultra-lean routing engine that transforms **Source Schemas**, **API Paths**, and an **Executor** into a callable runtime API tree.
3
+ A small, zero-dependency runtime API-tree builder.
8
4
 
9
5
  ---
10
6
 
11
- ## 💡 The Problem & Philosophy
7
+ ## 💡 When Should You Use This?
12
8
 
13
- In modern modular architectures, APIs often end up bloated because **schema contracts**, **route definitions**, and **runtime execution** are tangled together.
9
+ `@keshavsoft/api-tree` is built specifically for systems where **most of the execution logic is identical** and handled by a **single core function**, while only a few parameters vary from endpoint to endpoint.
14
10
 
15
- `@keshavsoft/api-tree` decouples these concerns completely:
11
+ ### The Problem It Solves
16
12
 
17
- $$\mathbf{Runtime\ Tree} = \underbrace{\mathbf{Source\ Schema}}_{\text{JSON Contract}} \;+\; \underbrace{\mathbf{API\ Paths}}_{\text{Dotted Routes}} \;+\; \underbrace{\mathbf{Executor}}_{\text{Execution Flavor}}$$
13
+ Consider an SDK or API client with 20, 50, or 200 endpoints. In most codebases, **95% of the work is identical across all of them**:
14
+ - Sending an HTTP POST or XML payload
15
+ - Setting headers and managing network timeouts
16
+ - Parsing envelopes and extracting data
18
17
 
19
- - **Domain-Agnostic**: Does not know about HTTP, XML, TDL, databases, or specific business logic.
20
- - **Single Responsibility**: Generates the navigable runtime object tree from contracts and leaves execution to your handler.
21
- - **Zero Dependencies**: Pure, modern ES module running at native speeds.
18
+ The only thing that actually changes between `app.masters.unit.all()` and `app.company.fetch()` are a few variables: a resource name, a TDL query string, or a URL parameter.
22
19
 
23
- ```text
24
- ┌─────────────────────────┐ ┌────────────────────────┐ ┌───────────────────────┐
25
- │ Source JSON │ + │ API Paths │ + │ Executor │
26
- │ (Schema Specifications) │ │ (Dotted Route Strings) │ │ (Custom Handler Func) │
27
- └───────────┬─────────────┘ └───────────┬────────────┘ └──────────┬────────────┘
28
- │ │ │
29
- └───────────────────────┬───────┴─────────────────────────────┘
30
- │
31
- ▼
32
- @keshavsoft/api-tree
33
- │
34
- ▼
35
- Callable Runtime API Tree
36
- app.users.profile.fetch("123")
20
+ Yet without `api-tree`, developers write dozens or hundreds of repetitive, hand-crafted wrapper functions just to call the exact same underlying function with different arguments:
21
+
22
+ ```javascript
23
+ // ❌ The Anti-Pattern: 100 repetitive functions doing the exact same thing
24
+ export const getUnits = () => dispatchTally("<TYPE>Unit</TYPE>...");
25
+ export const getLedgers = () => dispatchTally("<TYPE>Ledger</TYPE>...");
26
+ export const getCompany = () => dispatchTally("<TYPE>Company</TYPE>...");
37
27
  ```
38
28
 
39
29
  ---
40
30
 
41
- ## 📦 Installation
31
+ ## 🚀 The api-tree Pattern: 1 Engine + Variable JSON
32
+
33
+ Instead of writing endless boilerplate wrappers, you separate concerns into three clean parts:
34
+
35
+ 1. **One Single Executor**: You write your execution muscle exactly once. It knows how to send the request and handle responses.
36
+ 2. **Variable Data in JSON (`source.json`)**: You define only the things that change (TDL queries, actions, resources, URLs) in a declarative schema.
37
+ 3. **The Navigation List (`api.json`)**: You list the allowed routes in a flat, readable array.
42
38
 
43
- ```bash
44
- npm install @keshavsoft/api-tree
39
+ ```text
40
+ source JSON (Variables) + API paths (Routes) + executor (Single Function)
41
+ ↓
42
+ api-tree
43
+ ↓
44
+ Callable Runtime Tree
45
+ app.masters.unit.all()
45
46
  ```
46
47
 
48
+ When a new endpoint is needed, you don't write new JavaScript wrapper functions, manage imports, or test routing logic. **You simply add one entry to your JSON file.** `@keshavsoft/api-tree` binds your single executor to that new definition and instantly exposes it on the callable tree.
49
+
47
50
  ---
48
51
 
49
- ## 🚀 Quick Start
52
+ ## Usage
50
53
 
51
54
  ```javascript
52
55
  import apiTree from "@keshavsoft/api-tree";
53
56
 
54
- // 1. Source JSON (the schema/metadata contract)
57
+ // 1. Source JSON (the variable parameters)
55
58
  const source = {
56
59
  app: {
57
60
  users: {
58
61
  profile: {
59
- fetch: { action: "fetch", resource: "User", timeout: 5000 }
62
+ fetch: { action: "fetch", resource: "User" }
60
63
  }
61
64
  }
62
65
  }
@@ -67,67 +70,27 @@ const apiPaths = [
67
70
  "app.users.profile.fetch"
68
71
  ];
69
72
 
70
- // 3. Executor Function
71
- const executor = async ({ inRoutePath, inParam, inLeafSpec }) => {
72
- console.log(`Executing ${inRoutePath} for ID: ${inParam}`);
73
- console.log("Leaf schema definition:", inLeafSpec);
74
- return { id: inParam, name: "Alice", action: inLeafSpec.action };
73
+ // 3. The Single Executor Function (handles 100% of execution)
74
+ const executor = async ({ inRoutePath, inLeafSpec, inParam }) => {
75
+ return {
76
+ path: inRoutePath,
77
+ spec: inLeafSpec,
78
+ id: inParam
79
+ };
75
80
  };
76
81
 
77
82
  // 4. Build the callable tree
78
- const api = apiTree(source, apiPaths, executor);
83
+ const app = apiTree(source, apiPaths, executor);
79
84
 
80
85
  // 5. Call your generated tree!
81
- const user = await api.users.profile.fetch("123");
82
- console.log(user);
83
- // => { id: "123", name: "Alice", action: "fetch" }
84
- ```
85
-
86
- ---
87
-
88
- ## ⚡ Key Features (v2)
89
-
90
- ### 1. Pre-Resolved Leaf Specification (`inLeafSpec`)
91
- Your executor automatically receives the pre-resolved definition object directly from `source.json` under `inLeafSpec`. No need to write repetitive nested property access code!
92
-
93
- ### 2. Intelligent Root Handling
94
- - **Single-Root Unwrapping**: When all paths share a common root namespace (e.g., `app.users.list`, `app.orders.create`), `api-tree` automatically unwraps the root so you call `api.users.list()` directly.
95
- - **Multi-Root Preservation**: When paths span multiple top-level domains (e.g., `users.list` and `orders.create`), `api-tree` automatically preserves all top-level roots (`api.users.list()` and `api.orders.create()`).
96
- - **Explicit Override**: You can pass `{ inUnwrapRoot: false }` to keep the root prefix intact.
97
-
98
- ### 3. Dual Signature Support
99
- Supports both **positional** arguments and the **in-local named object** convention:
100
-
101
- ```javascript
102
- // Positional
103
- const api = apiTree(source, apiPaths, executor, options);
104
-
105
- // Named Object
106
- const api = apiTree({
107
- inSource: source,
108
- inApiPaths: apiPaths,
109
- inExecutor: executor,
110
- inOptions: { inUnwrapRoot: false }
111
- });
112
- ```
113
-
114
- ### 4. Callable Hybrid Branches
115
- If a path is both a callable node and has child branches (e.g. `api.users` and `api.users.profile`), `api-tree` attaches child branches directly onto the function:
116
- ```javascript
117
- await api.users(); // Callable root!
118
- await api.users.profile(); // Child leaf also callable!
86
+ const result = await app.users.profile.fetch("123");
119
87
  ```
120
88
 
121
89
  ---
122
90
 
123
- ## 📖 Execution Context Reference
124
-
125
- Whenever an attached leaf function is invoked:
126
- ```javascript
127
- await api.users.profile.fetch("param1", "extraArg1", "extraArg2");
128
- ```
91
+ ## Execution Context
129
92
 
130
- Your `executor` receives a single, standardized context object:
93
+ When an attached leaf function is invoked, your `executor` receives a single, standardized context object:
131
94
 
132
95
  | Property | Type | Description |
133
96
  | :--- | :--- | :--- |
@@ -140,61 +103,16 @@ Your `executor` receives a single, standardized context object:
140
103
 
141
104
  ---
142
105
 
143
- ## 🛡️ Input Validation & Error Handling
106
+ ## Validation & Guarantees
144
107
 
145
108
  `@keshavsoft/api-tree` performs strict pre-flight validation to catch contract misconfigurations early:
146
109
 
147
- - **`source`**: Must be a non-null plain JSON object (throws `TypeError: source must be a JSON object.`).
148
- - **`apiPaths`**: Must be an array of non-empty strings without empty segments (e.g., `"users..fetch"` or `"users."` throws `TypeError`).
149
- - **`executor`**: Must be a valid callable function (throws `TypeError: executor must be a function.`).
150
-
151
- ---
152
-
153
- ## 🌍 Real-World Architecture Examples
154
-
155
- ### Example A: Decoupling TallyPrime Runtimes
156
- ```javascript
157
- import apiTree from "@keshavsoft/api-tree";
158
- import { source, apiPaths } from "tally-spec";
159
- import tallyXmlExecutor from "./tallyXmlExecutor.js";
160
-
161
- // Generates app.masters.unit.all(), app.company.fetch(), etc.
162
- const tally = apiTree(source, apiPaths, tallyXmlExecutor);
163
- const units = await tally.masters.unit.all();
164
- ```
165
-
166
- ### Example B: Dynamic HTTP / REST Client
167
- ```javascript
168
- import apiTree from "@keshavsoft/api-tree";
169
-
170
- const endpoints = {
171
- api: {
172
- v1: {
173
- users: { get: { method: "GET", url: "/api/v1/users" } }
174
- }
175
- }
176
- };
177
-
178
- const client = apiTree(endpoints, ["api.v1.users.get"], async ({ inLeafSpec, inParam }) => {
179
- const res = await fetch(`${inLeafSpec.url}/${inParam}`, { method: inLeafSpec.method });
180
- return res.json();
181
- });
182
-
183
- const user = await client.v1.users.get(42);
184
- ```
185
-
186
- ---
187
-
188
- ## 🧪 Testing
189
-
190
- The package includes a comprehensive test suite using Node's native test runner:
191
-
192
- ```bash
193
- npm test
194
- ```
110
+ - **`source`**: Must be a non-null plain JSON object.
111
+ - **`apiPaths`**: Must be an array of non-empty strings with valid segments.
112
+ - **`executor`**: Must be a valid callable function.
195
113
 
196
114
  ---
197
115
 
198
- ## 📄 License
116
+ ## License
199
117
 
200
118
  MIT © [KeshavSoft](https://github.com/keshavsoft)
package/docs/api.html ADDED
@@ -0,0 +1,123 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>api-tree — API Reference</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <div class="layout">
11
+ <aside class="sidebar">
12
+ <div class="sidebar-brand">
13
+ <a href="index.html" class="brand-title">api-tree</a>
14
+ <div class="brand-subtitle">v2.0.0 &bull; KeshavSoft</div>
15
+ </div>
16
+ <div class="nav-label">Documentation</div>
17
+ <ul class="nav-list">
18
+ <li><a href="index.html" class="nav-link">The Story</a></li>
19
+ <li><a href="how-it-works.html" class="nav-link">How It Works</a></li>
20
+ <li><a href="api.html" class="nav-link active">API Reference</a></li>
21
+ <li><a href="examples.html" class="nav-link">Examples</a></li>
22
+ <li><a href="validation.html" class="nav-link">Guarantees</a></li>
23
+ </ul>
24
+ <div class="sidebar-footer">
25
+ <a href="https://github.com/keshavsoft/api-tree" target="_blank" rel="noopener">GitHub Repository &rarr;</a>
26
+ </div>
27
+ </aside>
28
+
29
+ <main class="content">
30
+ <h1>API Reference</h1>
31
+ <div class="lead">Complete contract specifications and execution context reference.</div>
32
+
33
+ <h2>Function Signatures</h2>
34
+ <p><code>@keshavsoft/api-tree</code> supports both positional arguments and the named object convention:</p>
35
+
36
+ <h3>Positional Call</h3>
37
+ <pre><code>import apiTree from "@keshavsoft/api-tree";
38
+
39
+ const api = apiTree(source, apiPaths, executor, options);</code></pre>
40
+
41
+ <h3>Named Object Call</h3>
42
+ <pre><code>import apiTree from "@keshavsoft/api-tree";
43
+
44
+ const api = apiTree({
45
+ inSource: source,
46
+ inApiPaths: apiPaths,
47
+ inExecutor: executor,
48
+ inOptions: { inUnwrapRoot: false }
49
+ });</code></pre>
50
+
51
+ <h2>Execution Context Reference</h2>
52
+ <p>Whenever an attached leaf method is called (e.g. <code>app.users.profile.fetch("123", "extra")</code>), your executor receives a single standardized context object:</p>
53
+
54
+ <table>
55
+ <thead>
56
+ <tr>
57
+ <th>Property</th>
58
+ <th>Type</th>
59
+ <th>Description</th>
60
+ </tr>
61
+ </thead>
62
+ <tbody>
63
+ <tr>
64
+ <td><code>inRoutePath</code></td>
65
+ <td><code>string</code></td>
66
+ <td>The full dot-notation route path (e.g. <code>"app.users.profile.fetch"</code>).</td>
67
+ </tr>
68
+ <tr>
69
+ <td><code>inParam</code></td>
70
+ <td><code>any</code></td>
71
+ <td>The primary argument passed to the leaf method.</td>
72
+ </tr>
73
+ <tr>
74
+ <td><code>inArgs</code></td>
75
+ <td><code>any[]</code></td>
76
+ <td>Array containing all subsequent arguments passed after <code>inParam</code>.</td>
77
+ </tr>
78
+ <tr>
79
+ <td><code>inLeafSpec</code></td>
80
+ <td><code>object | undefined</code></td>
81
+ <td>The resolved leaf definition object found in <code>source.json</code>.</td>
82
+ </tr>
83
+ <tr>
84
+ <td><code>inSource</code></td>
85
+ <td><code>object</code></td>
86
+ <td>The complete raw <code>source</code> schema object.</td>
87
+ </tr>
88
+ <tr>
89
+ <td><code>inPathSegments</code></td>
90
+ <td><code>string[]</code></td>
91
+ <td>Array of individual path segments (e.g. <code>["app", "users", "profile", "fetch"]</code>).</td>
92
+ </tr>
93
+ </tbody>
94
+ </table>
95
+
96
+ <h2>Options (<code>inOptions</code>)</h2>
97
+ <table>
98
+ <thead>
99
+ <tr>
100
+ <th>Option</th>
101
+ <th>Type</th>
102
+ <th>Default</th>
103
+ <th>Description</th>
104
+ </tr>
105
+ </thead>
106
+ <tbody>
107
+ <tr>
108
+ <td><code>inUnwrapRoot</code></td>
109
+ <td><code>boolean</code></td>
110
+ <td><code>true</code></td>
111
+ <td>When <code>true</code> and all paths share a common root segment, unwraps the root. Set to <code>false</code> to preserve the full root namespace.</td>
112
+ </tr>
113
+ </tbody>
114
+ </table>
115
+
116
+ <div class="page-nav">
117
+ <a href="how-it-works.html">&larr; How It Works</a>
118
+ <a href="examples.html">Next: Examples &rarr;</a>
119
+ </div>
120
+ </main>
121
+ </div>
122
+ </body>
123
+ </html>
@@ -0,0 +1,100 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>api-tree — Examples</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <div class="layout">
11
+ <aside class="sidebar">
12
+ <div class="sidebar-brand">
13
+ <a href="index.html" class="brand-title">api-tree</a>
14
+ <div class="brand-subtitle">v2.0.0 &bull; KeshavSoft</div>
15
+ </div>
16
+ <div class="nav-label">Documentation</div>
17
+ <ul class="nav-list">
18
+ <li><a href="index.html" class="nav-link">The Story</a></li>
19
+ <li><a href="how-it-works.html" class="nav-link">How It Works</a></li>
20
+ <li><a href="api.html" class="nav-link">API Reference</a></li>
21
+ <li><a href="examples.html" class="nav-link active">Examples</a></li>
22
+ <li><a href="validation.html" class="nav-link">Guarantees</a></li>
23
+ </ul>
24
+ <div class="sidebar-footer">
25
+ <a href="https://github.com/keshavsoft/api-tree" target="_blank" rel="noopener">GitHub Repository &rarr;</a>
26
+ </div>
27
+ </aside>
28
+
29
+ <main class="content">
30
+ <h1>Examples</h1>
31
+ <div class="lead">Practical implementations across different ecosystems.</div>
32
+
33
+ <h2>1. Decoupling TallyPrime Runtimes</h2>
34
+ <p>In the KeshavSoft Tally ecosystem, <code>tally-spec</code> owns the data contracts, <code>@keshavsoft/api-tree</code> builds the navigable tree, and <code>tally-xml-tdl</code> executes raw XML over HTTP:</p>
35
+ <pre><code>import apiTree from "@keshavsoft/api-tree";
36
+ import { source, apiPaths } from "tally-spec";
37
+ import tallyXmlExecutor from "./engine/execution/index.js";
38
+
39
+ // Builds callable tree: app.masters.unit.all(), app.company.fetch()
40
+ const app = apiTree(source, apiPaths, tallyXmlExecutor);
41
+
42
+ // Fetch live units directly from TallyPrime
43
+ const units = await app.masters.unit.all();
44
+ console.log(units);</code></pre>
45
+
46
+ <h2>2. Dynamic REST / HTTP Client</h2>
47
+ <p>Instead of manually writing HTTP client SDKs for every microservice, generate one on the fly from an endpoint schema:</p>
48
+ <pre><code>import apiTree from "@keshavsoft/api-tree";
49
+
50
+ const endpoints = {
51
+ api: {
52
+ v1: {
53
+ users: {
54
+ get: { method: "GET", url: "https://api.example.com/users" }
55
+ }
56
+ }
57
+ }
58
+ };
59
+
60
+ const apiPaths = ["api.v1.users.get"];
61
+
62
+ const httpExecutor = async ({ inLeafSpec, inParam }) => {
63
+ const response = await fetch(`${inLeafSpec.url}/${inParam}`, {
64
+ method: inLeafSpec.method
65
+ });
66
+ return response.json();
67
+ };
68
+
69
+ const client = apiTree(endpoints, apiPaths, httpExecutor);
70
+
71
+ // Invokes GET https://api.example.com/users/42
72
+ const user = await client.v1.users.get(42);</code></pre>
73
+
74
+ <h2>3. CLI Command Dispatcher</h2>
75
+ <p>Map command lines directly to business logic handlers using path notation:</p>
76
+ <pre><code>import apiTree from "@keshavsoft/api-tree";
77
+
78
+ const commands = {
79
+ cli: {
80
+ auth: {
81
+ login: { description: "Authenticate user session" },
82
+ logout: { description: "Terminate session" }
83
+ }
84
+ }
85
+ };
86
+
87
+ const cli = apiTree(commands, ["cli.auth.login", "cli.auth.logout"], async ({ inRoutePath, inParam }) => {
88
+ console.log(`Executing ${inRoutePath} with credentials:`, inParam);
89
+ });
90
+
91
+ await cli.auth.login({ username: "admin" });</code></pre>
92
+
93
+ <div class="page-nav">
94
+ <a href="api.html">&larr; API Reference</a>
95
+ <a href="validation.html">Next: Guarantees &rarr;</a>
96
+ </div>
97
+ </main>
98
+ </div>
99
+ </body>
100
+ </html>
@@ -0,0 +1,69 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>api-tree — How It Works</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <div class="layout">
11
+ <aside class="sidebar">
12
+ <div class="sidebar-brand">
13
+ <a href="index.html" class="brand-title">api-tree</a>
14
+ <div class="brand-subtitle">v2.0.0 &bull; KeshavSoft</div>
15
+ </div>
16
+ <div class="nav-label">Documentation</div>
17
+ <ul class="nav-list">
18
+ <li><a href="index.html" class="nav-link">The Story</a></li>
19
+ <li><a href="how-it-works.html" class="nav-link active">How It Works</a></li>
20
+ <li><a href="api.html" class="nav-link">API Reference</a></li>
21
+ <li><a href="examples.html" class="nav-link">Examples</a></li>
22
+ <li><a href="validation.html" class="nav-link">Guarantees</a></li>
23
+ </ul>
24
+ <div class="sidebar-footer">
25
+ <a href="https://github.com/keshavsoft/api-tree" target="_blank" rel="noopener">GitHub Repository &rarr;</a>
26
+ </div>
27
+ </aside>
28
+
29
+ <main class="content">
30
+ <h1>How It Works</h1>
31
+ <div class="lead">Under the hood of the dynamic runtime routing engine.</div>
32
+
33
+ <h2>1. The Assembly Pipeline</h2>
34
+ <p>When you call <code>apiTree(source, apiPaths, executor)</code>, the engine performs three synchronous steps:</p>
35
+ <ol>
36
+ <li><strong>Pre-Flight Validation</strong>: Ensures that <code>source</code> is a valid plain object, <code>apiPaths</code> contains valid non-empty string segments, and <code>executor</code> is a callable function.</li>
37
+ <li><strong>Tree Branching</strong>: Splits each dotted path into segments (e.g. <code>["app", "users", "profile", "fetch"]</code>) and recursively builds nested JavaScript objects.</li>
38
+ <li><strong>Leaf Handler Attachment</strong>: The final segment becomes an async callable leaf function wired to your executor.</li>
39
+ </ol>
40
+
41
+ <h2>2. Pre-Resolved Leaf Specifications (<code>inLeafSpec</code>)</h2>
42
+ <p>In older patterns, executors had to manually traverse <code>inSource</code> to locate their definition. In <code>v2</code>, <code>api-tree</code> automatically resolves the leaf object from <code>source.json</code> during tree construction:</p>
43
+ <pre><code>const executor = async ({ inRoutePath, inLeafSpec, inParam }) => {
44
+ // inLeafSpec is already extracted from source:
45
+ // { action: "fetch", resource: "User", timeout: 5000 }
46
+ console.log(inLeafSpec.resource);
47
+ };</code></pre>
48
+
49
+ <h2>3. Intelligent Root Handling</h2>
50
+ <p>Depending on your architecture, your API paths may belong to a single domain or multiple domains:</p>
51
+ <ul>
52
+ <li><strong>Single-Root Auto-Unwrapping</strong>: When all paths start with the same root (e.g. <code>["app.users.fetch", "app.reports.fetch"]</code>), <code>api-tree</code> automatically unwraps <code>app</code>, giving you clean access like <code>api.users.fetch()</code>.</li>
53
+ <li><strong>Multi-Root Domain Preservation</strong>: When paths belong to multiple roots (e.g. <code>["users.list", "orders.create"]</code>), <code>api-tree</code> preserves both roots (<code>api.users.list()</code> and <code>api.orders.create()</code>).</li>
54
+ <li><strong>Explicit Configuration</strong>: You can pass <code>{ inUnwrapRoot: false }</code> to force keeping the root prefix intact.</li>
55
+ </ul>
56
+
57
+ <h2>4. Hybrid Callable Branches</h2>
58
+ <p>In JavaScript, functions are objects. If your schema defines both a parent route and a child route (such as <code>api.users</code> and <code>api.users.profile</code>), <code>api-tree</code> seamlessly attaches the child branch onto the parent function:</p>
59
+ <pre><code>await api.users(); // Calls the parent handler
60
+ await api.users.profile(); // Calls the child handler</code></pre>
61
+
62
+ <div class="page-nav">
63
+ <a href="index.html">&larr; The Story</a>
64
+ <a href="api.html">Next: API Reference &rarr;</a>
65
+ </div>
66
+ </main>
67
+ </div>
68
+ </body>
69
+ </html>