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


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