Discovery.js

repository·master·Indexed 19 days ago

https://github.com/discoveryjs/discovery

A frontend framework for rapid ad hoc JSON data analysis, designed for creating shareable serverless reports and dashboards. It provides tools for exploring complex JSON structures, including a data loading API, custom encodings for payload transformation, and an Embed API for integrating applications into iframes. The ecosystem also includes specialized tools like JsonDiscovery, Statoscope, CPUpro, and the Jora query language.

Tokens
19K
Snippets
58
Records
82
Agent score
65%

What's inside @discoveryjs/discovery

  1. Overview of Discovery.js

    master

    Discovery.js is a framework designed for ad hoc JSON data analysis, creating shareable server-less reports, and building dashboards. It provides tools to discover patterns and insights within JSON documents.

    Beyond the core framework, Discovery.js powers several specialized tools:

    • JsonDiscovery: A browser extension (Chrome, Edge, Firefox) that acts as an advanced JSON viewer, allowing users to discover JSON documents and generate reports directly in the browser.
    • Statoscope: A toolkit for analyzing and validating webpack bundles.
    • CPUpro: A tool for rethinking CPU profile analysis.
    • react-native-bundle-discovery: A toolkit for analyzing and visualizing Metro bundles.
  2. What are encodings in Discovery.js?

    master

    Encodings are configurations used during payload loading to transform raw data into JavaScript objects. Discovery.js uses encodings to determine how to interpret incoming data chunks or full payloads.

    When loading data, Discovery.js checks the test function of each configured encoding against the first chunk of the payload. If test returns true, that encoding is used to decode the data.

    Custom encodings are applied before the default encodings. The default application order is:

    1. jsonxl (snapshot9), non-streaming
    2. json (utilizing @discoveryjs/json-ext), streaming
  3. Explore related Discovery.js tools

    master

    The Discovery.js ecosystem includes several companion projects for different workflows:

    • Discovery CLI: Command-line tools used to serve and build projects based on Discovery.js.
    • Jora: A dedicated data query language designed for working with JSON.
    • Jora CLI: A command-line interface tool to process JSON data using the Jora query language.
  4. Configure custom encodings in App, Widget, or Model

    master

    You can register custom encodings by passing an encodings array to the configuration object of an App, Widget, or Model.

    Additionally, preloaders can pass encodings to a data loader via the loadDataOptions object. The Model (the base class for App and Widget) integrates these encodings into its loadData* method calls.

    new App({
        encodings: [
            {
                name: 'lines/counter',
                test: () => true, // Always applicable
                streaming: false,
                decode: (payload) => new TextDecoder().decode(payload).split('\n').length
            }
        ]
    });
  5. Enable the embed feature in the embedded app

    master

    For an application to communicate with a host via the Embed API, it must explicitly enable the embed feature. This can be done when instantiating an App or a ViewModel.

    Using App:

    const myapp = new App({ embed: true });

    Using ViewModel:

    import { ViewModel, embed } from '@discoveryjs/discovery';
    
    const myapp = new ViewModel({
        extensions: [
            embed
        ]
    });
    import { App, ViewModel, embed } from '@discoveryjs/discovery';
    
    // App
    const myapp = new App({ embed: true });
    
    // ViewModel
    const myapp = new ViewModel({
        extensions: [
            embed
        ]
    });
  6. Enable the upload data extension

    master

    You can enable data uploading capabilities in either an App or a Widget instance. This feature allows users to load and unload data using file inputs or drag-and-drop functionality.

    To enable it with default settings in an App:

    const myapp = new App({ upload: true });

    To enable it in a Widget using the extensions option:

    const myapp = new Widget({
        extensions: [
            upload
        ]
    });
    import { App, Widget, upload } from '@discoveryjs/discovery';
    
    // App
    const myapp = new App({ upload: true });
    
    // Widget
    const myapp = new Widget({
        extensions: [
            upload
        ]
    });
  7. Sync host location state with an embedded app

    master

    To ensure the embedded app's routing stays in sync with the host page's location, follow this recommended sequence of API calls within the connectToEmbedApp callback:

    1. Call embedApp.setRouterPreventLocationUpdate(true) to prevent immediate feedback loops.
    2. Call embedApp.setPageHash(location.hash) to sync the hash.
    3. Call embedApp.setLocationSync(true) to enable ongoing synchronization.
    import { connectToEmbedApp } from "@discoveryjs/discovery/dist/discovery-embed.js";
        
    const disconnect = connectToEmbedApp(iframe, (embedApp) => {
        // ... any other setup
    
        // recommended order of API calls to sync location state with embed app
        embedApp.setRouterPreventLocationUpdate(true);
        embedApp.setPageHash(location.hash);
        embedApp.setLocationSync(true);
    });
  8. Understand the message types for embedded applications

    master

    When embedding a Discovery application within a host, communication occurs via a structured messaging system. There are three primary message directions:

    1. Host to Preinit: Messages sent from the host to the embedded app during its initialization phase (e.g., defineAction, setPageHash).
    2. Preinit to Host: Messages sent from the embedded app to the host during initialization (e.g., preinit, loadingState).
    3. Host to Client: General runtime messages sent from the host to the embedded application (e.g., setPageParams, setColorSchemeState, changeNavButtons, or dataStream).
    4. Client to Host: Messages sent from the embedded application back to the host (e.g., ready, pageHashChanged, action).

    All messages follow a standard shape defined by CreateMessageType, containing a from: 'discoveryjs-app', a unique id, a type (the action name), and a payload containing the data.

  9. How Model extensions work

    master

    Extensions allow you to apply logic to a Model instance. An extension can be a single function, an array of functions, or an object containing multiple extension functions. You apply them using model.apply(extensions). This is useful for modularizing features like custom text view renderers or shared query logic across multiple models.

    const myExtension = (host: Model) => {
        host.on('data', () => console.log('Data updated!'));
    };
    
    model.apply(myExtension);
    
    // Or applying an array of extensions
    model.apply([ext1, ext2]);
  10. How the Embed API works in the preloader

    master

    When embed: true is passed to the preloader, it initializes a communication bridge between the Discovery application (running in an iframe/embed) and the host window using postMessage.

    Key Behaviors:

    • Initialization: Sends a preinit message to the host containing the current page hash.
    • State Updates: Automatically sends loadingState messages to the host as the data loading progresses.
    • Message Handling: Listens for specific messages from the host: defineAction, setPageHash, and setRouterPreventLocationUpdate. These messages are collected in a postponeMessages array and returned when the embed is disposed.
    • Cleanup: When the embed is destroyed or the window unloads, it sends a destroy message to the host and cleans up event listeners.

    Return Value for Embeds: If using embed: true, the preloader returns a disposeEmbed function. Calling this function cleans up the communication bridge and returns an object containing the hostId and any postponeMessages received from the host.

    const { disposeEmbed } = preloader({
        embed: true,
        dataSource: 'url',
        data: { /* ... */ }
    });
    
    // Later, when cleaning up:
    const { hostId, postponeMessages } = disposeEmbed();
  11. Configure a view using SingleViewConfig

    master

    When rendering, you can pass a SingleViewConfig object to control conditional rendering, data scoping, and styling.

    Key properties:

    • view: The name of the registered view (string) or a RenderFunction.
    • when: A query (string or function) that determines if the view should render.
    • context: A query to transform the context passed to the view.
    • data: A query to transform the data passed to the view.
    • whenData: A query that determines if the view should render based on the transformed data.
    • className: A string, function, or array of strings/functions to apply CSS classes.
    • tooltip: A TooltipConfig object to attach a tooltip to the rendered element.
    • postRender: A function called after the view has been rendered.
    const config: SingleViewConfig = {
      view: 'my-view',
      when: (data) => data.isVisible,
      data: (data) => ({ ...data, processed: true }),
      className: (data) => data.active ? 'is-active' : 'is-inactive',
      tooltip: {
        content: 'This is a tooltip'
      }
    };
    
    await viewRenderer.render(container, config, data);
  12. Understand the LoadDataState and progress tracking

    master

    When loading data, the process is tracked via a LoadDataState object. This state allows you to monitor the lifecycle of a data request and its progress. The stage indicates the current phase of the operation:

    • inited: Initialized
    • request: Requesting the resource
    • receiving: Receiving data chunks
    • decoding: Decoding the received payload
    • received: Data loading and decoding complete
    • error: An error occurred during the process

    If the stage is not error, you can monitor LoadDataStateProgress to track completion:

    • done: Boolean indicating if the process is finished.
    • completed: The amount of data processed.
    • total: The total amount of data expected (if known).
    • elapsed: Time elapsed during the operation.
    • units: Typically 'bytes'.