@powerduck/tree 0.7.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 powerduck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,556 @@
1
+ # @powerduck/tree
2
+
3
+ Extensible tree component for API navigation, schema exploration, and documentation. Built on a generic core with adapters for OpenAPI, JSON Schema, and custom data sources.
4
+
5
+ [Website](https://www.powerduck.com) · [Documentation](https://www.powerduck.com/docs/tree) · [GitHub](https://github.com/powerducklab/tree) · [npm](https://www.npmjs.com/package/@powerduck/tree)
6
+
7
+ ## Features
8
+
9
+ - **Generic core** — `TreeNode<TMetadata>` works with any data source
10
+ - **OpenAPI adapter** — tag-based navigation with OAS 3.2 native parent nesting, `x-tagGroups`, `x-order`, `x-displayName`, `x-internal`
11
+ - **JSON Schema adapter** — deep expansion of nested properties, arrays, combinators with `jsonPath` metadata for editor line jumping
12
+ - **Doc adapter** — Stripe-style documentation tree with operation count badges, parameters, request body, and responses
13
+ - **React component** — search, expand/collapse toggle (Material Design unfold icons), keyboard navigation, method badges, custom render props, `searchRowExtra` for inline toolbar extensions
14
+ - **Drag and drop** — reorder within the same parent **and cross-level move into folders**, with three-zone drop detection, `canDrag`/`canDrop` predicates, `dragGroupKey` isolation, `onReorder`/`onMove` callbacks, and circular reference prevention
15
+ - **Context menu** — right-click or the more (…) button for per-node actions, with custom item definitions, icons, separators, danger styling, and two-step confirm for destructive actions
16
+ - **Imperative locate** — `locateNode(predicate)` finds a node, expands all ancestors, selects it, and scrolls into view (ideal for "jump to API" features)
17
+ - **JSON Patch integration** — `onPatch` callback emits RFC 6902 operations for reorder/move/delete, compatible with [`@powerduck/conf-patch`](https://www.npmjs.com/package/@powerduck/conf-patch) for applying to the original document
18
+ - **High-quality icons** — powered by [react-icons](https://react-icons.github.io/react-icons/) (Lucide + Material Design icon sets)
19
+ - **CSS variable theming** — compatible with powerduck `tokens.css`, light/dark mode
20
+ - **Zero hard dependencies** — core and adapters have no runtime dependencies; React layer requires `react`, `react-dom`, and `react-icons`
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @powerduck/tree
26
+ ```
27
+
28
+ ### Peer Dependencies
29
+
30
+ ```bash
31
+ npm install react react-dom react-icons
32
+ ```
33
+
34
+ ### CSS Import
35
+
36
+ Import the component styles in your app entry point:
37
+
38
+ ```ts
39
+ import "@powerduck/tree/react/index.css";
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ### API Debug Tree
45
+
46
+ ```tsx
47
+ import { useMemo } from "react";
48
+ import { buildOpenApiTree } from "@powerduck/tree";
49
+ import { Tree } from "@powerduck/tree/react";
50
+
51
+ function ApiNav({ document }) {
52
+ const { root } = useMemo(() => buildOpenApiTree(document), [document]);
53
+
54
+ return (
55
+ <div style={{ width: 320, height: "100%" }}>
56
+ <Tree
57
+ nodes={root.children ?? []}
58
+ onSelect={(node) => console.log(node.metadata?.method, node.metadata?.path)}
59
+ searchable
60
+ showExpandAll
61
+ defaultExpandDepth={2}
62
+ />
63
+ </div>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ### Schema Navigation Tree
69
+
70
+ ```tsx
71
+ import { useMemo } from "react";
72
+ import { buildSchemaTree } from "@powerduck/tree";
73
+ import { Tree } from "@powerduck/tree/react";
74
+
75
+ function SchemaNav({ schema, editorRef }) {
76
+ const { root } = useMemo(() => buildSchemaTree(schema), [schema]);
77
+
78
+ const handleSelect = (node) => {
79
+ const jsonPath = node.metadata?.jsonPath;
80
+ if (jsonPath) {
81
+ editorRef.current?.revealLine(jsonPath);
82
+ }
83
+ };
84
+
85
+ return (
86
+ <Tree
87
+ nodes={root.children ?? []}
88
+ onSelect={handleSelect}
89
+ searchable
90
+ defaultExpandDepth={3}
91
+ />
92
+ );
93
+ }
94
+ ```
95
+
96
+ ### Custom Data Source
97
+
98
+ ```tsx
99
+ import { Tree } from "@powerduck/tree/react";
100
+ import type { TreeNode } from "@powerduck/tree/core";
101
+
102
+ interface FileMeta {
103
+ type: "folder" | "file";
104
+ size?: number;
105
+ }
106
+
107
+ const files: TreeNode<FileMeta>[] = [
108
+ {
109
+ id: "src",
110
+ name: "src",
111
+ metadata: { type: "folder" },
112
+ children: [
113
+ { id: "src/index.ts", name: "index.ts", metadata: { type: "file", size: 512 } },
114
+ ],
115
+ },
116
+ ];
117
+
118
+ function FileTree() {
119
+ return <Tree nodes={files} searchable defaultExpandDepth={1} />;
120
+ }
121
+ ```
122
+
123
+ ## Drag and Drop
124
+
125
+ Enable drag and drop with the `draggable` prop. Nodes can be reordered within the same parent **or moved into a different parent** (cross-level) by dragging the grip handle.
126
+
127
+ ### Reordering (same level)
128
+
129
+ ```tsx
130
+ import { useState } from "react";
131
+ import { Tree } from "@powerduck/tree/react";
132
+ import type { ReorderResult } from "@powerduck/tree/core";
133
+
134
+ function SortableTree({ initialNodes }) {
135
+ const [nodes, setNodes] = useState(initialNodes);
136
+
137
+ const handleReorder = (result: ReorderResult) => {
138
+ console.log(`Moved "${result.moved.name}" from index ${result.fromIndex} to ${result.toIndex}`);
139
+ setNodes(result.nodes);
140
+ };
141
+
142
+ return (
143
+ <Tree
144
+ nodes={nodes}
145
+ draggable
146
+ onReorder={handleReorder}
147
+ />
148
+ );
149
+ }
150
+ ```
151
+
152
+ ### Cross-level move (drag into folder)
153
+
154
+ When dragging over a branch node, the middle 50% of the row highlights the node as a drop target (folder). Dropping there moves the node to become a child of that folder.
155
+
156
+ ```tsx
157
+ import { useState } from "react";
158
+ import { Tree } from "@powerduck/tree/react";
159
+ import type { TreeNode } from "@powerduck/tree/core";
160
+
161
+ function MoveableTree({ initialNodes }) {
162
+ const [nodes, setNodes] = useState(initialNodes);
163
+
164
+ const handleMove = (newNodes: TreeNode[], movedNode, targetParentId) => {
165
+ console.log(`Moved "${movedNode.name}" into folder "${targetParentId}"`);
166
+ setNodes(newNodes);
167
+ };
168
+
169
+ return (
170
+ <Tree
171
+ nodes={nodes}
172
+ draggable
173
+ onReorder={(result) => setNodes(result.nodes)}
174
+ onMove={handleMove}
175
+ />
176
+ );
177
+ }
178
+ ```
179
+
180
+ Drop zones on a branch node:
181
+ - **Top 25%** — insert before (reorder)
182
+ - **Middle 50%** — move into this folder (cross-level)
183
+ - **Bottom 25%** — insert after (reorder)
184
+
185
+ Circular references are automatically prevented: a node cannot be dropped into its own descendant.
186
+
187
+ ### Control which nodes can be dragged
188
+
189
+ ```tsx
190
+ <Tree
191
+ nodes={nodes}
192
+ draggable
193
+ canDrag={(node) => node.metadata?.type !== "folder"}
194
+ onReorder={handleReorder}
195
+ />
196
+ ```
197
+
198
+ ### Control where nodes can be dropped
199
+
200
+ ```tsx
201
+ <Tree
202
+ nodes={nodes}
203
+ draggable
204
+ canDrop={(dragged, target, position) => {
205
+ /* position is "before" | "after" | "child" */
206
+ return position !== "child" || target.metadata?.type === "folder";
207
+ }}
208
+ onReorder={handleReorder}
209
+ onMove={(nodes) => setNodes(nodes)}
210
+ />
211
+ ```
212
+
213
+ ## Imperative Handle
214
+
215
+ Access tree methods via `ref`:
216
+
217
+ ```tsx
218
+ import { useRef } from "react";
219
+ import { Tree } from "@powerduck/tree/react";
220
+ import type { TreeHandle } from "@powerduck/tree/react";
221
+
222
+ function TreeWithLocate() {
223
+ const treeRef = useRef<TreeHandle>(null);
224
+
225
+ const locateByOperationId = (operationId: string) => {
226
+ const found = treeRef.current?.locateNode((node) =>
227
+ node.metadata?.operationId === operationId
228
+ );
229
+ /* found is the matched TreeNode, or undefined if not found */
230
+ };
231
+
232
+ return (
233
+ <>
234
+ <button onClick={() => locateByOperationId("getPet")}>Locate getPet</button>
235
+ <Tree ref={treeRef} nodes={nodes} />
236
+ </>
237
+ );
238
+ }
239
+ ```
240
+
241
+ ### Handle methods
242
+
243
+ | Method | Description |
244
+ | --- | --- |
245
+ | `expandAll()` | Expand all nodes |
246
+ | `collapseAll()` | Collapse all nodes |
247
+ | `expandToDepth(depth)` | Expand nodes to a specific depth |
248
+ | `getExpandedIds()` | Get currently expanded node IDs |
249
+ | `getSelectedNode()` | Get the currently selected node |
250
+ | `scrollToNode(id)` | Scroll a node into view by ID |
251
+ | `locateNode(predicate)` | Find a node by predicate, expand ancestors, select, and scroll into view |
252
+
253
+ ## Context Menu
254
+
255
+ Enable per-node actions via right-click or the more (…) button that appears on row hover. Define menu items with the `contextMenuItems` prop.
256
+
257
+ ```tsx
258
+ import { LuCopy, LuTrash2 } from "react-icons/lu";
259
+ import { Tree } from "@powerduck/tree/react";
260
+ import type { ContextMenuItem } from "@powerduck/tree/react";
261
+
262
+ function ApiTree({ nodes }) {
263
+ const buildMenu = (node): ContextMenuItem[] => [
264
+ {
265
+ label: "Copy operationId",
266
+ icon: <LuCopy size={14} />,
267
+ onClick: (n) => navigator.clipboard.writeText(n.metadata?.operationId ?? ""),
268
+ },
269
+ { separator: true, label: "", onClick: () => undefined },
270
+ {
271
+ label: "Delete",
272
+ icon: <LuTrash2 size={14} />,
273
+ danger: true,
274
+ onClick: (n) => console.log("Delete", n.id),
275
+ },
276
+ ];
277
+
278
+ return <Tree nodes={nodes} contextMenuItems={buildMenu} />;
279
+ }
280
+ ```
281
+
282
+ ### ContextMenuItem properties
283
+
284
+ | Property | Type | Description |
285
+ | --- | --- | --- |
286
+ | `label` | `string` | Display text |
287
+ | `icon` | `ReactNode` | Optional icon before label |
288
+ | `onClick` | `(node) => void` | Click handler |
289
+ | `disabled` | `boolean` | Disable the item |
290
+ | `danger` | `boolean` | Red text for destructive actions |
291
+ | `separator` | `boolean` | Render a horizontal line above this item |
292
+
293
+ ## JSON Patch & conf-patch Integration
294
+
295
+ When nodes carry a `jsonPath` in their metadata, the tree emits RFC 6902 JSON Patch operations via the `onPatch` callback. Apply these to the original document with [`@powerduck/conf-patch`](https://www.npmjs.com/package/@powerduck/conf-patch).
296
+
297
+ ```tsx
298
+ import { patchContent } from "@powerduck/conf-patch/core";
299
+ import { Tree } from "@powerduck/tree/react";
300
+ import type { JsonPatchOp } from "@powerduck/tree/react";
301
+
302
+ function EditableTree({ initialDocument, format }) {
303
+ const [document, setDocument] = useState(initialDocument);
304
+
305
+ const handlePatch = (ops: JsonPatchOp[]) => {
306
+ /* Apply patch operations to the document string. */
307
+ const updated = patchContent(document, ops, format);
308
+ setDocument(updated);
309
+ };
310
+
311
+ return (
312
+ <Tree
313
+ nodes={nodes}
314
+ draggable
315
+ onReorder={(result) => setNodes(result.nodes)}
316
+ onPatch={handlePatch}
317
+ />
318
+ );
319
+ }
320
+ ```
321
+
322
+ Patch operations are currently emitted for **reorder** (array `move`). Nodes must have `metadata.jsonPath` as an array of path segments for patch generation.
323
+
324
+ ## API Reference
325
+
326
+ ### Core
327
+
328
+ #### `TreeNode<TMetadata>`
329
+
330
+ ```ts
331
+ interface TreeNode<TMetadata = unknown> {
332
+ id: string;
333
+ name: string;
334
+ order?: number;
335
+ children?: TreeNode<TMetadata>[];
336
+ metadata?: TMetadata;
337
+ }
338
+ ```
339
+
340
+ #### Tree Utilities
341
+
342
+ | Function | Description |
343
+ |----------|-------------|
344
+ | `findNode(nodes, id)` | Find a node by ID |
345
+ | `findPath(nodes, id)` | Find full path from root to node |
346
+ | `getLeaves(nodes)` | Get all leaf nodes |
347
+ | `countNodes(nodes)` | Count all nodes |
348
+ | `getMaxDepth(nodes)` | Get maximum tree depth |
349
+ | `sortNodes(nodes)` | Sort by order, then name |
350
+ | `sortTree(nodes)` | Recursively sort all levels |
351
+ | `filterTree(nodes, { query })` | Filter by search query |
352
+ | `getExpandableIds(nodes, { maxDepth })` | Get IDs to expand |
353
+ | `getBranchIds(nodes)` | Get all branch node IDs |
354
+ | `flattenTree(nodes)` | Flatten to pre-order list with depth |
355
+ | `updateNode(nodes, id, updater)` | Immutable node update |
356
+ | `removeNode(nodes, id)` | Immutable node removal |
357
+ | `insertChild(nodes, parentId, child)` | Immutable child insertion |
358
+
359
+ ### OpenAPI Adapter
360
+
361
+ #### `buildOpenApiTree(document, options?)`
362
+
363
+ ```ts
364
+ interface OpenApiTreeOptions {
365
+ showInternal?: boolean; // default false
366
+ showComponents?: boolean; // default true
367
+ showWebhooks?: boolean; // default true
368
+ defaultExpandDepth?: number; // default 2
369
+ }
370
+ ```
371
+
372
+ **Navigation strategy (priority order):**
373
+ 1. OAS 3.2 native `parent`-nested tags
374
+ 2. `x-tagGroups` (with powerduck nested `groups` extension)
375
+ 3. Flat tag grouping (fallback)
376
+
377
+ **Supported extensions:**
378
+ - `x-order` — sort tags and operations
379
+ - `x-displayName` — human-friendly tag label
380
+ - `x-internal` / `x-scalar-ignore` — hide operations
381
+ - `x-tagGroups` — group tags into navigation folders
382
+
383
+ ### JSON Schema Adapter
384
+
385
+ #### `buildSchemaTree(schema, options?)`
386
+
387
+ ```ts
388
+ interface SchemaTreeOptions {
389
+ maxDepth?: number; // default 8
390
+ showDeprecated?: boolean; // default true
391
+ expandCombinators?: boolean; // default true
392
+ showArrayItems?: boolean; // default true
393
+ }
394
+ ```
395
+
396
+ #### `findSchemaNodeByPath(root, jsonPath)`
397
+
398
+ Find a schema tree node by its JSON path.
399
+
400
+ #### `formatSchemaType(metadata)`
401
+
402
+ Format a schema type label for display (e.g. `"string (email)"`, `"string | null"`).
403
+
404
+ ### Doc Adapter
405
+
406
+ #### `buildDocTree(document, options?)`
407
+
408
+ ```ts
409
+ interface DocTreeOptions {
410
+ showInternal?: boolean; // default false
411
+ showComponents?: boolean; // default true
412
+ showWebhooks?: boolean; // default true
413
+ expandOperationDetails?: boolean; // default true
414
+ }
415
+ ```
416
+
417
+ Builds a Stripe-style documentation tree where each operation expands to show parameters, request body, and responses.
418
+
419
+ ### React Component
420
+
421
+ #### `<Tree />` Props
422
+
423
+ | Prop | Type | Default | Description |
424
+ |------|------|---------|-------------|
425
+ | `nodes` | `TreeNode[]` | required | Root nodes to render |
426
+ | `onSelect` | `(node) => void` | — | Called when a leaf node is clicked |
427
+ | `onExpandedChange` | `(ids) => void` | — | Called when expanded nodes change |
428
+ | `defaultExpandedIds` | `string[]` | — | Initially expanded node IDs |
429
+ | `defaultExpandDepth` | `number` | `1` | Default expansion depth |
430
+ | `searchable` | `boolean` | `false` | Show search input |
431
+ | `searchPlaceholder` | `string` | `"Search..."` | Search input placeholder |
432
+ | `searchRowExtra` | `ReactNode` | — | Extra content inside the search row (between input and action buttons) |
433
+ | `showExpandAll` | `boolean` | `false` | Show expand/collapse all toggle button (single toggle, Material Design unfold icons) |
434
+ | `showRefresh` | `boolean` | `false` | Show refresh button |
435
+ | `onRefresh` | `() => void` | — | Called when refresh button is clicked |
436
+ | `toolbar` | `ReactNode` | — | Custom toolbar content rendered above the tree |
437
+ | `renderNode` | `(ctx, defaultNode) => ReactNode` | — | Fully custom node renderer |
438
+ | `renderIcon` | `(ctx) => ReactNode` | — | Custom icon renderer |
439
+ | `renderLabel` | `(ctx) => ReactNode` | — | Custom label renderer |
440
+ | `renderSuffix` | `(ctx) => ReactNode` | — | Custom suffix (badges, count, etc.) |
441
+ | `className` | `string` | — | Additional CSS class |
442
+ | `style` | `CSSProperties` | — | Inline styles |
443
+ | `size` | `"xs" \| "sm" \| "md"` | `"sm"` | Size variant |
444
+ | `showIndentGuides` | `boolean` | `true` | Show indent guide lines |
445
+ | `maxHeight` | `number \| string` | — | Max height before scrolling |
446
+ | `draggable` | `boolean` | `false` | Enable drag and drop reordering |
447
+ | `dragGroupKey` | `string` | — | Metadata key for drag isolation (nodes with different group values cannot intermix) |
448
+ | `onReorder` | `(result: ReorderResult) => void` | — | Called when a node is reordered within the same parent |
449
+ | `onMove` | `(nodes, movedNode, targetParentId) => void` | — | Called when a node is moved to a different parent |
450
+ | `canDrag` | `(node) => boolean` | — | Predicate to control which nodes can be dragged |
451
+ | `canDrop` | `(dragged, target, position) => boolean` | — | Predicate to control allowed drop targets |
452
+ | `contextMenuItems` | `(node) => ContextMenuItem[]` | — | Right-click / more-button context menu items |
453
+ | `onPatch` | `(ops, context) => void` | — | Called with RFC 6902 JSON Patch ops on reorder (for conf-patch integration) |
454
+
455
+ #### Tree Handle (via ref)
456
+
457
+ ```ts
458
+ interface TreeHandle {
459
+ expandAll: () => void;
460
+ collapseAll: () => void;
461
+ expandToDepth: (depth: number) => void;
462
+ getExpandedIds: () => string[];
463
+ getSelectedNode: () => TreeNode | undefined;
464
+ scrollToNode: (id: string) => void;
465
+ }
466
+ ```
467
+
468
+ ## Architecture
469
+
470
+ ```
471
+ @powerduck/tree
472
+ ├── core/ Generic tree types and utilities (zero dependencies)
473
+ ├── adapters/ Data source adapters
474
+ │ ├── openapi.ts OpenAPI document → navigation tree
475
+ │ ├── json-schema.ts JSON Schema → deep exploration tree
476
+ │ └── doc.ts OpenAPI → Stripe-style documentation tree
477
+ ├── react/ React Tree component
478
+ │ ├── Tree.tsx Main component
479
+ │ ├── Tree.module.css Styles (CSS variables)
480
+ │ ├── hooks/ useTreeSearch, useTreeExpansion
481
+ │ └── libs/ Types and utilities
482
+ └── index.ts Main entry (core + adapters)
483
+ ```
484
+
485
+ ### Entry Points
486
+
487
+ | Import | Description |
488
+ |--------|-------------|
489
+ | `@powerduck/tree` | Core types, utilities, and all adapters |
490
+ | `@powerduck/tree/react` | React Tree component and hooks |
491
+ | `@powerduck/tree/adapters` | OpenAPI, JSON Schema, and Doc adapters |
492
+ | `@powerduck/tree/core` | Core types and utilities only |
493
+
494
+ ## Theming
495
+
496
+ The component uses CSS variables that align with powerduck's `tokens.css`. Override them in your application:
497
+
498
+ ```css
499
+ :root {
500
+ --color-surface: #ffffff;
501
+ --color-text-primary: #26282b;
502
+ --color-accent: #f28c28;
503
+ /* ... see tokens.css for full list */
504
+ }
505
+
506
+ :root[data-theme="dark"] {
507
+ --color-surface: #1f2125;
508
+ --color-text-primary: #edf0f3;
509
+ /* ... */
510
+ }
511
+ ```
512
+
513
+ ## OAS 3.2 Nested Tags
514
+
515
+ OAS 3.2 supports native tag nesting via the `parent` field:
516
+
517
+ ```yaml
518
+ tags:
519
+ - name: Account
520
+ - name: Users
521
+ parent: Account
522
+ - name: Admin
523
+ parent: Account
524
+ - name: Roles
525
+ parent: Admin
526
+ ```
527
+
528
+ The adapter automatically detects and resolves nested tags, with circular reference detection and unknown parent warnings.
529
+
530
+ ## x-tagGroups with Nested Folders
531
+
532
+ The powerduck extension supports nested groups via the `groups` field:
533
+
534
+ ```yaml
535
+ x-tagGroups:
536
+ - name: Account
537
+ tags: [Users]
538
+ groups:
539
+ - name: Admin
540
+ tags: [Roles, Permissions]
541
+ ```
542
+
543
+ Redoc and Scalar safely ignore the unknown `groups` field.
544
+
545
+ ## Development
546
+
547
+ ```bash
548
+ npm install
549
+ npm run build # Build with tsup
550
+ npm run test # Run tests with vitest
551
+ npm run typecheck # Type check with tsc
552
+ ```
553
+
554
+ ## License
555
+
556
+ MIT
@@ -0,0 +1 @@
1
+ "use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,r=Object.prototype.hasOwnProperty,a={};function s(e){return[...e].sort((e,t)=>{const n=e.order??Number.MAX_SAFE_INTEGER,o=t.order??Number.MAX_SAFE_INTEGER;if(n!==o)return n-o;return e.name.localeCompare(t.name,"en-US",{numeric:!0,sensitivity:"base"})||e.id.localeCompare(t.id)})}((e,n)=>{for(var o in n)t(e,o,{get:n[o],enumerable:!0})})(a,{buildDocTree:()=>z,buildOpenApiTree:()=>y,buildSchemaTree:()=>v,findSchemaNodeByPath:()=>N,formatSchemaType:()=>C}),module.exports=(e=a,((e,a,s,i)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of o(a))r.call(e,c)||c===s||t(e,c,{get:()=>a[c],enumerable:!(i=n(a,c))||i.enumerable});return e})(t({},"__esModule",{value:!0}),e));var i=["get","put","post","delete","options","head","patch","trace"],c={showInternal:!1,showComponents:!0,showWebhooks:!0,defaultExpandDepth:2};function d(e){return"object"!=typeof e||null===e||Array.isArray(e)?null:e}function p(e){return"string"==typeof e&&e.trim().length>0?e.trim():void 0}function u(e){return"boolean"==typeof e?e:void 0}function m(e){return Array.isArray(e)?e:void 0}function f(e,t){return!t&&(!0===u(e["x-scalar-ignore"])||!0===u(e["x-internal"]))}function h(e){return"number"==typeof(t=e["x-order"])&&Number.isFinite(t)?t:void 0;var t}function l(e){return p(e["x-displayName"])}function g(e,t,n,o){const r=new Map;for(const t of e)r.set(t.name,t);const a=new Map;for(const t of e){if(!t.parent){a.set(t.name,null);continue}if(!r.has(t.parent)){o.push(`Tag "${t.name}" references unknown parent "${t.parent}", treating as root.`),a.set(t.name,null);continue}const e=[t.name];let n=t.parent,s=!1;for(;n;){if(e.includes(n)){s=!0,o.push(`Circular tag reference detected: ${e.join(" -> ")} -> ${n}. Breaking cycle.`);break}e.push(n),n=r.get(n)?.parent}a.set(t.name,s?null:t.parent)}const i=new Map;for(const e of t){if(f(e.raw,n.showInternal))continue;const t=e.tags.length>0?e.tags:["Other"];for(const n of t){const t=i.get(n)??[];t.push(e),i.set(n,t)}}const c=new Map;for(const t of e){const e=a.get(t.name)??null??"__root__",n=c.get(e)??[];n.push(t),c.set(e,n)}const d=e=>s((c.get(e)??[]).map(e=>{const t=(e=>{const t=(i.get(e.name)??[]).map(t=>({id:`op:${e.name}:${t.id}`,name:t.name,order:h(t.raw),metadata:{method:t.method,path:t.path,operationId:t.operationId,deprecated:t.deprecated,summary:t.summary,pointer:t.pointer,source:"oas32-parent",kind:"operation"}}));return{id:`tag:${e.name}`,name:e.displayName,order:e.order,children:s(t),metadata:{source:"oas32-parent",kind:"tag"}}})(e),n=d(e.name);return n.length>0&&(t.children=s([...t.children??[],...n])),t})),p=d("__root__"),u=i.get("Other")??[];if(u.length>0){const e=u.map(e=>({id:`op:Other:${e.id}`,name:e.name,order:h(e.raw),metadata:{method:e.method,path:e.path,operationId:e.operationId,deprecated:e.deprecated,summary:e.summary,pointer:e.pointer,source:"fallback",kind:"operation"}}));p.push({id:"tag:Other",name:"Other",order:Number.MAX_SAFE_INTEGER,children:s(e),metadata:{source:"fallback",kind:"tag"}})}return s(p)}function y(e,t={}){const n={...c,...t},o=[],r=d(e);if(!r)return{root:{id:"root",name:"OpenAPI",children:[],metadata:{kind:"root"}},warnings:["Document is null or not an object."]};const a=function(e){const t=d(e.paths);if(!t)return[];const n=[];for(const[e,o]of Object.entries(t)){const t=d(o);if(t)for(const o of i){const r=t[o];if(null==r)continue;const a=d(r);if(!a)continue;const s=p(a.operationId),i=p(a.summary),c=m(a.tags)?.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(e=>e.length>0)??[],f=i??s??`${o.toUpperCase()} ${e}`,h=s??`${o}:${e}`,l=`/paths/${encodeURIComponent(e)}/${o}`;n.push({id:h,path:e,method:o,name:f,summary:i,operationId:s,deprecated:!0===u(a.deprecated),tags:c,pointer:l,raw:a})}}return n}(r),y=function(e){const t=m(e.tags);if(!t)return[];const n=[];for(const e of t){const t=d(e),o=p(t?.name);o&&t&&n.push({name:o,displayName:l(t)??o,description:p(t.description),parent:p(t.parent),order:h(t),raw:t})}return n}(r);let $;if(function(e){return e.some(e=>void 0!==e.parent)}(y))$=g(y,a,n,o);else{const e=function(e){const t=m(e["x-tagGroups"]);if(!t)return[];const n=(e,t)=>{const o=d(e),r=p(o?.name),a=m(o?.tags)?.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(e=>e.length>0)??[];if(!r)return null;const s=m(o?.groups)?.map((e,t)=>n(e,t)).filter(e=>null!==e)??[];return{name:r,tags:a,groups:s.length>0?s:void 0,order:h(o??{})??t}};return t.map((e,t)=>n(e,t)).filter(e=>null!==e)}(r);$=e.length>0?function(e,t,n,o){const r=new Map;for(const e of t)r.set(e.name,e.displayName);const a=new Map;for(const e of n)if(!f(e.raw,o.showInternal))for(const t of e.tags){const n=a.get(t)??[];n.push(e),a.set(t,n)}const i=e=>(a.get(e)??[]).map(t=>({id:`op:${e}:${t.id}`,name:t.name,order:h(t.raw),metadata:{method:t.method,path:t.path,operationId:t.operationId,deprecated:t.deprecated,summary:t.summary,pointer:t.pointer,source:"tag-group",kind:"operation"}})),c=e=>({id:`tag:${e}`,name:r.get(e)??e,children:s(i(e)),metadata:{source:"tag-group",kind:"tag"}}),d=e=>{const t=[];for(const n of e.tags){const e=a.get(n);e?.length&&t.push(c(n))}for(const n of e.groups??[])t.push(d(n));return{id:`tag-group:${e.name}`,name:e.name,order:e.order,children:s(t),metadata:{source:"tag-group",kind:"tag-group"}}};return s(e.map(d))}(e,y,a,n):function(e,t,n){const o=new Map,r=new Map;for(let t=0;t<e.length;t+=1){const n=e[t];r.set(n.name,n.order??t)}for(const e of t){if(f(e.raw,n.showInternal))continue;const t=e.tags.length>0?e.tags:["Other"];for(const n of t){const t=o.get(n)??[];t.push(e),o.set(n,t)}}const a=new Map;for(const t of e)a.set(t.name,t.displayName);const i=[];for(const[e,t]of o){const n=t.map(t=>({id:`op:${e}:${t.id}`,name:t.name,order:h(t.raw),metadata:{method:t.method,path:t.path,operationId:t.operationId,deprecated:t.deprecated,summary:t.summary,pointer:t.pointer,source:"tag",kind:"operation"}}));i.push({id:`tag:${e}`,name:a.get(e)??e,order:"Other"===e?Number.MAX_SAFE_INTEGER:r.get(e),children:s(n),metadata:{source:"tag",kind:"tag"}})}return s(i)}(y,a,n)}const k=function(e,t){if(!t.showWebhooks)return;const n=d(e.webhooks);if(!n)return;const o=[];for(const[e,r]of Object.entries(n)){const n=d(r);if(n)for(const r of i){const a=d(n[r]);if(!a||f(a,t.showInternal))continue;const s=p(a.operationId),i=p(a.summary),c=i??s??`${r.toUpperCase()} ${e}`;o.push({id:`webhook:${e}:${r}`,name:c,order:h(a),metadata:{method:r,path:e,operationId:s,deprecated:!0===u(a.deprecated),summary:i,pointer:`/webhooks/${encodeURIComponent(e)}/${r}`,source:"tag",kind:"webhook"}})}}return 0!==o.length?{id:"section:webhooks",name:"Webhooks",order:Number.MAX_SAFE_INTEGER-2,children:s(o),metadata:{source:"section",kind:"section",section:"webhooks"}}:void 0}(r,n),w=function(e,t){if(!t.showComponents)return;const n=d(e.components),o=d(n?.schemas);if(!o)return;const r=[];for(const[e,n]of Object.entries(o)){const o=d(n);o&&!f(o,t.showInternal)&&r.push({id:`schema:${e}`,name:e,order:h(o),metadata:{pointer:`/components/schemas/${encodeURIComponent(e)}`,source:"tag",kind:"schema"}})}return 0!==r.length?{id:"section:components",name:"Components",order:Number.MAX_SAFE_INTEGER-1,children:s(r),metadata:{source:"section",kind:"section",section:"components"}}:void 0}(r,n);return{root:{id:"root",name:"OpenAPI",children:s([{id:"section:apis",name:"APIs",children:$,metadata:{kind:"section",source:"section",section:"apis"},order:0},...k?[k]:[],...w?[w]:[]]),metadata:{kind:"root"}},warnings:o}}var $={openapi:0,info:1,servers:2,paths:3,webhooks:4,components:5,security:6,tags:7,externalDocs:8,jsonSchemaDialect:9},k={maxDepth:8,showDeprecated:!0,expandCombinators:!0,showArrayItems:!0},w=new Set(["string","number","integer","boolean","null"]);function b(e){return"object"!=typeof e||null===e||Array.isArray(e)?null:e}function I(e){return"string"==typeof e&&e.trim().length>0?e.trim():void 0}function A(e){return"boolean"==typeof e?e:void 0}function O(e){return Array.isArray(e)?e:void 0}function j(e){return e?Array.isArray(e)?e.join(" | "):e:"any"}function _(e,t,n,o,r,a){const i=function(e){const t=e.type;return"string"==typeof t||Array.isArray(t)&&t.every(e=>"string"==typeof e)?t:void 0}(t),c=I(t.format),d=I(t.description),p=!0===A(t.deprecated),u=function(e){if(!e)return"unknown";switch(Array.isArray(e)?e[0]:e){case"object":return"object";case"array":return"array";case"string":return"string";case"number":return"number";case"integer":return"integer";case"boolean":return"boolean";case"null":return"null";default:return"unknown"}}(i),m={jsonPath:n,type:i,format:c,required:a,deprecated:p,description:d,kind:u},f=I(t.$ref);if(f){if(r.refStack.has(f))return r.warnings.push(`Circular $ref detected at ${n.join(".")}: ${f}. Breaking recursion.`),{id:n.join(".")||"root",name:e,metadata:{...m,kind:"ref",isLeaf:!0}};r.refStack.add(f)}let h,l=function(e){return!!e&&(Array.isArray(e)?e.every(e=>w.has(e)):w.has(e))}(i);if(o>=r.options.maxDepth)return f&&r.refStack.delete(f),{id:n.join(".")||"root",name:e,metadata:{...m,isLeaf:!0}};const g=b(t.properties);if(g){const e=O(t.required)??[],a=new Set(e.filter(e=>"string"==typeof e)),i=[];for(const[e,t]of Object.entries(g)){const s=b(t);s&&((r.options.showDeprecated||!0!==A(s.deprecated))&&i.push(_(e,s,[...n,"properties",e],o+1,r,a.has(e))))}h=[...h??[],...s(i)],l=!1}if(r.options.showArrayItems&&"array"===i){const e=b(t.items);if(e){h=[...h??[],_("items",e,[...n,"items"],o+1,r,!1)],l=!1}else m.hasItems=!1}if(r.options.expandCombinators){const e=[{key:"allOf",kind:"allOf"},{key:"anyOf",kind:"anyOf"},{key:"oneOf",kind:"oneOf"}];for(const{key:a,kind:i}of e){const e=O(t[a]);if(!e?.length)continue;const c=e.map((e,t)=>{const s=b(e);return s?_(`[${t}]`,s,[...n,a,String(t)],o+1,r,!1):{id:`${n.join(".")}.${a}[${t}]`,name:`[${t}]`,metadata:{jsonPath:[...n,a,String(t)],kind:"unknown",isLeaf:!0}}});h=[...h??[],{id:`${n.join(".")}.${a}`,name:a,children:s(c),metadata:{jsonPath:[...n,a],kind:i}}],l=!1}}const y=O(t.enum);return y?.length&&!h?.length&&(m.kind="enum",l=!0),f&&r.refStack.delete(f),{id:n.join(".")||"root",name:e,order:$[e],metadata:{...m,isLeaf:l},children:h}}function v(e,t={}){const n={...k,...t},o=[],r=b(e);if(!r)return{root:{id:"root",name:"Schema",metadata:{jsonPath:[],kind:"root",isLeaf:!0}},warnings:["Schema is null or not an object."]};return{root:_("root",r,[],0,{options:n,warnings:o,refStack:new Set},!0),warnings:o}}function N(e,t){if(0===t.length)return e;const n=t.join("."),o=[e];for(;o.length>0;){const e=o.shift();if(!e)continue;const t=e.metadata?.jsonPath?.join(".");if(t===n)return e;for(const t of e.children??[])o.push(t)}}function C(e){return"enum"===e.kind?"enum":"ref"===e.kind?"$ref":e.format?`${j(e.type)} (${e.format})`:j(e.type)}var E=["get","put","post","delete","options","head","patch","trace"],M={showInternal:!1,showComponents:!1,showWebhooks:!0,expandOperationDetails:!0};function S(e){return"object"!=typeof e||null===e||Array.isArray(e)?null:e}function R(e){return"string"==typeof e&&e.trim().length>0?e.trim():void 0}function x(e){return"boolean"==typeof e?e:void 0}function T(e){return Array.isArray(e)?e:void 0}function U(e,t){return!t&&(!0===x(e["x-scalar-ignore"])||!0===x(e["x-internal"]))}function P(e){const t=e["x-order"];return"number"==typeof t&&Number.isFinite(t)?t:void 0}function D(e){return R(e["x-displayName"])}function G(e,t){const n=[],o=T(e.parameters);if(o?.length){const e=function(e,t){const n=[];for(const o of e){const e=S(o);if(!e)continue;const r=R(e.name),a=R(e.in);if(!r||!a)continue;const s=`${a}:${r}`;n.push({id:`${t}/parameters/${encodeURIComponent(s)}`,name:r,metadata:{paramIn:a,required:!0===x(e.required),description:R(e.description),pointer:`${t}/parameters`,kind:"parameter"}})}return s(n)}(o,t);e.length>0&&n.push({id:`${t}/parameters`,name:"Parameters",children:e,metadata:{pointer:`${t}/parameters`,kind:"parameters"}})}const r=S(e.requestBody);if(r){const e=S(r.content),o=[];if(e)for(const[n]of Object.entries(e))o.push({id:`${t}/requestBody/content/${encodeURIComponent(n)}`,name:n,metadata:{contentType:n,pointer:`${t}/requestBody/content/${encodeURIComponent(n)}`,kind:"content"}});n.push({id:`${t}/requestBody`,name:"Request Body",metadata:{description:R(r.description),required:!0===x(r.required),pointer:`${t}/requestBody`,kind:"request-body"},children:o.length>0?o:void 0})}const a=S(e.responses);if(a){const e=function(e,t){const n=[];for(const[o,r]of Object.entries(e)){const e=S(r);if(!e)continue;const a=R(e.description),s=S(e.content),i=[];if(s)for(const[e]of Object.entries(s))i.push({id:`${t}/responses/${encodeURIComponent(o)}/content/${encodeURIComponent(e)}`,name:e,metadata:{contentType:e,pointer:`${t}/responses/${encodeURIComponent(o)}/content/${encodeURIComponent(e)}`,kind:"content"}});n.push({id:`${t}/responses/${encodeURIComponent(o)}`,name:o,metadata:{statusCode:o,description:a,pointer:`${t}/responses/${encodeURIComponent(o)}`,kind:"response"},children:i.length>0?i:void 0})}return s(n)}(a,t);e.length>0&&n.push({id:`${t}/responses`,name:"Responses",children:e,metadata:{pointer:`${t}/responses`,kind:"responses"}})}return n}function q(e,t,n,o){const r=t.expandOperationDetails?G(e.raw,e.pointer):void 0;return{id:`op:${o}:${e.id}`,name:e.name,order:P(e.raw),metadata:{method:e.method,path:e.path,operationId:e.operationId,deprecated:e.deprecated,summary:e.summary,description:e.description,pointer:e.pointer,source:n,kind:"operation"},children:r&&r.length>0?r:void 0}}function F(e,t,n,o){const r=new Map;for(const t of e)r.set(t.name,t);const a=new Map;for(const t of e){if(!t.parent){a.set(t.name,null);continue}if(!r.has(t.parent)){o.push(`Tag "${t.name}" references unknown parent "${t.parent}", treating as root.`),a.set(t.name,null);continue}const e=[t.name];let n=t.parent,s=!1;for(;n;){if(e.includes(n)){s=!0,o.push(`Circular tag reference detected: ${e.join(" -> ")} -> ${n}. Breaking cycle.`);break}e.push(n),n=r.get(n)?.parent}a.set(t.name,s?null:t.parent)}const i=new Map;for(const e of t){if(U(e.raw,n.showInternal))continue;const t=e.tags.length>0?e.tags:["Other"];for(const n of t){const t=i.get(n)??[];t.push(e),i.set(n,t)}}const c=new Map;for(const t of e){const e=a.get(t.name)??null??"__root__",n=c.get(e)??[];n.push(t),c.set(e,n)}const d=e=>s((c.get(e)??[]).map(e=>{const t=(e=>{const t=(i.get(e.name)??[]).map(t=>q(t,n,"oas32-parent",e.name));return{id:`tag:${e.name}`,name:e.displayName,order:e.order,children:s(t),metadata:{description:e.description,source:"oas32-parent",kind:"tag"}}})(e),o=d(e.name);return o.length>0&&(t.children=s([...t.children??[],...o])),t})),p=d("__root__"),u=i.get("Other")??[];if(u.length>0){const e=u.map(e=>q(e,n,"fallback","Other"));p.push({id:"tag:Other",name:"Other",order:Number.MAX_SAFE_INTEGER,children:s(e),metadata:{source:"fallback",kind:"tag"}})}return s(p)}function B(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){const e=S(n);if(!e||"string"!=typeof e.name)continue;const o={name:e.name};Array.isArray(e.tags)&&(o.tags=e.tags.filter(e=>"string"==typeof e)),Array.isArray(e.groups)&&(o.groups=B(e.groups)),t.push(o)}return t}function X(e,t,n,o,r,a=0){const i=[];if(e.tags)for(const a of e.tags){const e=t.get(a);if(!e||0===e.length)continue;const c=e.map(e=>q(e,r,"tag-group",a));i.push({id:`tag:${a}`,name:n.get(a)??a,order:o.get(a),children:s(c),metadata:{source:"tag-group",kind:"tag"}})}if(e.groups)for(const s of e.groups)i.push(X(s,t,n,o,r,a+1));return{id:`tag-group:${e.name}`,name:e.name,order:1e3*a,children:s(i),metadata:{source:"tag-group",kind:"tag-group"}}}function L(e,t,n,o){const r=new Map,a=new Map,i=new Map;for(let e=0;e<t.length;e+=1)a.set(t[e].name,e),i.set(t[e].name,t[e].displayName);for(const e of n){if(U(e.raw,o.showInternal))continue;const t=e.tags.length>0?e.tags:["Other"];for(const n of t){const t=r.get(n)??[];t.push(e),r.set(n,t)}}const c=B(e["x-tagGroups"]).map(e=>X(e,r,i,a,o)),d=r.get("Other");if(d&&d.length>0){const e=d.map(e=>q(e,o,"fallback","Other"));c.push({id:"tag:Other",name:"Other",order:Number.MAX_SAFE_INTEGER,children:s(e),metadata:{source:"fallback",kind:"tag"}})}return s(c)}function W(e){return e.map(e=>{const t=e.children?W(e.children):void 0,n="operation"===e.metadata?.kind?1:0,o=t?.reduce((e,t)=>e+(t.metadata?.operationCount??0),0)??0;return{...e,children:t,metadata:{...e.metadata,operationCount:n+o}}})}function z(e,t={}){const n={...M,...t},o=[],r=S(e);if(!r)return{root:{id:"root",name:"API Documentation",children:[],metadata:{kind:"root"}},warnings:["Document is null or not an object."]};const a=function(e){const t=S(e.paths);if(!t)return[];const n=[];for(const[e,o]of Object.entries(t)){const t=S(o);if(t)for(const o of E){const r=S(t[o]);if(!r)continue;const a=R(r.operationId),s=R(r.summary),i=R(r.description),c=T(r.tags)?.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(e=>e.length>0)??[],d=s??a??`${o.toUpperCase()} ${e}`,p=a??`${o}:${e}`,u=`/paths/${encodeURIComponent(e)}/${o}`;n.push({id:p,path:e,method:o,name:d,summary:s,description:i,operationId:a,deprecated:!0===x(r.deprecated),tags:c,pointer:u,raw:r})}}return n}(r),i=function(e){const t=T(e.tags);if(!t)return[];const n=[];for(const e of t){const t=S(e),o=R(t?.name);o&&t&&n.push({name:o,displayName:D(t)??o,description:R(t.description),parent:R(t.parent),order:P(t)})}return n}(r);let c;c=function(e){return e.some(e=>void 0!==e.parent)}(i)?F(i,a,n,o):function(e){return Array.isArray(e["x-tagGroups"])&&e["x-tagGroups"].length>0}(r)?L(r,i,a,n):function(e,t,n){const o=new Map,r=new Map;for(let t=0;t<e.length;t+=1)r.set(e[t].name,t);for(const e of t){if(U(e.raw,n.showInternal))continue;const t=e.tags.length>0?e.tags:["Other"];for(const n of t){const t=o.get(n)??[];t.push(e),o.set(n,t)}}const a=new Map;for(const t of e)a.set(t.name,t.displayName);const i=[];for(const[e,t]of o){const o=t.map(t=>q(t,n,"tag",e));i.push({id:`tag:${e}`,name:a.get(e)??e,order:"Other"===e?Number.MAX_SAFE_INTEGER:r.get(e),children:s(o),metadata:{source:"tag",kind:"tag"}})}return s(i)}(i,a,n);const d=function(e,t){if(!t.showComponents)return;const n=S(e.components),o=S(n?.schemas);if(!o)return;const r=[];for(const[e,n]of Object.entries(o)){const o=S(n);o&&!U(o,t.showInternal)&&r.push({id:`schema:${e}`,name:e,order:P(o),metadata:{description:R(o.description),pointer:`/components/schemas/${encodeURIComponent(e)}`,source:"tag",kind:"schema"}})}return 0!==r.length?{id:"section:components",name:"Components",order:Number.MAX_SAFE_INTEGER-1,children:s(r),metadata:{source:"tag",kind:"component"}}:void 0}(r,n);return{root:{id:"root",name:"API Documentation",children:s(W([...c,...d?[d]:[]])),metadata:{kind:"root"}},warnings:o}}