@keshavsoft/api-tree 1.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 +77 -52
- package/docs/api.html +123 -0
- package/docs/examples.html +100 -0
- package/docs/how-it-works.html +69 -0
- package/docs/index.html +95 -26
- package/docs/style.css +272 -0
- package/docs/validation.html +88 -0
- package/package.json +7 -3
- package/src/index.d.ts +65 -19
- package/src/index.js +2 -2
- package/src/v2/blueprint/api.json +4 -0
- package/src/v2/blueprint/source.json +22 -0
- package/src/v2/index.js +33 -0
- package/src/v2/internal-working/route/attachPath.js +38 -0
- package/src/v2/internal-working/route/createLeafHandler.js +25 -0
- package/src/v2/internal-working/route/index.js +35 -0
- package/src/v2/internal-working/route/resolveLeafSpec.js +22 -0
- package/src/v2/internal-working/validate/index.js +42 -0
- package/src/v2/internal-working/validate/isPlainObject.js +12 -0
- 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,93 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# api-tree
|
|
2
2
|
|
|
3
|
-
A small runtime API-tree builder.
|
|
3
|
+
A small, zero-dependency runtime API-tree builder.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## π‘ When Should You Use This?
|
|
8
|
+
|
|
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.
|
|
10
|
+
|
|
11
|
+
### The Problem It Solves
|
|
12
|
+
|
|
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
|
|
17
|
+
|
|
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.
|
|
19
|
+
|
|
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>...");
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
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.
|
|
6
38
|
|
|
7
39
|
```text
|
|
8
|
-
source JSON
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
api-tree
|
|
15
|
-
β
|
|
16
|
-
callable API
|
|
40
|
+
source JSON (Variables) + API paths (Routes) + executor (Single Function)
|
|
41
|
+
β
|
|
42
|
+
api-tree
|
|
43
|
+
β
|
|
44
|
+
Callable Runtime Tree
|
|
45
|
+
app.masters.unit.all()
|
|
17
46
|
```
|
|
18
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
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
19
52
|
## Usage
|
|
20
53
|
|
|
21
|
-
```
|
|
54
|
+
```javascript
|
|
22
55
|
import apiTree from "@keshavsoft/api-tree";
|
|
23
56
|
|
|
57
|
+
// 1. Source JSON (the variable parameters)
|
|
24
58
|
const source = {
|
|
25
59
|
app: {
|
|
26
60
|
users: {
|
|
27
61
|
profile: {
|
|
28
|
-
fetch: { action: "fetch", resource: "
|
|
62
|
+
fetch: { action: "fetch", resource: "User" }
|
|
29
63
|
}
|
|
30
64
|
}
|
|
31
65
|
}
|
|
32
66
|
};
|
|
33
67
|
|
|
68
|
+
// 2. Allowable API Paths
|
|
34
69
|
const apiPaths = [
|
|
35
70
|
"app.users.profile.fetch"
|
|
36
71
|
];
|
|
37
72
|
|
|
38
|
-
|
|
73
|
+
// 3. The Single Executor Function (handles 100% of execution)
|
|
74
|
+
const executor = async ({ inRoutePath, inLeafSpec, inParam }) => {
|
|
39
75
|
return {
|
|
40
76
|
path: inRoutePath,
|
|
41
|
-
|
|
42
|
-
|
|
77
|
+
spec: inLeafSpec,
|
|
78
|
+
id: inParam
|
|
43
79
|
};
|
|
44
80
|
};
|
|
45
81
|
|
|
46
|
-
|
|
82
|
+
// 4. Build the callable tree
|
|
83
|
+
const app = apiTree(source, apiPaths, executor);
|
|
47
84
|
|
|
48
|
-
|
|
85
|
+
// 5. Call your generated tree!
|
|
86
|
+
const result = await app.users.profile.fetch("123");
|
|
49
87
|
```
|
|
50
88
|
|
|
51
|
-
|
|
89
|
+
---
|
|
52
90
|
|
|
53
|
-
|
|
91
|
+
## Execution Context
|
|
54
92
|
|
|
55
|
-
|
|
56
|
-
2. **apiPaths** β a flat array of API paths such as `app.users.profile.fetch`.
|
|
57
|
-
3. **executor** β the function that decides what the selected operation actually does.
|
|
93
|
+
When an attached leaf function is invoked, your `executor` receives a single, standardized context object:
|
|
58
94
|
|
|
59
|
-
|
|
95
|
+
| Property | Type | Description |
|
|
96
|
+
| :--- | :--- | :--- |
|
|
97
|
+
| `inRoutePath` | `string` | The full dot-notation route path (e.g. `"app.users.profile.fetch"`). |
|
|
98
|
+
| `inParam` | `any` | The primary argument passed to the leaf method. |
|
|
99
|
+
| `inArgs` | `any[]` | Array of all additional arguments passed beyond `inParam`. |
|
|
100
|
+
| `inLeafSpec` | `object \| undefined` | The resolved leaf definition object found in `source.json`. |
|
|
101
|
+
| `inSource` | `object` | The complete raw `source` schema object. |
|
|
102
|
+
| `inPathSegments` | `string[]` | Array of path segments (e.g. `["app", "users", "profile", "fetch"]`). |
|
|
60
103
|
|
|
61
|
-
|
|
104
|
+
---
|
|
62
105
|
|
|
63
|
-
|
|
106
|
+
## Validation & Guarantees
|
|
64
107
|
|
|
65
|
-
-
|
|
66
|
-
- `apiPaths` is an array.
|
|
67
|
-
- every API path is a string.
|
|
68
|
-
- `executor` is a function.
|
|
108
|
+
`@keshavsoft/api-tree` performs strict pre-flight validation to catch contract misconfigurations early:
|
|
69
109
|
|
|
70
|
-
|
|
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.
|
|
71
113
|
|
|
72
|
-
|
|
114
|
+
---
|
|
73
115
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
## Architecture
|
|
77
|
-
|
|
78
|
-
```text
|
|
79
|
-
Domain repository
|
|
80
|
-
β
|
|
81
|
-
βββ source.json
|
|
82
|
-
βββ api.json
|
|
83
|
-
βββ execution code
|
|
84
|
-
β
|
|
85
|
-
β source + paths + executor
|
|
86
|
-
βΌ
|
|
87
|
-
@keshavsoft/api-tree
|
|
88
|
-
β
|
|
89
|
-
βΌ
|
|
90
|
-
callable runtime API
|
|
91
|
-
```
|
|
116
|
+
## License
|
|
92
117
|
|
|
93
|
-
|
|
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>
|
package/docs/index.html
CHANGED
|
@@ -1,26 +1,95 @@
|
|
|
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</title>
|
|
7
|
-
<
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
</
|
|
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 β The Situation & Solution</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 active">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">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-tree</h1>
|
|
31
|
+
<div class="lead">A small, zero-dependency runtime API-tree builder.</div>
|
|
32
|
+
|
|
33
|
+
<p>We had a situation in our codebase: we were writing function after function just to expose different endpoints.</p>
|
|
34
|
+
|
|
35
|
+
<p>Yet under the hood, every single one of those functions was doing the exact same thing: calling one core execution function. The only difference from endpoint to endpoint was a few variablesβlike a resource name, an action, or a query string.</p>
|
|
36
|
+
|
|
37
|
+
<p>Writing dozens of nearly identical wrapper functions created boilerplate, duplicated code, and made maintenance tedious.</p>
|
|
38
|
+
|
|
39
|
+
<h2>What We Did</h2>
|
|
40
|
+
|
|
41
|
+
<p>We separated the parts that never change from the parts that do:</p>
|
|
42
|
+
|
|
43
|
+
<ul>
|
|
44
|
+
<li><strong>The Single Function (Executor)</strong>: The core execution engine is written exactly once. It handles the network requests, headers, and response parsing.</li>
|
|
45
|
+
<li><strong>The Variables (source.json)</strong>: The parameters that vary per endpoint are moved into a clean JSON contract.</li>
|
|
46
|
+
<li><strong>The Routes (api.json)</strong>: The allowed endpoints are defined in a flat array of dot-notation paths.</li>
|
|
47
|
+
</ul>
|
|
48
|
+
|
|
49
|
+
<p><strong>api-tree</strong> marries the single executor function with the variable JSON, dynamically building the callable dot-notation tree at runtime.</p>
|
|
50
|
+
|
|
51
|
+
<h2>The Code</h2>
|
|
52
|
+
|
|
53
|
+
<p>Instead of writing endless wrapper functions like this:</p>
|
|
54
|
+
|
|
55
|
+
<pre><code>// The repetitive pattern
|
|
56
|
+
export const getUnits = () => execute({ resource: "Unit" });
|
|
57
|
+
export const getLedgers = () => execute({ resource: "Ledger" });
|
|
58
|
+
export const getCompany = () => execute({ resource: "Company" });</code></pre>
|
|
59
|
+
|
|
60
|
+
<p>You write your executor once, put your variables in JSON, and let <code>api-tree</code> generate the surface:</p>
|
|
61
|
+
|
|
62
|
+
<pre><code>import apiTree from "@keshavsoft/api-tree";
|
|
63
|
+
|
|
64
|
+
// 1. Only the variables
|
|
65
|
+
const source = {
|
|
66
|
+
app: {
|
|
67
|
+
masters: {
|
|
68
|
+
unit: { all: { resource: "Unit", action: "fetch" } }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const apiPaths = ["app.masters.unit.all"];
|
|
74
|
+
|
|
75
|
+
// 2. The single executor function
|
|
76
|
+
const execute = async ({ inLeafSpec, inParam }) => {
|
|
77
|
+
return await dispatch(inLeafSpec.resource, inParam);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// 3. Build the callable tree
|
|
81
|
+
const app = apiTree(source, apiPaths, execute);
|
|
82
|
+
|
|
83
|
+
// 4. Call naturally
|
|
84
|
+
const units = await app.masters.unit.all();</code></pre>
|
|
85
|
+
|
|
86
|
+
<p>When you need a new endpoint tomorrow, you do not write a new JavaScript function. You simply add a new entry to your JSON file, and it is immediately available on the tree.</p>
|
|
87
|
+
|
|
88
|
+
<div class="page-nav">
|
|
89
|
+
<span></span>
|
|
90
|
+
<a href="how-it-works.html">Next: How It Works →</a>
|
|
91
|
+
</div>
|
|
92
|
+
</main>
|
|
93
|
+
</div>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|