Skip to content

feat: add DataAccessor abstraction for custom value introspection#211

Open
TooTallNate wants to merge 3 commits intostorybookjs:mainfrom
vercel-labs:data-accessor-abstraction
Open

feat: add DataAccessor abstraction for custom value introspection#211
TooTallNate wants to merge 3 commits intostorybookjs:mainfrom
vercel-labs:data-accessor-abstraction

Conversation

@TooTallNate
Copy link
Copy Markdown

@TooTallNate TooTallNate commented Apr 2, 2026

Summary

  • Introduces a DataAccessor interface that abstracts all native JavaScript introspection operations (typeof, instanceof, Object.getOwnPropertyNames, Object.keys, property access, etc.) used when inspecting values
  • Allows react-inspector to inspect proxy objects, remote values, or values in a different runtime (e.g. QuickJS JSValue handles) that don't support native JS reflection
  • Fully backward-compatible: when no custom dataAccessor prop is provided, the default implementation uses native JS operations

Motivation

When inspecting values that are not regular JavaScript objects — such as proxy handles to values in a WebAssembly-embedded QuickJS runtime — native operations like Object.getOwnPropertyNames(), typeof, instanceof, and bracket property access don't work. These values have their own APIs for introspection (e.g. handle.getProp(name), vm.typeof(handle)).

This PR abstracts every introspection operation behind a DataAccessor interface so consumers can provide custom implementations for non-native value types.

API

New exports from react-inspector:

  • DataAccessor (TypeScript interface) — 18 methods covering all introspection operations:

    • Type checking: typeof(), isNull(), isArray(), isDate(), isRegExp(), isIterable(), isBuffer(), hasChildren(), isObjectPrototype()
    • Property access: getProperty(), getOwnPropertyNames(), keys(), hasOwnProperty(), propertyIsEnumerable()
    • Object metadata: getPrototypeOf(), getConstructorName(), getFunctionName()
    • Display: toString(), iterate(), length()
  • defaultDataAccessor — the default implementation using native JS operations

New prop on Inspector, ObjectInspector, and TableInspector:

  • dataAccessor?: DataAccessor — optional custom accessor for inspecting non-native values

Example usage with QuickJS

import { Inspector, DataAccessor } from 'react-inspector';

const jsValueAccessor: DataAccessor = {
  typeof(value) { return vm.typeof(value as JSValueHandle); },
  toString(value) { return (value as JSValueHandle).toString(); },
  isNull(value) { return (value as JSValueHandle).isNull; },
  isArray(value) { /* use qjs_is_array */ },
  getOwnPropertyNames(value) { /* use qjs_get_own_property_names */ },
  getProperty(value, prop) { return (value as JSValueHandle).getProp(prop); },
  getPrototypeOf(value) { return (value as JSValueHandle).getProp('__proto__'); },
  // ... etc
};

<Inspector data={jsValueHandle} dataAccessor={jsValueAccessor} />

Changes

File What changed
src/DataAccessor.ts New file: DataAccessor interface + defaultDataAccessor implementation
src/DataAccessorContext.ts New file: React context + useDataAccessor() hook
src/index.tsx Exports DataAccessor type and defaultDataAccessor
src/object-inspector/ObjectInspector.tsx Uses accessor in createIterator; accepts dataAccessor prop
src/object/ObjectValue.tsx Uses accessor via context for all type checks and display
src/object-inspector/ObjectPreview.tsx Uses accessor via context for previews
src/tree-view/TreeView.tsx Provides accessor via DataAccessorContext.Provider; passes to getExpandedPaths
src/tree-view/pathUtils.ts Uses accessor for getProperty in path expansion
src/table-inspector/TableInspector.tsx Uses accessor; wraps in context provider
src/table-inspector/getHeaders.ts Uses accessor for type checks and key enumeration
src/table-inspector/DataContainer.tsx Uses accessor via context

Testing

All 30 existing tests pass. Build succeeds with clean type output.

npm install-able URL

Abstract all native JavaScript introspection operations (typeof, instanceof,
Object.getOwnPropertyNames, Object.keys, property access, etc.) behind a
DataAccessor interface. This allows react-inspector to inspect proxy objects,
remote values, or values in a different runtime (e.g. QuickJS JSValue handles)
that don't support native JS reflection.

The new `dataAccessor` prop is accepted by Inspector, ObjectInspector, and
TableInspector. When not provided, it defaults to `defaultDataAccessor` which
uses native JS operations, making this a fully backward-compatible change.

Exports: `DataAccessor` (type) and `defaultDataAccessor` (implementation).
Copilot AI review requested due to automatic review settings April 2, 2026 00:22
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a DataAccessor abstraction and React context plumbing so react-inspector can introspect “non-native” values (proxies, remote handles, alternate runtimes) without relying on direct JavaScript reflection APIs.

Changes:

  • Added DataAccessor interface plus defaultDataAccessor implementation and a DataAccessorContext/useDataAccessor() hook.
  • Wired dataAccessor through Tree/Object/Table inspector rendering paths (previews, property access, key enumeration, path expansion).
  • Exported the new public API surface (DataAccessor type + defaultDataAccessor) from the package entrypoint.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/DataAccessor.ts Defines the accessor contract and the native default implementation.
src/DataAccessorContext.ts Adds context + hook to access the current accessor throughout the component tree.
src/index.tsx Exposes DataAccessor and defaultDataAccessor as new public exports.
src/tree-view/TreeView.tsx Accepts dataAccessor prop and provides it via context; passes accessor into path expansion.
src/tree-view/pathUtils.ts Uses accessor-based property reads during automatic expansion (getExpandedPaths).
src/object-inspector/ObjectInspector.tsx Creates iterators using accessor operations and forwards dataAccessor to TreeView.
src/object-inspector/ObjectPreview.tsx Uses accessor to preview arrays/objects without native reflection.
src/object/ObjectValue.tsx Uses accessor for type checks and display formatting.
src/table-inspector/TableInspector.tsx Accepts dataAccessor, uses it for header discovery and cell reads, and provides it via context.
src/table-inspector/getHeaders.ts Uses accessor for array/object detection and key enumeration.
src/table-inspector/DataContainer.tsx Uses accessor from context for property existence checks and value retrieval.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Remove unused DataAccessor type imports from TreeView.tsx and TableInspector.tsx
- Fix getter binding in defaultDataAccessor.getProperty to call with receiver
- Handle sparse arrays in ObjectPreview by checking hasOwnProperty per index
- Use accessor.typeof instead of native typeof in TableInspector sort comparator
- Add unit tests for getExpandedPaths with a custom accessor
@TooTallNate
Copy link
Copy Markdown
Author

In case you're curious, you can see this functionality being utilized in this commit: vercel-labs/quickjs-wasi@308cbe1

Already available live at: https://quickjs-wasi.labs.vercel.dev/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants