Skip to content

Commit 47cdd9a

Browse files
authored
Merge pull request #125 from livesession/feat/uniform-inspection
Feat/uniform inspection
2 parents 011264a + 439c3c6 commit 47cdd9a

27 files changed

Lines changed: 838 additions & 41 deletions

.dev/docs/14.uniform/UniformDataFormat.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ The format builds on three levels: `Reference` contains `Definition` objects, wh
6262
| `$$xor` | OpenAPI `oneOf` | `properties[]` contains alternatives | Discriminated unions requiring exactly one |
6363
| `$$array` | OpenAPI `type: array` | `ofProperty` contains item type | Collections and lists |
6464
| `$$enum` | OpenAPI `enum` | `properties[]` for values | Enumerated constants |
65+
| `$$function` | TypeDoc reflection | `properties[]` for parameters, `ofProperty` for return type | Function/callback signatures |
6566

6667
## Property Metadata System
6768

@@ -115,6 +116,29 @@ Contains TypeScript source information like file paths and symbol IDs.
115116
| `ApiRefProperties` | Recursive property tree renderer |
116117
| `SubProperties` | Nested properties with expand/collapse |
117118

119+
## Function Property Type (`$$function`)
120+
121+
The `$$function` type represents function/callback properties. It reuses the same `properties[]` and `ofProperty` fields as other special types:
122+
123+
- `properties[]` — function **parameters** (each as a `DefinitionProperty` with name, type, description)
124+
- `ofProperty`**return type** (same pattern as `$$array`)
125+
126+
Example: `onCallback(arg1: boolean, job: Job): User` produces:
127+
128+
```json
129+
{
130+
"name": "onCallback",
131+
"type": "$$function",
132+
"properties": [
133+
{ "name": "arg1", "type": "boolean" },
134+
{ "name": "job", "type": "Job", "symbolDef": { "id": "..." } }
135+
],
136+
"ofProperty": { "name": "", "type": "User", "symbolDef": { "id": "..." } }
137+
}
138+
```
139+
140+
Void return types produce `ofProperty: { type: "void" }`. Nested callbacks (function parameters that are themselves functions) produce nested `$$function` types.
141+
118142
## TypeDoc Integration via MiniUniform
119143

120144
Transformation Steps:
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Uniform Inspection
2+
3+
Uniform Inspection adds a reactive Proxy layer over uniform References. It wraps static type metadata with a decoupled value layer, enabling interactive playgrounds, live API explorers, and two-way bindings.
4+
5+
## Architecture
6+
7+
```
8+
infer(reference, instance)
9+
10+
├── Reference (frozen, readonly) — type metadata
11+
│ └── definitions[0].properties[]
12+
13+
└── ValueMap (mutable, fast) — runtime values
14+
├── "name" → "LiveSession"
15+
└── "addUser" → fn
16+
17+
18+
inferred.play({ plugins }) → Proxy<T>
19+
20+
├── get → ValueMap.get(key) + hooks
21+
├── set → ValueMap.set(key, val) + hooks
22+
└── call → instance.method(...args) + hooks
23+
```
24+
25+
## Package Entry
26+
27+
Exported from `@xyd-js/uniform/inspection` and also accessible via the default import:
28+
29+
```ts
30+
// Named imports
31+
import { infer, uniform } from "@xyd-js/uniform/inspection"
32+
33+
// Or via default import
34+
import uniform from "@xyd-js/uniform"
35+
uniform.infer(reference, instance)
36+
uniform.inspect(proxy).json()
37+
```
38+
39+
## API
40+
41+
### `infer(reference, instance)`
42+
43+
Creates an `Infer` wrapper around an instance. The Reference is frozen (deep clone + `Object.freeze`). The instance's current values are captured into a separate `ValueMap`.
44+
45+
```ts
46+
const inferred = infer(reference, { name: "LiveSession", port: 3000 })
47+
```
48+
49+
Returns an `Infer<T>` object with:
50+
51+
| Method | Returns | Description |
52+
|--------|---------|-------------|
53+
| `play(options?)` | `Proxy<T>` | Creates a reactive proxy backed by the ValueMap |
54+
| `snapshot()` | `Record<string, any>` | Frozen copy of current values |
55+
| `reset()` | `void` | Restores all values to initial state |
56+
| `reference` | `Reference` | Readonly access to the frozen Reference |
57+
58+
### `inferred.play(options?)`
59+
60+
Returns a Proxy that looks like `T` but reads/writes to the internal ValueMap. The original instance is never mutated.
61+
62+
```ts
63+
const proxy = inferred.play()
64+
proxy.name // reads from ValueMap
65+
proxy.name = "New" // writes to ValueMap, fires hooks
66+
proxy.addUser(...) // calls original instance method, fires hooks
67+
```
68+
69+
### `uniform(proxy)`
70+
71+
Returns utilities for a play proxy:
72+
73+
```ts
74+
uniform(proxy).json() // current values as plain object (excludes functions)
75+
uniform(proxy).reference() // frozen Reference metadata
76+
```
77+
78+
Throws if the argument is not a play proxy.
79+
80+
### `inferred.snapshot()` / `inferred.reset()`
81+
82+
```ts
83+
const before = inferred.snapshot() // frozen copy of current values
84+
proxy.name = "New"
85+
const after = inferred.snapshot() // different values, same Reference
86+
inferred.reset() // back to initial values
87+
```
88+
89+
## Plugins
90+
91+
Plugins are the extension point for framework-specific behavior. A plugin receives a `PlayPluginContext` in its `setup()` method.
92+
93+
```ts
94+
interface PlayPlugin {
95+
name: string
96+
setup(context: PlayPluginContext): void
97+
}
98+
99+
interface PlayPluginContext {
100+
reference: Reference
101+
registerHooks(hooks: InspectionHooks): void
102+
refresh(): void
103+
subscribe(listener: () => void): () => void
104+
}
105+
```
106+
107+
### Hooks
108+
109+
Plugins register hooks via `context.registerHooks()`. Multiple plugins can register hooks — they fire in plugin registration order.
110+
111+
```ts
112+
interface InspectionHooks {
113+
get?(property: string, value: any): void
114+
set?(property: string, oldValue: any, newValue: any): void
115+
call?(method: string, args: any[], returnValue: any): void
116+
}
117+
```
118+
119+
### Example: logging plugin
120+
121+
```ts
122+
function loggingPlugin(): PlayPlugin {
123+
return {
124+
name: "logging",
125+
setup(context) {
126+
context.registerHooks({
127+
set(property, oldValue, newValue) {
128+
console.log(`${property}: ${oldValue} → ${newValue}`)
129+
},
130+
call(method, args) {
131+
console.log(`${method}(${args.join(", ")})`)
132+
}
133+
})
134+
}
135+
}
136+
}
137+
138+
const proxy = inferred.play({ plugins: [loggingPlugin()] })
139+
```
140+
141+
### Subscribe
142+
143+
Plugins can subscribe to value changes for framework-agnostic reactivity:
144+
145+
```ts
146+
setup(context) {
147+
const unsub = context.subscribe(() => {
148+
// any value changed — re-render, etc.
149+
})
150+
}
151+
```
152+
153+
## Value Tracking
154+
155+
The Reference stores type metadata only and is frozen after creation. Runtime values live in a decoupled `ValueMap`:
156+
157+
| Operation | Reference (static) | ValueMap (dynamic) |
158+
|-----------|--------------------|--------------------|
159+
| `proxy.name` | Finds property type info | `values.get("name")` |
160+
| `proxy.name = "X"` | Not touched | `values.set("name", "X")` + hooks |
161+
| `proxy.method(...)` | Finds method signature | Calls `instance.method(...)` + hooks |
162+
| `proxy.nested.field` | Walks property tree | `values.get("nested.field")` |
163+
164+
### Nested Objects
165+
166+
Nested property access returns sub-proxies for deep observation:
167+
168+
```ts
169+
proxy.db.host = "production" // sets ValueMap key "db.host"
170+
```
171+
172+
## Internal Implementation
173+
174+
| File | Purpose |
175+
|------|---------|
176+
| `src/inspection/Infer.ts` | `InferImpl` class, `infer()` factory |
177+
| `src/inspection/ValueMap.ts` | Flat `Map<string, any>` with dot-path support, snapshot/reset |
178+
| `src/inspection/play.ts` | Proxy factory with get/set/apply traps, hook dispatch |
179+
| `src/inspection/uniform.ts` | `uniform()` helper using WeakMap to find Infer from proxy |
180+
| `src/inspection/types.ts` | `Infer`, `PlayPlugin`, `InspectionHooks` interfaces |

.dev/docs/GLOSSARY.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ HMR (Hot Module Replacement)
9494

9595
## I
9696

97+
Infer
98+
: The `infer(reference, instance)` function from `@xyd-js/uniform/inspection`. Wraps a static Reference with a decoupled ValueMap, enabling reactive proxies via `play()`, value snapshots, and reset.
99+
100+
Inspection
101+
: The reactive Proxy layer over uniform References (`@xyd-js/uniform/inspection`). Provides `infer()`, `play()`, plugins with hooks (get/set/call), `subscribe()`, `snapshot()`, `reset()`, and `uniform(proxy).json()`. Enables interactive playgrounds and live API explorers.
102+
103+
InspectionHooks
104+
: The hook interface registered by plugins via `context.registerHooks()`. Supports `get(property, value)`, `set(property, oldValue, newValue)`, and `call(method, args, returnValue)`.
105+
97106
Integrations
98107
: Third-party service configurations in `docs.json` including analytics (LiveSession), search (Orama, Algolia), support (Chatwoot, Intercom, LiveChat), and diagrams (Mermaid, Graphviz).
99108

@@ -209,8 +218,14 @@ UI
209218
Uniform
210219
: The `@xyd-js/uniform` package. A normalized, language-agnostic data format for API documentation. The central abstraction layer between API spec parsers (OpenAPI, GraphQL, TypeDoc) and UI rendering (Atlas).
211220

221+
Uniform Inspection
222+
: The `@xyd-js/uniform/inspection` entry. A reactive Proxy layer that wraps uniform References with a decoupled ValueMap. Provides `infer()`, `play()`, plugins, hooks, snapshots, and `json()` output for interactive documentation use cases.
223+
212224
## V
213225

226+
ValueMap
227+
: Internal data structure in Uniform Inspection that stores runtime property values separately from the frozen Reference. Uses a flat `Map<string, any>` with dot-path keys for nested properties. Supports `snapshot()` (frozen copy) and `reset()` (restore initial values).
228+
214229
Variant
215230
: A `DefinitionVariant` in the Uniform format representing alternative representations of a definition (e.g., different HTTP status codes or content types). Managed by `VariantContext` in the UI.
216231

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282

8383
### 14. Uniform
8484
- @.dev/docs/14.uniform/UniformDataFormat.md
85+
- @.dev/docs/14.uniform/UniformInspection.md
8586

8687
### 15. Atlas
8788
- @.dev/docs/15.atlas/AtlasUiComponents.md
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"host": "localhost",
3+
"port": 3000
4+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"title": "Config",
3+
"canonical": "config",
4+
"description": "",
5+
"examples": {"groups": []},
6+
"definitions": [{
7+
"title": "Properties",
8+
"properties": [
9+
{"name": "host", "type": "string", "description": ""},
10+
{"name": "port", "type": "number", "description": ""}
11+
]
12+
}]
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"value": 0
3+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"title": "Calculator",
3+
"canonical": "calculator",
4+
"description": "",
5+
"examples": {"groups": []},
6+
"definitions": [{
7+
"title": "Properties",
8+
"properties": [
9+
{"name": "value", "type": "number", "description": ""},
10+
{"name": "add", "type": "$$function", "description": ""}
11+
]
12+
}]
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"db": {"host": "localhost", "port": 5432}
3+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"title": "AppConfig",
3+
"canonical": "app-config",
4+
"description": "",
5+
"examples": {"groups": []},
6+
"definitions": [{
7+
"title": "Properties",
8+
"properties": [
9+
{"name": "db", "type": "object", "description": ""}
10+
]
11+
}]
12+
}

0 commit comments

Comments
 (0)