@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.
- package/README.md +52 -134
- package/docs/api.html +123 -0
- package/docs/examples.html +100 -0
- package/docs/how-it-works.html +69 -0
- package/docs/index.html +63 -266
- package/docs/style.css +272 -0
- package/docs/validation.html +88 -0
- package/package.json +7 -3
- package/src/index.d.ts +1 -5
- package/src/index.js +2 -2
- package/src/v3/index.js +34 -0
- package/src/v3/internal-working/buildTree.js +35 -0
- package/src/v4/blueprint/api.json +13 -0
- package/src/v4/blueprint/source.json +228 -0
- package/src/v4/index.js +29 -0
- package/src/v4/internal-working/buildTree.js +35 -0
- package/src/v4/internal-working/guards/index.js +15 -0
- package/src/v4/internal-working/guards/isFunction.js +9 -0
- package/src/v4/internal-working/guards/isObject.js +9 -0
- package/src/v4/internal-working/guards/isStringArray.js +15 -0
- package/src/v5/blueprint/api.json +13 -0
- package/src/v5/blueprint/source.json +228 -0
- package/src/v5/engine/guards/index.js +15 -0
- package/src/v5/engine/guards/isFunction.js +9 -0
- package/src/v5/engine/guards/isObject.js +9 -0
- package/src/v5/engine/guards/isStringArray.js +15 -0
- package/src/v5/engine/run.js +35 -0
- package/src/v5/index.js +29 -0
- package/src/v6/blueprint/api.json +13 -0
- package/src/v6/blueprint/source.json +228 -0
- package/src/v6/engine/guards/index.js +15 -0
- package/src/v6/engine/guards/isFunction.js +9 -0
- package/src/v6/engine/guards/isObject.js +9 -0
- package/src/v6/engine/guards/isStringArray.js +15 -0
- package/src/v6/engine/run.js +35 -0
- package/src/v6/index.js +29 -0
package/README.md
CHANGED
|
@@ -1,62 +1,65 @@
|
|
|
1
|
-
#
|
|
1
|
+
# api-tree
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[]()
|
|
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
|
-
## 💡
|
|
7
|
+
## 💡 When Should You Use This?
|
|
12
8
|
|
|
13
|
-
|
|
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
|
-
|
|
11
|
+
### The Problem It Solves
|
|
16
12
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
##
|
|
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
|
-
```
|
|
44
|
-
|
|
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
|
-
##
|
|
52
|
+
## Usage
|
|
50
53
|
|
|
51
54
|
```javascript
|
|
52
55
|
import apiTree from "@keshavsoft/api-tree";
|
|
53
56
|
|
|
54
|
-
// 1. Source JSON (the
|
|
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"
|
|
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,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
83
|
+
const app = apiTree(source, apiPaths, executor);
|
|
79
84
|
|
|
80
85
|
// 5. Call your generated tree!
|
|
81
|
-
const
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
148
|
-
- **`apiPaths`**: Must be an array of non-empty strings
|
|
149
|
-
- **`executor`**: Must be a valid callable 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
|
-
##
|
|
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 • 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 →</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">← How It Works</a>
|
|
118
|
+
<a href="examples.html">Next: Examples →</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 • 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 →</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">← API Reference</a>
|
|
95
|
+
<a href="validation.html">Next: Guarantees →</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 • 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 →</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">← The Story</a>
|
|
64
|
+
<a href="api.html">Next: API Reference →</a>
|
|
65
|
+
</div>
|
|
66
|
+
</main>
|
|
67
|
+
</div>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|