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.