Dev Build Log

Why We Designed a WebSocket Mock DSL Instead of YAML, JSON, or JS

Lightweight Mock shouldn't get heavy first

For HTTP, Mock is already light: capture a request, tick features, edit the response. One round trip and you're done.

Lightweight WebSocket Mock is almost as short: connect, login, one reply, a heartbeat every few seconds. You're mocking a small dialog, not a program.

Existing tools still drop that dialog into a JSON rule table, YAML config, or a JS file. They're capable, and they run. The catch: something you could say in a dozen lines first needs field names, braces, and setInterval. The light case gets weighed down by the container.

We designed a DSL for that—to make lightweight Mock lighter. The UI calls it WS Flow; the file is .dpws. People write the script. JSON stays a compile artifact.

The same dialog

“Challenge on connect → login success → tick every 15s” as rule JSON often looks like this:

{
  "url": "wss://api.example.com/ws",
  "onOpen": [{ "send": { "type": "challenge" } }],
  "handlers": [
    {
      "match": { "type": "login" },
      "reply": { "type": "loginSuccess" },
      "then": {
        "every": "15s",
        "send": { "type": "tick" }
      }
    }
  ]
}

The words go to onOpen, handlers, match, reply, and then. YAML drops the braces; the shell remains—keys, lists, another nest, indent that means both config and conversation. JS can do anything; a Mock also turns into a small program.

The same story as Flow:

# ws wss://api.example.com/ws
# profile type
# ping auto

--@open
    challenge

--login
    loginSuccess
        --@loop 15s
            tick

--@open is connect. --login is the client logging in. Indented lines are the reply and the later push. loginSuccess compiles via the header to {"type":"loginSuccess"}—no hand-written JSON per business frame. Delay and periodic push sit on the line (+300ms, --@loop 15s). No extra control flow.

That's the grammar: an indent tree and a handful of tokens (--, --@, ~scope, |, $, +delay, !close). No if / for / functions. Use cases: WebSocket Mock. Tokens: .dpws language reference.

HTTP Mock still starts from a capture and a GUI. WS Flow only serves this kind of short dialog. Don't mash them into one form.

DSL for the light cases; JS for the rest

Flow is only for lightweight Mock: a short, sequential session. Concurrent or out-of-order frames, deep branches, dynamic or random payloads, reading external data—YAML/JSON ecosystems (Schema, diffs, CI) or plain JS fit better. That isn't an unfinished DSL. We don't want a light grammar stretched into a general-purpose language.

A custom DSL has a bill: parser, diagnostics, highlighting, completion, and a new set of tokens for teammates. Validate is in the editor; highlighting and completion are not. What we get is a file that reads like a dialog, not like config.

Same thread as the last two

Why we moved capture history from sql.js to native SQLite was about storage that survives all-day sessions.
Dropping Electron for Tauri was about a shell that was too heavy.
This one: lightweight WebSocket Mock shouldn't get heavy in JSON, YAML, or JS first. We used a shorter DSL to keep it light.

Related docs

Next

After Leaving Electron, Silent Updates Had to Be Ours—after the shell swap, stock updaters didn't match three processes. Checks and downloads moved into the tray; apply is a silent overwrite after one click.


Download DevPeek to see HTTP Mock next to capture. WS Flow is in the WebSocket Mock guide. Different take? GitHub Discussions.