@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.
Files changed (45) hide show
  1. package/README.md +77 -52
  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 +95 -26
  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 +65 -19
  10. package/src/index.js +2 -2
  11. package/src/v2/blueprint/api.json +4 -0
  12. package/src/v2/blueprint/source.json +22 -0
  13. package/src/v2/index.js +33 -0
  14. package/src/v2/internal-working/route/attachPath.js +38 -0
  15. package/src/v2/internal-working/route/createLeafHandler.js +25 -0
  16. package/src/v2/internal-working/route/index.js +35 -0
  17. package/src/v2/internal-working/route/resolveLeafSpec.js +22 -0
  18. package/src/v2/internal-working/validate/index.js +42 -0
  19. package/src/v2/internal-working/validate/isPlainObject.js +12 -0
  20. package/src/v3/index.js +34 -0
  21. package/src/v3/internal-working/buildTree.js +35 -0
  22. package/src/v4/blueprint/api.json +13 -0
  23. package/src/v4/blueprint/source.json +228 -0
  24. package/src/v4/index.js +29 -0
  25. package/src/v4/internal-working/buildTree.js +35 -0
  26. package/src/v4/internal-working/guards/index.js +15 -0
  27. package/src/v4/internal-working/guards/isFunction.js +9 -0
  28. package/src/v4/internal-working/guards/isObject.js +9 -0
  29. package/src/v4/internal-working/guards/isStringArray.js +15 -0
  30. package/src/v5/blueprint/api.json +13 -0
  31. package/src/v5/blueprint/source.json +228 -0
  32. package/src/v5/engine/guards/index.js +15 -0
  33. package/src/v5/engine/guards/isFunction.js +9 -0
  34. package/src/v5/engine/guards/isObject.js +9 -0
  35. package/src/v5/engine/guards/isStringArray.js +15 -0
  36. package/src/v5/engine/run.js +35 -0
  37. package/src/v5/index.js +29 -0
  38. package/src/v6/blueprint/api.json +13 -0
  39. package/src/v6/blueprint/source.json +228 -0
  40. package/src/v6/engine/guards/index.js +15 -0
  41. package/src/v6/engine/guards/isFunction.js +9 -0
  42. package/src/v6/engine/guards/isObject.js +9 -0
  43. package/src/v6/engine/guards/isStringArray.js +15 -0
  44. package/src/v6/engine/run.js +35 -0
  45. package/src/v6/index.js +29 -0
package/README.md CHANGED
@@ -1,93 +1,118 @@
1
- # @keshavsoft/api-tree
1
+ # api-tree
2
2
 
3
- A small runtime API-tree builder.
3
+ A small, zero-dependency runtime API-tree builder.
4
4
 
5
- `@keshavsoft/api-tree` does one job: it takes **source JSON**, **API paths**, and an **executor**, then returns a callable runtime tree.
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
- API paths
11
- +
12
- executor
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
- ```js
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: "users" }
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
- const executor = async ({ inRoutePath, inParam, inSource }) => {
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
- param: inParam,
42
- spec: inSource.app.users.profile.fetch
77
+ spec: inLeafSpec,
78
+ id: inParam
43
79
  };
44
80
  };
45
81
 
46
- const api = apiTree(source, apiPaths, executor);
82
+ // 4. Build the callable tree
83
+ const app = apiTree(source, apiPaths, executor);
47
84
 
48
- const result = await api.users.profile.fetch("123");
85
+ // 5. Call your generated tree!
86
+ const result = await app.users.profile.fetch("123");
49
87
  ```
50
88
 
51
- ## Contract
89
+ ---
52
90
 
53
- `apiTree(source, apiPaths, executor)` accepts exactly three responsibilities:
91
+ ## Execution Context
54
92
 
55
- 1. **source** β€” the domain/source JSON object.
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
- `@keshavsoft/api-tree` does not know Tally, XML, HTTP, databases, or business rules.
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
- ## Validation
104
+ ---
62
105
 
63
- The public entry point checks:
106
+ ## Validation & Guarantees
64
107
 
65
- - `source` is a JSON-compatible object.
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
- Invalid input produces a clear `TypeError` before the tree is built.
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
- ## Blueprint examples
114
+ ---
73
115
 
74
- The package includes `src/v1/blueprint/source.json` and `src/v1/blueprint/api.json` as small reference examples. They are documentation/blueprint material, not hidden runtime configuration.
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
- The domain repository owns the meaning and execution. `@keshavsoft/api-tree` only creates the navigable runtime surface.
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>
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
- <style>body{max-width:850px;margin:50px auto;padding:0 20px;font:16px/1.6 system-ui,sans-serif}code,pre{background:#f3f3f3;padding:3px 6px;border-radius:4px}pre{padding:16px;overflow:auto}h1{margin-bottom:4px}.muted{color:#666}</style>
8
- </head>
9
- <body>
10
- <h1>api-tree</h1>
11
- <p class="muted">A small runtime API-tree builder.</p>
12
- <h2>The story</h2>
13
- <p>A domain repository already knows its structure and its execution logic. api-tree does not replace that logic. It only turns a flat list of API paths into a callable runtime tree.</p>
14
- <pre>source JSON + API paths + executor
15
- ↓
16
- api-tree
17
- ↓
18
- app.users.profile.fetch()</pre>
19
- <h2>Three inputs</h2>
20
- <ul><li><b>source</b> β€” source/domain JSON</li><li><b>apiPaths</b> β€” flat array of dotted paths</li><li><b>executor</b> β€” external function that performs the operation</li></ul>
21
- <h2>Why separate it?</h2>
22
- <p>The route mechanism is reusable. Tally XML, Tally JSON, Tally Simple, or another domain can keep its own JSON and execution code while sharing the same runtime API-tree mechanism.</p>
23
- <h2>Validation</h2>
24
- <p>The public function rejects invalid source, API-path arrays, and executors with clear TypeErrors.</p>
25
- </body>
26
- </html>
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 &amp; 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 &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 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 &rarr;</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 &rarr;</a>
91
+ </div>
92
+ </main>
93
+ </div>
94
+ </body>
95
+ </html>