# Show HN: WebBase-III – dBASE III rebuilt in the browser with its own interpreter

> Source: <https://github.com/DDecoene/WebBaseIII>
> Published: 2026-06-24 08:46:16+00:00

**dBASE III is back. In your browser. USE customers like it's 1984.**

Remember the dot prompt? Before SQL won, before ORMs, before anyone said "full-stack" — there was dBASE III. You typed `USE customers`

, then `LIST`

, and your data was just *there*. WebBase-III brings that whole world back: the terminal, the language, `BROWSE`

, `@ SAY GET`

forms, `.prg`

programs, indexes, reports — rebuilt from scratch as a modern web app with its own interpreter in TypeScript, backed by Node.js, WebSockets, and SQLite.

**Try it in one click — no install:**

The Codespace installs dependencies and starts the dev server automatically. Open the forwarded port **5173** and you're at the dot prompt.

The command interface — type W3Script and see results instantly.

`LIST`

prints all records in active index order. The status bar shows the active database and table.

`INDEX ON name TO BYNAME`

creates a SQLite index and activates it — subsequent `LIST`

output is sorted alphabetically. `SEEK "Delta NV"`

jumps the record pointer to the first match in O(log n).

`BROWSE`

opens a spreadsheet-style grid. Records are shown in active index order. Tab/Enter to edit a cell, Ctrl+N for a new row, Delete to remove a row, Esc to return to the terminal.

`EDIT <name>`

opens the built-in `.prg`

source editor. Programs support the full W3Script language: `DO CASE/ENDCASE`

, `DO WHILE/ENDDO`

, `IF/ENDIF`

, form layouts, and all data commands. Ctrl+S saves, Esc cancels.

`@ row,col SAY "label" GET variable`

lays out character-cell form fields. `READ`

renders them as a live form and waits for the user to fill in values and submit.

The permanent left sidebar with category pickers and action buttons.

Wizards open in the main area with a live W3Script preview.

| Feature | Details |
|---|---|
W3Script interpreter |
dBASE III command dialect: navigation, filters, variables, loops, conditionals, forms, programs |
BROWSE grid |
Inline cell editing, keyboard nav, index-ordered display |
Form engine |
`@ ROW,COL SAY … GET` character-cell layout with `READ` |
Indexing |
`INDEX ON` , `SEEK` , `FIND` — active index controls all record order |
DO CASE |
Multi-branch conditional, `OTHERWISE` fallback |
Built-in functions |
`EOF()` , `BOF()` , `FOUND()` , `RECNO()` , `SUBSTR()` , `STR()` , `AT()` , `CTOD()` , `DTOC()` and more |
Program files |
Save, edit, and run `.prg` scripts with `DO` / `EDIT` |
The Assistant |
Permanent left sidebar — open databases/tables, browse, filter, index, search, design reports, run programs without typing |
Multi-user |
Each WebSocket connection gets its own isolated interpreter session |
Persistent storage |
`better-sqlite3` with WAL mode — databases survive server restart |

The sidebar on the left drives everything without typing: open or create databases and tables, browse and filter data, build indexes, search, design and run reports, and run programs. Every click generates a real W3Script command that echoes into the terminal — watch it to learn the language. Wizards (New table, Filter, report designer, …) open in the main area and show a live preview of the command they will run.

```
npm install
npm run dev        # http://localhost:5173
```

Production:

```
npm run serve      # builds, then serves everything on http://localhost:3000
```

LAN / Tailscale: the server binds to `0.0.0.0`

, so `http://<tailscale-ip>:3000`

works out of the box.

```
USE DATABASE mydb
CREATE TABLE customers (name CHAR(40), phone CHAR(20), country CHAR(30))
USE customers

APPEND RECORD
REPLACE name WITH "Acme Corp", phone WITH "555-1234", country WITH "BE"
APPEND RECORD
REPLACE name WITH "Zeta Ltd", phone WITH "555-5678", country WITH "NL"

INDEX ON name TO BYNAME
LIST                        * sorted A→Z

SEEK "Zeta Ltd"             * jump to record instantly
BROWSE                      * open editable grid
SET FILTER TO country == "BE"
LIST                        * filtered view
SET FILTER TO               * clear filter
```

WebBase-III supports **unlimited work areas** — each independently holding a table, record pointer, filter, and index. Link areas by key field using `SET RELATION TO`

for relational data access. Cross-area field access uses `alias.field`

dot notation.

Note:dBASE III supported a maximum of 10 work areas (DOS file handle limit). WebBase-III has no such limit. dBASE III used`alias->field`

arrow syntax; WebBase-III uses modern`alias.field`

dot notation.

| Command | What it does |
|---|---|
`SELECT <alias>` |
Activate (or create) a work area by name |
`USE <table> [ALIAS <name>]` |
Open table in active area; optional alias override |
`SET RELATION TO <expr> INTO <alias>` |
Link active area to another; auto-seeks on every navigation |
`SET RELATION TO` |
Clear relation on active area |
`LIST [col, alias.col, ...]` |
List records; optional column list with cross-area fields |
`LIST AREAS` |
Show all open work areas, pointers, indexes, and relations |
`CLOSE` |
Close active area's table |
`CLOSE ALL` |
Close all work areas, reset to single empty area `1` |

**Cross-area field access**: use `alias.field`

dot notation anywhere an expression is accepted — `SET FILTER TO`

, `IF`

, `REPLACE`

, `LIST`

, `INDEX ON`

.

| Command | What it does |
|---|---|
`USE <table>` |
Select a table; restores any saved active index |
`USE DATABASE <name>` |
Open a named SQLite database |
`LIST` |
Print records in active index order (up to 500) |
`LIST STRUCTURE` |
Show column schema |
`LIST TABLES` |
Show all tables with record counts |
`LIST DATABASES` |
Show all databases on disk (alias: `LIST DBS` ) |
`BROWSE` |
Open the editable grid |
`CLEAR` |
Clear terminal output |
`CREATE TABLE <n> (col TYPE, ...)` |
Create a table |
`DROP TABLE <name>` |
Delete a table |
`APPEND RECORD` |
Insert a blank row |
`DELETE` / `DELETE ALL` |
Delete current or all records |
`PACK` |
VACUUM the SQLite file |
`GO TOP` / `GO BOTTOM` / `GO <n>` |
Move record pointer |
`SKIP <n>` |
Move pointer forward/back |
`REPLACE <field> WITH <val>, ...` |
Update field(s) on current row |
`REPLACE ALL <field> WITH <val>, ...` |
Update all (filtered) rows |
`SET FILTER TO <expr>` |
Set a WHERE clause; empty clears it |

| Command | What it does |
|---|---|
`INDEX ON <expr> TO <tag>` |
Create index on expression; sets it active immediately |
`SET INDEX TO <tag>` |
Activate a previously created index |
`SET INDEX TO` |
Clear active index — restores natural insert order |
`REINDEX` |
Rebuild SQLite indexes for current table |
`LIST INDEXES` |
Print all indexes for current table with `*` active marker |
`SEEK <expr>` |
Position record pointer at first index match |
`FIND <string>` |
Alias for SEEK (unquoted string — dBASE III legacy form) |

| Command | What it does |
|---|---|
`CREATE REPORT <name>` |
Create a new report definition (opens JSON editor) |
`MODIFY REPORT <name>` |
Edit an existing report definition |
`REPORT FORM <name>` |
Run report — ASCII to terminal + HTML preview panel |
`LIST REPORTS` |
List all saved report definitions |
`DELETE REPORT <name>` |
Delete a report definition |

| Command | What it does |
|---|---|
`DO <name>` |
Run a saved `.prg` program |
`EDIT <name>` |
Open `.prg` source editor |
`LIST PROGRAMS` |
Show all saved programs |

Demo programs live in

`demos/*.prg`

and are the single source of truth: they are seeded into the program store on every server start, overwriting any store copy. Try`DO inventory`

for a full interactive showcase (work areas, relations, indexes, forms).

| Command | What it does |
|---|---|
`STORE <val> TO <var>` |
Assign a variable |
`INPUT "prompt" TO <var>` |
Collect keyboard input |
`@ r,c SAY "text" GET <var>` |
Define a form field |
`READ` |
Display the form and wait for submit |

| Command | What it does |
|---|---|
`IF <cond> … ENDIF` |
Conditional block |
`DO WHILE <cond> … ENDDO` |
Loop |
`DO CASE … ENDCASE` |
Multi-branch conditional (`CASE` , `OTHERWISE` ) |
`HELP` |
Print command reference |
`QUIT` |
Exit |

Functions work anywhere an expression is accepted — `IF`

, `DO WHILE`

, `STORE`

, `REPLACE`

, `INDEX ON`

, `SET FILTER TO`

, etc.

| Function | Returns |
|---|---|
`EOF()` |
True if record pointer is past last record |
`BOF()` |
True if record pointer is before first record |
`FOUND()` |
True if last `SEEK` / `FIND` matched |
`RECNO()` |
Current record number |
`RECCOUNT()` |
Total records in current table |
`UPPER(str)` |
Uppercase |
`LOWER(str)` |
Lowercase |
`TRIM(str)` |
Strip leading and trailing spaces |
`LTRIM(str)` |
Strip leading spaces only |
`SUBSTR(str, start, len)` |
Substring — 1-based; `len` optional (to end) |
`LEN(str)` |
String length |
`AT(needle, haystack)` |
1-based position; 0 if not found (case-sensitive) |
`STR(num, len, dec)` |
Number to right-justified string; default len=10, dec=0 |
`VAL(str)` |
String to number; non-numeric → 0 |
`INT(n)` |
Truncate toward zero |
`ABS(n)` |
Absolute value |
`SPACE(n)` |
String of n spaces |
`REPLICATE(str, n)` |
Repeat string n times |
`DATE()` |
Today as `MM/DD/YY` |
`DTOC(date)` |
Date to display string `MM/DD/YY` |
`CTOD(str)` |
Display string `MM/DD/YY` to ISO date |

W3Script supports both styles:

| Syntax | Value |
|---|---|
`TRUE` / `FALSE` |
Boolean true/false |
`.T.` / `.TRUE.` |
Boolean true (dBASE III style) |
`.F.` / `.FALSE.` |
Boolean false (dBASE III style) |

Boolean values display as `.T.`

/ `.F.`

in output to match dBASE conventions.

Logical operators are accepted in both styles too: `NOT`

/ `.NOT.`

, `AND`

/ `.AND.`

, `OR`

/ `.OR.`

(e.g. `DO WHILE .NOT. EOF()`

).

| Key | Action |
|---|---|
| Arrow keys | Navigate cells |
| Enter / F2 | Edit selected cell |
| Tab / Shift+Tab | Move right / left |
| Ctrl+N | New row |
| Delete | Delete current row |
| F5 | Refresh from DB |
| Esc | Exit grid, return to terminal |

```
server/
  index.ts              Node.js HTTP + WebSocket server (port 3000)
  Session.ts            Per-connection session: parses commands, drives Executor
  SessionManager.ts     Tracks all active sessions
  ServerDatabaseBridge.ts  IDatabaseBridge impl wrapping better-sqlite3
  ProgramStore.ts       .prg program storage in data/system.sqlite3
  IndexStore.ts         Index metadata + active index in data/system.sqlite3

src/
  interpreter/
    Lexer.ts            Tokenises W3Script input (case-insensitive)
    Parser.ts           Recursive-descent AST builder
    Executor.ts         Async AST runner; manages db/table/filter/vars/rowPtr/activeIndex
    Builtins.ts         Stateless built-in function implementations

  terminal/
    Terminal.ts         REPL UI — command history, multi-line block accumulation

  ui/
    Grid.ts             BROWSE spreadsheet — inline cell editing, keyboard nav
    FormLayout.ts       @ SAY GET form engine — character-cell coordinates
    ProgramEditor.ts    .prg source editor UI

  ws/
    WsClient.ts         Browser WebSocket client
npm test                    # unit + integration tests (Vitest)
npx playwright test         # end-to-end browser tests (requires dev server running)
```

The Playwright suite (`tests/integration.spec.ts`

, `tests/crm.spec.ts`

) drives a real browser against the running app and covers navigation, filters, indexing, programs, forms, and BROWSE.

AGPL-3.0 — see [LICENSE.md](/DDecoene/WebBaseIII/blob/main/LICENSE.md).

Why AGPL? WebBase-III is a toy, and the license keeps it that way: anyone can use it, fork it, and learn from it, but nobody can take it closed and sell it as a hosted service without giving their changes back. If you want to run it, hack it, or ship features from your dBASE memories — that's exactly what it's for.
