Skip to content

Feature Request: Lifecycle Hooks and Structured Progress for GUI/Wrapper Integration #361

@sigmaSd

Description

@sigmaSd

The last changes allowed me to remove a lot of hacks, here is how the dax wrapper look now https://github.com/sigmaSd/Chef/blob/f11117c0db46e6eb3e3551ac141d0b0a0268f13e/src/dax_wrapper.ts

I asked gemini if there are other features that could be useful , if these don't sound interesting I can just close the issue, here is the answer:

Context

I currently have to rely on Proxy wrappers and reimplementations of internal logic (like pipeToPath) to integrate dax with my application's state (signals, progress bars, status listeners).

I would like to propose two API additions that would allow wrappers to integrate cleanly with dax without "hacks".

1. Structured Progress Callbacks

Problem: dax's pipeToPath and download progress are tightly coupled to the console/stderr output. To get raw progress numbers (loaded/total bytes) for a GUI progress bar, I currently have to reimplement pipeToPath completely, manually fetching the response and reading the stream.

Proposal: Add an .onProgress() method to RequestBuilder.

await $.request("https://example.com/file.zip")
  .onProgress((loaded: number, total: number | undefined) => {
    // Allows integrating with external UI systems
    myGui.updateProgress(loaded, total);
  })
  .pipeToPath("file.zip");

This would allow users to leverage dax's optimized streaming file writing while still observing the data flow.

2. Global "Middleware" or Lifecycle Hooks

Problem: My application has a dynamic AbortSignal (e.g., a "Cancel" button that resets for each task) and a global status listener (to show "Running..." or "Idle").
Currently, I have to wrap $ in a Proxy to intercept every command and request creation to:

  1. Inject the current signal dynamically (.signal(context.getSignal())).
  2. Notify the listener when execution starts and ends.

Proposal: Add support for lifecycle hooks or dynamic configuration in build$.

const $ = dax.build$({
  hooks: {
    // Called before execution, allowing modification of the builder
    beforeCommand: (builder) => {
      // Dynamic signal injection
      builder.signal(myContext.getSignal());
      // Status notification
      myContext.notify("running", builder.getText());
    },
    beforeRequest: (builder) => {
      builder.signal(myContext.getSignal());
      myContext.notify("running", builder.getUrl());
    },
    // Called after completion (success or failure)
    afterCommand: (result) => {
      myContext.notify("idle");
    },
    afterRequest: (result) => {
      myContext.notify("idle");
    }
  }
});

Alternatively, allowing the signal option in build$ to accept a getter function (provider) would solve the specific signal injection issue:

const $ = dax.build$({
  // dax calls this provider for every new command/request
  signal: () => myContext.currentSignal 
});

Benefit

These changes would allow developers to build rich CLIs, TUIs, and GUIs on top of dax using its native, robust implementations instead of having to maintain complex wrappers and custom stream handling logic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions