@rip-lang/db 0.9.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,320 +2,176 @@
2
2
 
3
3
  # Rip DB - @rip-lang/db
4
4
 
5
- > **Simple RestFUL HTTP server for concurrent DuckDB read and write queries**
5
+ > **DuckDB server with the official DuckDB UI**
6
6
 
7
- Uses custom Zig-based DuckDB bindings (`lib/`) for fast, type-safe database access.
7
+ High-performance DuckDB HTTP server using native Zig bindings. Includes full
8
+ compatibility with the official DuckDB UI — no WASM, pure native speed.
8
9
 
9
- ## Overview
10
+ ## Quick Start
10
11
 
11
12
  ```bash
12
- # Install globally
13
+ # Install
13
14
  bun add -g @rip-lang/db
14
15
 
15
- # Run with in-memory database
16
+ # Run (opens DuckDB UI at http://localhost:4213)
16
17
  rip-db
17
18
 
18
- # Run with file-based database
19
- rip-db mydb.duckdb
19
+ # With a database file
20
+ rip-db mydata.duckdb
20
21
 
21
- # Specify port
22
- rip-db mydb.duckdb --port=8080
22
+ # Custom port
23
+ rip-db --port=8080
23
24
  ```
24
25
 
25
- Or run directly with Rip:
26
+ Open http://localhost:4213 to use the official DuckDB UI.
26
27
 
27
- ```bash
28
- rip db.rip :memory: --port=4000
29
- ```
28
+ ## Features
29
+
30
+ - **Official DuckDB UI** — Beautiful query interface at `/`
31
+ - **Native Zig bindings** — Zero-copy binary protocol, no JS serialization overhead
32
+ - **JSON API** — Simple REST endpoints for programmatic access
33
+ - **Single process** — DuckDB's multi-threaded engine handles concurrency
30
34
 
31
35
  ## API Endpoints
32
36
 
33
- ### POST /sql
37
+ ### DuckDB UI (Binary Protocol)
34
38
 
35
- Execute any SQL statement (queries or mutations) with JSON body.
39
+ The official DuckDB UI works out of the box at `http://localhost:4213/`.
36
40
 
37
- **Request:**
38
- ```json
39
- {
40
- "sql": "SELECT * FROM users WHERE id = ?",
41
- "params": [1]
42
- }
41
+ | Endpoint | Method | Purpose |
42
+ |----------|--------|---------|
43
+ | `/` | GET | Official DuckDB UI |
44
+ | `/ddb/run` | POST | Execute SQL (binary) |
45
+ | `/ddb/tokenize` | POST | Syntax highlighting |
46
+ | `/ddb/interrupt` | POST | Cancel query |
47
+
48
+ ### JSON API
49
+
50
+ For programmatic access:
51
+
52
+ **POST /sql** — Execute SQL with JSON body
53
+
54
+ ```bash
55
+ curl -X POST http://localhost:4213/sql \
56
+ -H "Content-Type: application/json" \
57
+ -d '{"sql": "SELECT * FROM users WHERE id = 1"}'
43
58
  ```
44
59
 
45
- **Response (JSONCompact format):**
60
+ Response (JSONCompact format):
46
61
  ```json
47
62
  {
48
- "meta": [
49
- {"name": "id", "type": "INTEGER"},
50
- {"name": "name", "type": "VARCHAR"},
51
- {"name": "email", "type": "VARCHAR"}
52
- ],
53
- "data": [
54
- [1, "Alice", "alice@example.com"]
55
- ],
63
+ "meta": [{"name": "id", "type": "INTEGER"}, {"name": "name", "type": "VARCHAR"}],
64
+ "data": [[1, "Alice"]],
56
65
  "rows": 1,
57
66
  "time": 0.001
58
67
  }
59
68
  ```
60
69
 
61
- ### POST /
62
-
63
- Execute SQL with raw body (duck-ui compatible). Used by [duck-ui](https://demo.duckui.com).
64
-
65
- **Request:**
66
- ```
67
- POST / HTTP/1.1
68
- Content-Type: application/x-www-form-urlencoded
70
+ **POST /** — Raw SQL in body (duck-ui compatible)
69
71
 
70
- SELECT * FROM users WHERE id = 1
72
+ ```bash
73
+ curl -X POST http://localhost:4213/ -d "SELECT 42 as answer"
71
74
  ```
72
75
 
73
- **Response:** Same JSONCompact format as `/sql`.
74
-
75
- ### GET /health
76
-
77
- Simple health check (no database query).
76
+ **GET /health** Health check
78
77
 
79
78
  ```json
80
- { "ok": true }
79
+ {"ok": true}
81
80
  ```
82
81
 
83
- ### GET /status
84
-
85
- Database info including table list.
82
+ **GET /status** — Database info
86
83
 
87
84
  ```json
88
- {
89
- "ok": true,
90
- "database": "mydb.duckdb",
91
- "tables": ["users", "orders", "products"],
92
- "time": "2026-02-02T14:30:00.000Z"
93
- }
85
+ {"ok": true, "database": "mydata.duckdb", "tables": ["users", "orders"]}
94
86
  ```
95
87
 
96
- ### GET /tables
97
-
98
- List all tables in the database.
88
+ **GET /tables** — List tables
99
89
 
100
90
  ```json
101
- {
102
- "ok": true,
103
- "tables": ["users", "orders", "products"]
104
- }
91
+ {"ok": true, "tables": ["users", "orders"]}
105
92
  ```
106
93
 
107
- ### GET /schema/:table
108
-
109
- Get schema for a specific table.
94
+ **GET /schema/:table** — Table schema
110
95
 
111
96
  ```json
112
- {
113
- "ok": true,
114
- "table": "users",
115
- "columns": [
116
- { "column_name": "id", "data_type": "INTEGER", "is_nullable": "NO" },
117
- { "column_name": "name", "data_type": "VARCHAR", "is_nullable": "YES" },
118
- { "column_name": "email", "data_type": "VARCHAR", "is_nullable": "YES" }
119
- ]
120
- }
121
- ```
122
-
123
- ### GET /ui
124
-
125
- Built-in SQL console. Open in browser:
126
-
127
- ```
128
- http://localhost:4000/ui
129
- ```
130
-
131
- ## DuckDB UI Compatibility
132
-
133
- rip-db implements the official DuckDB UI binary protocol, making it compatible with
134
- DuckDB's built-in UI. This means you can use the beautiful DuckDB UI with rip-db!
135
-
136
- ### Binary Protocol Endpoints
137
-
138
- | Endpoint | Method | Purpose |
139
- |-------------------|--------|----------------------------|
140
- | `/ddb/run` | POST | Execute SQL (binary result)|
141
- | `/ddb/interrupt` | POST | Cancel running query |
142
- | `/ddb/tokenize` | POST | Syntax highlighting |
143
- | `/info` | GET | Version info |
144
-
145
- ### How It Works
146
-
147
- ```
148
- ┌─────────────┐ Binary Protocol ┌─────────────┐
149
- │ DuckDB UI │ ◀─────────────────────▶│ rip-db │
150
- │ (Browser) │ │ Server │
151
- └─────────────┘ └─────────────┘
97
+ {"ok": true, "table": "users", "columns": [{"column_name": "id", "data_type": "INTEGER"}]}
152
98
  ```
153
99
 
154
- The UI sends SQL to `/ddb/run`, rip-db executes it and returns results in DuckDB's
155
- binary format. The UI has no idea it's talking to rip-db instead of native DuckDB!
156
-
157
- For protocol details, see [PROTOCOL.md](./PROTOCOL.md).
158
-
159
- ## Examples
100
+ ## Building from Source
160
101
 
161
- ### Create a table
102
+ Requires Zig 0.15+ and DuckDB development files.
162
103
 
163
104
  ```bash
164
- curl -X POST http://localhost:4000/sql \
165
- -H "Content-Type: application/json" \
166
- -d '{"sql": "CREATE TABLE users (id INTEGER PRIMARY KEY, name VARCHAR, email VARCHAR)"}'
167
- ```
105
+ cd packages/db
168
106
 
169
- ### Insert data
107
+ # Build for current platform (outputs to lib/{os}-{arch}/duckdb.node)
108
+ zig build -Doptimize=ReleaseFast --prefix .
170
109
 
171
- ```bash
172
- curl -X POST http://localhost:4000/sql \
173
- -H "Content-Type: application/json" \
174
- -d '{"sql": "INSERT INTO users VALUES (?, ?, ?)", "params": [1, "Alice", "alice@example.com"]}'
110
+ # Run tests
111
+ zig build test
175
112
  ```
176
113
 
177
- ### Query with parameters
114
+ Set `DUCKDB_DIR` if DuckDB is not at `/opt/homebrew`:
178
115
 
179
116
  ```bash
180
- curl -X POST http://localhost:4000/sql \
181
- -H "Content-Type: application/json" \
182
- -d '{"sql": "SELECT * FROM users WHERE name LIKE ?", "params": ["%Ali%"]}'
117
+ DUCKDB_DIR=/path/to/duckdb zig build
183
118
  ```
184
119
 
185
- ### Aggregations
120
+ ### Cross-Compiling
121
+
122
+ Zig can cross-compile to other platforms:
186
123
 
187
124
  ```bash
188
- curl -X POST http://localhost:4000/sql \
189
- -H "Content-Type: application/json" \
190
- -d '{"sql": "SELECT COUNT(*) as total, AVG(age) as avg_age FROM users"}'
125
+ zig build -Dtarget=x86_64-linux-gnu # Linux x64
126
+ zig build -Dtarget=aarch64-linux-gnu # Linux ARM64
127
+ zig build -Dtarget=x86_64-windows # Windows x64
128
+ zig build -Dtarget=aarch64-macos # macOS ARM64
129
+ zig build -Dtarget=x86_64-macos # macOS x64
191
130
  ```
192
131
 
193
- ## Response Format
132
+ **Note:** Cross-compiling requires DuckDB libraries for each target platform.
133
+ For production, use CI/CD with native builds on each OS (GitHub Actions runners).
194
134
 
195
- All responses use JSONCompact format (compatible with DuckDB HTTP Server and duck-ui):
135
+ ## Architecture
196
136
 
197
- | Field | Type | Description |
198
- |-------|------|-------------|
199
- | `meta` | object[] | Column metadata: `[{name, type}, ...]` |
200
- | `data` | any[][] | Row data as arrays |
201
- | `rows` | number | Number of rows returned |
202
- | `time` | number | Execution time in seconds |
203
- | `error` | string | Error message (on failure) |
204
-
205
- **Success response:**
206
- ```json
207
- {
208
- "meta": [{"name": "id", "type": "INTEGER"}, {"name": "name", "type": "VARCHAR"}],
209
- "data": [[1, "Alice"], [2, "Bob"]],
210
- "rows": 2,
211
- "time": 0.001
212
- }
213
137
  ```
214
-
215
- **Error response:**
216
- ```json
217
- {"error": "Table 'foo' does not exist"}
138
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
139
+ │ DuckDB UI │ binary │ rip-db │ FFI │ DuckDB │
140
+ │ (Browser) │◀────────▶│ (Bun) │◀────────▶│ (native) │
141
+ └─────────────┘ └─────────────┘ └─────────────┘
218
142
  ```
219
143
 
220
- This format is compatible with [duck-ui](https://demo.duckui.com) and other DuckDB HTTP clients.
221
-
222
- ## Performance
223
-
224
- Our custom Zig-based DuckDB bindings deliver exceptional performance:
144
+ - **Single FFI call** per query (not per-value)
145
+ - **Zero-copy** for numeric columns
146
+ - **Binary serialization in Zig** — no JavaScript overhead
147
+ - **Pre-allocated buffers** — no allocations per query
225
148
 
226
- | Operation | Latency | Throughput |
227
- |-----------|---------|------------|
228
- | Point lookup (WHERE id=?) | 0.09ms | 11,000 qps |
229
- | Range scan (LIMIT 100) | 0.20ms | 5,000 qps |
230
- | Aggregation (COUNT/AVG/MAX) | 0.12ms | 8,400 qps |
231
- | JOIN + GROUP BY | 0.25ms | 4,000 qps |
232
- | INSERT (single row) | 0.13ms | 7,700 qps |
149
+ See [INTERNALS.md](./INTERNALS.md) for protocol details.
233
150
 
234
- ### Comparison to MySQL/PostgreSQL
235
-
236
- | Operation | Our DuckDB | MySQL/PG (localhost) | Speedup |
237
- |-----------|------------|----------------------|---------|
238
- | Point lookup | **0.09ms** | 0.3-1ms | 3-10x faster |
239
- | Range scan | **0.20ms** | 1-5ms | 5-25x faster |
240
- | Aggregation | **0.12ms** | 2-20ms | 17-170x faster |
241
- | JOIN + GROUP BY | **0.25ms** | 5-50ms | 20-200x faster |
242
- | INSERT | **0.13ms** | 0.5-2ms | 4-15x faster |
243
-
244
- **Why so fast?**
245
- - **Zero network latency** — DuckDB runs in-process
246
- - **No connection overhead** — No auth, handshake, or protocol parsing
247
- - **Columnar engine** — DuckDB is optimized for analytical queries
248
- - **Direct FFI** — Zig bindings call DuckDB's C API directly
249
-
250
- ## Architecture & Concurrency
251
-
252
- ### Single Process, High Concurrency
253
-
254
- DuckDB only allows **one process** to access a database file at a time (for write access). This means rip-db runs as a **single process** — it does NOT use rip-server's multi-worker model.
255
-
256
- However, rip-db handles **high concurrency** through two mechanisms:
151
+ ## Files
257
152
 
258
153
  ```
259
- ┌─────────────────────────────────────────────────────────┐
260
- │ rip-db (single process) │
261
- ├─────────────────────────────────────────────────────────┤
262
- Bun HTTP Server (async I/O) │
263
- │ ├─ Request 1 ──┐ │
264
- ├─ Request 2 ──┼── thousands of concurrent │
265
- ├─ Request 3 ──┤ connections via event loop │
266
- └─ Request N ──┘ │
267
- ├─────────────────────────────────────────────────────────┤
268
- │ rip-api (routes, middleware) │
269
- ├─────────────────────────────────────────────────────────┤
270
- │ DuckDB (multi-threaded query engine) │
271
- │ └─ Queries parallelized across CPU cores │
272
- └─────────────────────────────────────────────────────────┘
154
+ packages/db/
155
+ ├── build.zig # Zig build configuration
156
+ ├── src/
157
+ └── duckdb.zig # Native Zig bindings
158
+ ├── lib/
159
+ ├── duckdb.mjs # Bun FFI wrapper
160
+ └── darwin-arm64/
161
+ └── duckdb.node # Compiled library
162
+ ├── bin/
163
+ └── rip-db # CLI entry point
164
+ ├── db.rip # Server implementation
165
+ ├── README.md # This file
166
+ └── INTERNALS.md # Protocol & architecture details
273
167
  ```
274
168
 
275
- **Layer 1: Bun's Async I/O**
276
- - Single JavaScript thread with event loop
277
- - Handles thousands of concurrent HTTP connections
278
- - Non-blocking I/O — while one request waits on DuckDB, others are processed
279
- - No thread-per-request overhead
280
-
281
- **Layer 2: DuckDB's Multi-Threading**
282
- - Queries are parallelized across all CPU cores internally
283
- - MVCC for concurrent reads
284
- - Writes serialized automatically by DuckDB
285
-
286
- ### Why Not Multi-Process?
287
-
288
- | Approach | Works with DuckDB? | Why |
289
- |----------|-------------------|-----|
290
- | Multi-process (rip-server) | ❌ No | DuckDB only allows one process with write access |
291
- | Multi-threaded | ✅ Yes | DuckDB handles this internally |
292
- | Async I/O | ✅ Yes | Bun's event loop handles concurrent connections |
293
-
294
- ### The Result
295
-
296
- Even as a single process, rip-db can handle:
297
- - **Thousands of concurrent connections** (Bun async I/O)
298
- - **Parallel query execution** (DuckDB multi-threading)
299
- - **High throughput** (11,000+ queries/sec on typical hardware)
300
-
301
- This is the correct architecture for DuckDB — single process, high concurrency.
302
-
303
169
  ## Requirements
304
170
 
305
171
  - Bun 1.0+
306
172
  - rip-lang 2.0+
307
173
  - @rip-lang/api 0.5+
308
174
 
309
- ## Building from Source
310
-
311
- To rebuild the native DuckDB bindings (requires Zig 0.15+ and DuckDB):
312
-
313
- ```bash
314
- ./src/build.sh
315
- ```
316
-
317
- This creates `lib/{platform}-{arch}/duckdb.node` for your platform.
318
-
319
175
  ## License
320
176
 
321
177
  MIT
package/bin/rip-db CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { execSync } from 'child_process';
3
+ import { execFileSync } from 'child_process';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
6
 
@@ -8,10 +8,10 @@ const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = dirname(__filename);
9
9
 
10
10
  const dbRip = join(__dirname, '..', 'db.rip');
11
- const args = process.argv.slice(2).join(' ');
11
+ const args = process.argv.slice(2);
12
12
 
13
13
  try {
14
- execSync(`rip ${dbRip} ${args}`, { stdio: 'inherit' });
14
+ execFileSync('rip', [dbRip, ...args], { stdio: 'inherit' });
15
15
  } catch (error) {
16
16
  process.exit(error.status || 1);
17
17
  }
package/build.zig ADDED
@@ -0,0 +1,88 @@
1
+ const std = @import("std");
2
+
3
+ pub fn build(b: *std.Build) void {
4
+ // Use native target by default
5
+ const target = b.standardTargetOptions(.{});
6
+ // Default to ReleaseFast for plain `zig build`, but allow overrides:
7
+ // - `-Doptimize=...`
8
+ // - `--release=fast|safe|small`
9
+ const optimize = b.option(
10
+ std.builtin.OptimizeMode,
11
+ "optimize",
12
+ "Prioritize performance, safety, or binary size",
13
+ ) orelse switch (b.release_mode) {
14
+ .off, .fast => std.builtin.OptimizeMode.ReleaseFast,
15
+ .safe => std.builtin.OptimizeMode.ReleaseSafe,
16
+ .small => std.builtin.OptimizeMode.ReleaseSmall,
17
+ .any => std.builtin.OptimizeMode.ReleaseFast,
18
+ };
19
+ // Note: Recommended explicit build for packaging: zig build -Doptimize=ReleaseFast --prefix .
20
+
21
+ // Get DuckDB directory (defaults to /opt/homebrew)
22
+ const duckdb_dir = std.process.getEnvVarOwned(b.allocator, "DUCKDB_DIR") catch "/opt/homebrew";
23
+
24
+ // Create the module for the library
25
+ const module = b.createModule(.{
26
+ .root_source_file = b.path("src/duckdb.zig"),
27
+ .target = target,
28
+ .optimize = optimize,
29
+ });
30
+
31
+ // Add DuckDB include path
32
+ module.addIncludePath(.{ .cwd_relative = b.fmt("{s}/include", .{duckdb_dir}) });
33
+
34
+ // Build the shared library
35
+ const lib = b.addLibrary(.{
36
+ .linkage = .dynamic,
37
+ .name = "duckdb",
38
+ .root_module = module,
39
+ });
40
+
41
+ // Link against DuckDB
42
+ lib.addLibraryPath(.{ .cwd_relative = b.fmt("{s}/lib", .{duckdb_dir}) });
43
+ lib.linkSystemLibrary("duckdb");
44
+
45
+ // Determine output directory based on target
46
+ const os_tag = target.result.os.tag;
47
+ const arch = target.result.cpu.arch;
48
+
49
+ const os_name: []const u8 = switch (os_tag) {
50
+ .macos => "darwin",
51
+ .linux => "linux",
52
+ .windows => "windows",
53
+ else => "unknown",
54
+ };
55
+
56
+ const arch_name: []const u8 = switch (arch) {
57
+ .aarch64 => "arm64",
58
+ .x86_64 => "x64",
59
+ else => "unknown",
60
+ };
61
+
62
+ // Install to lib/{os}-{arch}/duckdb.node
63
+ const install_dir = b.fmt("lib/{s}-{s}", .{ os_name, arch_name });
64
+ const install_file = b.addInstallFileWithDir(
65
+ lib.getEmittedBin(),
66
+ .{ .custom = install_dir },
67
+ "duckdb.node",
68
+ );
69
+ b.getInstallStep().dependOn(&install_file.step);
70
+
71
+ // Tests
72
+ const test_module = b.createModule(.{
73
+ .root_source_file = b.path("src/duckdb.zig"),
74
+ .target = target,
75
+ .optimize = optimize,
76
+ });
77
+ test_module.addIncludePath(.{ .cwd_relative = b.fmt("{s}/include", .{duckdb_dir}) });
78
+
79
+ const tests = b.addTest(.{
80
+ .root_module = test_module,
81
+ });
82
+ tests.addLibraryPath(.{ .cwd_relative = b.fmt("{s}/lib", .{duckdb_dir}) });
83
+ tests.linkSystemLibrary("duckdb");
84
+
85
+ const run_tests = b.addRunArtifact(tests);
86
+ const test_step = b.step("test", "Run unit tests");
87
+ test_step.dependOn(&run_tests.step);
88
+ }