@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/docs/style.css ADDED
@@ -0,0 +1,272 @@
1
+ :root {
2
+ --bg: #0d1117;
3
+ --sidebar-bg: #090d13;
4
+ --card-bg: #161b22;
5
+ --border: #30363d;
6
+ --text: #e6edf3;
7
+ --text-muted: #8b949e;
8
+ --accent: #58a6ff;
9
+ --accent-hover: #79c0ff;
10
+ --active-bg: rgba(56, 139, 253, 0.15);
11
+ --code-bg: #161b22;
12
+ --callout-border: #388bfd;
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+
21
+ body {
22
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
23
+ background: var(--bg);
24
+ color: var(--text);
25
+ line-height: 1.7;
26
+ min-height: 100vh;
27
+ }
28
+
29
+ .layout {
30
+ display: flex;
31
+ min-height: 100vh;
32
+ }
33
+
34
+ /* Sidebar Navigation (Left Side) */
35
+ .sidebar {
36
+ width: 250px;
37
+ background: var(--sidebar-bg);
38
+ border-right: 1px solid var(--border);
39
+ padding: 36px 20px 40px;
40
+ flex-shrink: 0;
41
+ position: sticky;
42
+ top: 0;
43
+ height: 100vh;
44
+ overflow-y: auto;
45
+ display: flex;
46
+ flex-direction: column;
47
+ }
48
+
49
+ .sidebar-brand {
50
+ margin-bottom: 32px;
51
+ }
52
+
53
+ .brand-title {
54
+ font-size: 1.25rem;
55
+ font-weight: 700;
56
+ color: #ffffff;
57
+ text-decoration: none;
58
+ display: block;
59
+ letter-spacing: -0.01em;
60
+ }
61
+
62
+ .brand-subtitle {
63
+ font-size: 0.8rem;
64
+ color: var(--text-muted);
65
+ margin-top: 4px;
66
+ }
67
+
68
+ .nav-label {
69
+ font-size: 0.72rem;
70
+ text-transform: uppercase;
71
+ letter-spacing: 0.08em;
72
+ color: var(--text-muted);
73
+ font-weight: 600;
74
+ margin-bottom: 12px;
75
+ }
76
+
77
+ .nav-list {
78
+ list-style: none;
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 4px;
82
+ margin-bottom: auto;
83
+ }
84
+
85
+ .nav-link {
86
+ display: flex;
87
+ align-items: center;
88
+ padding: 8px 12px;
89
+ border-radius: 6px;
90
+ font-size: 0.92rem;
91
+ color: var(--text-muted);
92
+ text-decoration: none;
93
+ transition: all 0.15s ease;
94
+ }
95
+
96
+ .nav-link:hover {
97
+ color: var(--text);
98
+ background: rgba(255, 255, 255, 0.04);
99
+ }
100
+
101
+ .nav-link.active {
102
+ color: var(--accent);
103
+ background: var(--active-bg);
104
+ font-weight: 600;
105
+ }
106
+
107
+ .sidebar-footer {
108
+ padding-top: 24px;
109
+ border-top: 1px solid var(--border);
110
+ font-size: 0.8rem;
111
+ color: var(--text-muted);
112
+ }
113
+
114
+ .sidebar-footer a {
115
+ color: var(--text-muted);
116
+ text-decoration: none;
117
+ }
118
+
119
+ .sidebar-footer a:hover {
120
+ color: var(--accent);
121
+ }
122
+
123
+ /* Main Content Area (Right Side) */
124
+ .content {
125
+ flex: 1;
126
+ max-width: 800px;
127
+ padding: 56px 64px 96px;
128
+ }
129
+
130
+ h1 {
131
+ font-size: 2.2rem;
132
+ font-weight: 700;
133
+ color: #ffffff;
134
+ margin-bottom: 8px;
135
+ letter-spacing: -0.025em;
136
+ }
137
+
138
+ .lead {
139
+ font-size: 1.15rem;
140
+ color: var(--text-muted);
141
+ margin-bottom: 40px;
142
+ }
143
+
144
+ h2 {
145
+ font-size: 1.45rem;
146
+ font-weight: 600;
147
+ color: #ffffff;
148
+ margin: 44px 0 16px;
149
+ letter-spacing: -0.015em;
150
+ }
151
+
152
+ h3 {
153
+ font-size: 1.15rem;
154
+ font-weight: 600;
155
+ color: #ffffff;
156
+ margin: 28px 0 12px;
157
+ }
158
+
159
+ p {
160
+ margin-bottom: 18px;
161
+ color: #c9d1d9;
162
+ font-size: 1.02rem;
163
+ }
164
+
165
+ ul, ol {
166
+ margin: 0 0 24px 24px;
167
+ color: #c9d1d9;
168
+ }
169
+
170
+ li {
171
+ margin-bottom: 10px;
172
+ font-size: 1.02rem;
173
+ }
174
+
175
+ strong {
176
+ color: #ffffff;
177
+ }
178
+
179
+ pre {
180
+ background: var(--card-bg);
181
+ border: 1px solid var(--border);
182
+ border-radius: 6px;
183
+ padding: 18px 22px;
184
+ overflow-x: auto;
185
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
186
+ font-size: 0.9rem;
187
+ line-height: 1.6;
188
+ margin: 20px 0 28px;
189
+ color: #e6edf3;
190
+ }
191
+
192
+ code {
193
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
194
+ font-size: 0.88em;
195
+ background: rgba(110, 118, 129, 0.2);
196
+ padding: 2px 6px;
197
+ border-radius: 4px;
198
+ color: #79c0ff;
199
+ }
200
+
201
+ pre code {
202
+ background: none;
203
+ padding: 0;
204
+ color: inherit;
205
+ }
206
+
207
+
208
+ table {
209
+ width: 100%;
210
+ border-collapse: collapse;
211
+ margin: 24px 0;
212
+ }
213
+
214
+ th, td {
215
+ border: 1px solid var(--border);
216
+ padding: 12px 16px;
217
+ text-align: left;
218
+ font-size: 0.92rem;
219
+ }
220
+
221
+ th {
222
+ background: var(--card-bg);
223
+ color: #ffffff;
224
+ font-weight: 600;
225
+ }
226
+
227
+ td {
228
+ background: rgba(22, 27, 34, 0.4);
229
+ }
230
+
231
+ .page-nav {
232
+ margin-top: 60px;
233
+ padding-top: 24px;
234
+ border-top: 1px solid var(--border);
235
+ display: flex;
236
+ justify-content: space-between;
237
+ }
238
+
239
+ .page-nav a {
240
+ color: var(--accent);
241
+ text-decoration: none;
242
+ font-size: 0.95rem;
243
+ font-weight: 500;
244
+ }
245
+
246
+ .page-nav a:hover {
247
+ color: var(--accent-hover);
248
+ text-decoration: underline;
249
+ }
250
+
251
+ /* Mobile Responsive */
252
+ @media (max-width: 820px) {
253
+ .layout {
254
+ flex-direction: column;
255
+ }
256
+ .sidebar {
257
+ width: 100%;
258
+ height: auto;
259
+ position: static;
260
+ border-right: none;
261
+ border-bottom: 1px solid var(--border);
262
+ padding: 24px 20px;
263
+ }
264
+ .nav-list {
265
+ flex-direction: row;
266
+ flex-wrap: wrap;
267
+ gap: 8px;
268
+ }
269
+ .content {
270
+ padding: 36px 20px 64px;
271
+ }
272
+ }
@@ -0,0 +1,88 @@
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 — Guarantees &amp; Validation</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">Examples</a></li>
22
+ <li><a href="validation.html" class="nav-link active">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>Guarantees &amp; Validation</h1>
31
+ <div class="lead">Fail-fast contract validation and reliability guarantees.</div>
32
+
33
+ <h2>Fail-Fast Philosophy</h2>
34
+ <p><code>@keshavsoft/api-tree</code> strictly enforces contract integrity before building any runtime objects. If any input is malformed, it throws a descriptive <code>TypeError</code> immediately so errors never leak into runtime invocations.</p>
35
+
36
+ <h2>Validation Rules</h2>
37
+
38
+ <h3>1. Source Object</h3>
39
+ <ul>
40
+ <li>Must be a non-null plain JavaScript object.</li>
41
+ <li>Arrays, primitives, and null throw: <code>TypeError: source must be a JSON object.</code></li>
42
+ </ul>
43
+
44
+ <h3>2. API Paths Array</h3>
45
+ <ul>
46
+ <li>Must be a JavaScript Array. Non-arrays throw: <code>TypeError: apiPaths must be an array of strings.</code></li>
47
+ <li>Every element must be a string. Non-string elements throw: <code>TypeError: apiPaths must be an array of strings. apiPaths[i] must be a string.</code></li>
48
+ <li>Empty strings throw: <code>TypeError: apiPaths[i] must not be an empty string.</code></li>
49
+ <li>Paths with empty segments (such as <code>"users..fetch"</code> or <code>"users."</code>) throw: <code>TypeError: apiPaths[i] contains an invalid empty path segment.</code></li>
50
+ </ul>
51
+
52
+ <h3>3. Executor Function</h3>
53
+ <ul>
54
+ <li>Must be a callable function. Non-functions throw: <code>TypeError: executor must be a function.</code></li>
55
+ </ul>
56
+
57
+ <h3>4. Options</h3>
58
+ <ul>
59
+ <li>If provided, options must be a plain object. Passing non-objects throws: <code>TypeError: options must be an object if provided.</code></li>
60
+ </ul>
61
+
62
+ <h2>Test Suite Verification</h2>
63
+ <p>The package is verified using Node's native test runner with 100% passing tests:</p>
64
+ <pre><code>npm test
65
+
66
+ ✔ v2: builds a callable API tree with single root unwrapped
67
+ ✔ v2: automatically preserves multiple root namespaces
68
+ ✔ v2: supports explicit inUnwrapRoot: false option
69
+ ✔ v2: supports multi-argument leaves and inPathSegments
70
+ ✔ v2: supports hybrid branches (callable node with children)
71
+ ✔ v2: rejects empty paths or empty segments
72
+ ✔ v2: rejects invalid source, executor, and options
73
+ ✔ builds a callable API tree
74
+ ✔ supports multi-argument leaves
75
+ ✔ supports named-object signature
76
+ ✔ rejects a non-object source
77
+ ✔ rejects non-array api paths
78
+ ✔ rejects non-string api paths
79
+ ✔ rejects a non-function executor</code></pre>
80
+
81
+ <div class="page-nav">
82
+ <a href="examples.html">&larr; Examples</a>
83
+ <a href="index.html">Back to The Story &rarr;</a>
84
+ </div>
85
+ </main>
86
+ </div>
87
+ </body>
88
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keshavsoft/api-tree",
3
- "version": "1.0.0",
3
+ "version": "6.1.0",
4
4
  "description": "Build a callable runtime API tree from API paths, source JSON, and an external executor.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -20,7 +20,7 @@
20
20
  "LICENSE"
21
21
  ],
22
22
  "scripts": {
23
- "test": "node --test test/test.js",
23
+ "test": "node --test test/*.test.js",
24
24
  "prepublishOnly": "npm test"
25
25
  },
26
26
  "repository": {
@@ -40,5 +40,9 @@
40
40
  "license": "MIT",
41
41
  "engines": {
42
42
  "node": ">=20.10"
43
+ },
44
+ "devDependencies": {
45
+ "fast-xml-parser": "^5.11.2",
46
+ "tally-spec": "^1.0.0"
43
47
  }
44
- }
48
+ }
package/src/index.d.ts CHANGED
@@ -1,19 +1,65 @@
1
- export type ApiExecutor<TSource = Record<string, unknown>, TResult = unknown, TParam = unknown> = (input: {
2
- inRoutePath: string;
3
- inParam: TParam;
4
- inSource: TSource;
5
- }) => TResult | Promise<TResult>;
6
-
7
- export type ApiPaths = string[];
8
-
9
- export default function start<TSource = Record<string, unknown>, TResult = unknown, TParam = unknown>(
10
- source: TSource,
11
- apiPaths: ApiPaths,
12
- executor: ApiExecutor<TSource, TResult, TParam>
13
- ): Record<string, unknown>;
14
-
15
- export function start<TSource = Record<string, unknown>, TResult = unknown, TParam = unknown>(
16
- source: TSource,
17
- apiPaths: ApiPaths,
18
- executor: ApiExecutor<TSource, TResult, TParam>
19
- ): Record<string, unknown>;
1
+ /**
2
+ * Context passed to the executor function when a leaf method is invoked.
3
+ */
4
+ export interface ApiTreeExecutionContext<TSource = Record<string, any>> {
5
+ /** The full dot-notation path of the invoked leaf (e.g. "app.users.profile.fetch") */
6
+ inRoutePath: string;
7
+ /** The first argument passed to the leaf method */
8
+ inParam?: any;
9
+ /** Array of all remaining arguments passed to the leaf method */
10
+ inArgs: any[];
11
+ /** The complete source JSON object */
12
+ inSource: TSource;
13
+ }
14
+
15
+ /**
16
+ * An executor function responsible for handling the execution of an invoked leaf.
17
+ */
18
+ export type ApiTreeExecutor<TSource = Record<string, any>, TReturn = any> = (
19
+ context: ApiTreeExecutionContext<TSource>
20
+ ) => Promise<TReturn> | TReturn;
21
+
22
+ /**
23
+ * Options for customizing api-tree building behavior.
24
+ */
25
+ export interface ApiTreeOptions {
26
+ /**
27
+ * Whether to unwrap a single shared root namespace.
28
+ * Default: true when all paths share one root namespace; false when paths have different roots.
29
+ */
30
+ inUnwrapRoot?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Named argument object for apiTree.
35
+ */
36
+ export interface ApiTreeParams<TSource = Record<string, any>, TReturn = any> {
37
+ inSource: TSource;
38
+ inApiPaths: string[];
39
+ inExecutor: ApiTreeExecutor<TSource, TReturn>;
40
+ inOptions?: ApiTreeOptions;
41
+ }
42
+
43
+ /**
44
+ * Builds and returns a callable runtime API tree from source JSON, API paths, and an executor.
45
+ *
46
+ * @param source Source JSON object containing schema definitions
47
+ * @param apiPaths Array of dot-separated route paths (e.g. ["app.users.fetch"])
48
+ * @param executor Execution function invoked when a leaf is called
49
+ * @param options Optional configuration
50
+ */
51
+ export declare function apiTree<TSource = Record<string, any>, TReturn = any>(
52
+ source: TSource,
53
+ apiPaths: string[],
54
+ executor: ApiTreeExecutor<TSource, TReturn>,
55
+ options?: ApiTreeOptions
56
+ ): Record<string, any>;
57
+
58
+ /**
59
+ * Named argument signature for apiTree.
60
+ */
61
+ export declare function apiTree<TSource = Record<string, any>, TReturn = any>(
62
+ params: ApiTreeParams<TSource, TReturn>
63
+ ): Record<string, any>;
64
+
65
+ export default apiTree;
package/src/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { default } from "./v1/index.js";
2
- export * from "./v1/index.js";
1
+ export { default } from "./v6/index.js";
2
+ export * from "./v6/index.js";
@@ -0,0 +1,4 @@
1
+ [
2
+ "app.users.profile.fetch",
3
+ "app.reports.summary.fetch"
4
+ ]
@@ -0,0 +1,22 @@
1
+ {
2
+ "app": {
3
+ "users": {
4
+ "profile": {
5
+ "fetch": {
6
+ "action": "fetch",
7
+ "resource": "User",
8
+ "description": "Fetch user profile details"
9
+ }
10
+ }
11
+ },
12
+ "reports": {
13
+ "summary": {
14
+ "fetch": {
15
+ "action": "fetch",
16
+ "resource": "Report",
17
+ "description": "Fetch summary report"
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,33 @@
1
+ import validate from "./internal-working/validate/index.js";
2
+ import routeStart from "./internal-working/route/index.js";
3
+
4
+ const start = (inSource, inApiPaths, inExecutor, inOptions) => {
5
+ let source = inSource;
6
+ let paths = inApiPaths;
7
+ let executor = inExecutor;
8
+ let options = inOptions;
9
+
10
+ if (inSource && typeof inSource === "object" && "inSource" in inSource) {
11
+ source = inSource.inSource;
12
+ paths = inSource.inApiPaths;
13
+ executor = inSource.inExecutor;
14
+ options = inSource.inOptions;
15
+ }
16
+
17
+ validate({
18
+ inSource: source,
19
+ inApiPaths: paths,
20
+ inExecutor: executor,
21
+ inOptions: options
22
+ });
23
+
24
+ return routeStart({
25
+ inApiPaths: paths,
26
+ inSource: source,
27
+ inExecutor: executor,
28
+ inOptions: options
29
+ });
30
+ };
31
+
32
+ export default start;
33
+ export { start, start as apiTree };
@@ -0,0 +1,38 @@
1
+ import createLeafHandler from "./createLeafHandler.js";
2
+ import resolveLeafSpec from "./resolveLeafSpec.js";
3
+
4
+ const startFunc = ({ inTree, inPath, inSource, inExecutor }) => {
5
+ const localTree = inTree;
6
+ const localPath = inPath;
7
+ const localSource = inSource;
8
+ const localExecutor = inExecutor;
9
+
10
+ const parts = localPath.split(".");
11
+ const leafName = parts.pop();
12
+
13
+ let branch = localTree;
14
+ for (const segment of parts) {
15
+ branch[segment] ??= {};
16
+ branch = branch[segment];
17
+ }
18
+
19
+ const leafSpec = resolveLeafSpec({
20
+ inSource: localSource,
21
+ inPath: localPath
22
+ });
23
+
24
+ const handler = createLeafHandler({
25
+ inPath: localPath,
26
+ inSource: localSource,
27
+ inExecutor: localExecutor,
28
+ inLeafSpec: leafSpec
29
+ });
30
+
31
+ if (branch[leafName] && typeof branch[leafName] === "object") {
32
+ Object.assign(handler, branch[leafName]);
33
+ }
34
+
35
+ branch[leafName] = handler;
36
+ };
37
+
38
+ export default startFunc;
@@ -0,0 +1,25 @@
1
+ const startFunc = ({ inPath, inSource, inExecutor, inLeafSpec }) => {
2
+ const localPath = inPath;
3
+ const localSource = inSource;
4
+ const localExecutor = inExecutor;
5
+ const localLeafSpec = inLeafSpec;
6
+ const localPathSegments = localPath.split(".");
7
+
8
+ const handler = async (inParam, ...inArgs) => {
9
+ const localParam = inParam;
10
+ const localArgs = inArgs;
11
+
12
+ return await localExecutor({
13
+ inRoutePath: localPath,
14
+ inParam: localParam,
15
+ inArgs: localArgs,
16
+ inSource: localSource,
17
+ inLeafSpec: localLeafSpec,
18
+ inPathSegments: localPathSegments
19
+ });
20
+ };
21
+
22
+ return handler;
23
+ };
24
+
25
+ export default startFunc;
@@ -0,0 +1,35 @@
1
+ import attachPath from "./attachPath.js";
2
+
3
+ const startFunc = ({ inApiPaths, inSource, inExecutor, inOptions }) => {
4
+ const localApiPaths = inApiPaths;
5
+ const localSource = inSource;
6
+ const localExecutor = inExecutor;
7
+ const localOptions = inOptions ?? {};
8
+
9
+ const tree = {};
10
+
11
+ for (const path of localApiPaths) {
12
+ attachPath({
13
+ inTree: tree,
14
+ inPath: path,
15
+ inSource: localSource,
16
+ inExecutor: localExecutor
17
+ });
18
+ }
19
+
20
+ if (localApiPaths.length === 0) {
21
+ return tree;
22
+ }
23
+
24
+ const rootSegments = new Set(localApiPaths.map((path) => path.split(".")[0]));
25
+
26
+ if (rootSegments.size === 1) {
27
+ const sharedRoot = rootSegments.values().next().value;
28
+ const unwrap = localOptions.inUnwrapRoot !== false;
29
+ return unwrap && tree[sharedRoot] ? tree[sharedRoot] : tree;
30
+ }
31
+
32
+ return tree;
33
+ };
34
+
35
+ export default startFunc;
@@ -0,0 +1,22 @@
1
+ const startFunc = ({ inSource, inPath }) => {
2
+ const localSource = inSource;
3
+ const localPath = inPath;
4
+
5
+ if (!localSource || typeof localSource !== "object" || !localPath) {
6
+ return undefined;
7
+ }
8
+
9
+ const segments = localPath.split(".");
10
+ let current = localSource;
11
+
12
+ for (const segment of segments) {
13
+ if (current === null || typeof current !== "object" || !(segment in current)) {
14
+ return undefined;
15
+ }
16
+ current = current[segment];
17
+ }
18
+
19
+ return current;
20
+ };
21
+
22
+ export default startFunc;