@rip-lang/db 0.10.0 → 1.0.2

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/db.rip CHANGED
@@ -6,45 +6,45 @@
6
6
  # Fully compatible with the official DuckDB UI!
7
7
  #
8
8
  # Usage:
9
- # rip db.rip <database.duckdb> [--port 4000]
10
- # rip db.rip :memory: --port 4000
9
+ # rip db.rip <database.duckdb> [--port 4213]
10
+ # rip db.rip :memory: --port 4213
11
11
  #
12
- # Then open http://localhost:4000/ for the official DuckDB UI!
12
+ # Then open http://localhost:4213/ for the official DuckDB UI!
13
13
  # (UI assets are proxied from ui.duckdb.org)
14
14
  #
15
- # Endpoints:
16
- # GET / - Official DuckDB UI (proxied from ui.duckdb.org)
17
- # GET /ui - Built-in SQL console (simple, no proxy)
18
- # POST /sql - Execute SQL (JSON API)
19
- # POST /ddb/run - DuckDB UI binary protocol
20
- # GET /health - Health check
21
- # GET /status - Database info
22
- # GET /tables - List tables
23
- # GET /schema/:table - Table schema
15
+ # Architecture:
16
+ # - Pure Bun FFI to DuckDB library (no npm package, no Zig)
17
+ # - Binary serialization in Rip for DuckDB UI protocol
18
+ # - Simple, clean, fast
24
19
  #
25
20
  # ==============================================================================
26
21
 
27
22
  import { get, post, read, start, use } from '@rip-lang/api'
28
23
  import { cors } from '@rip-lang/api/middleware'
29
- import { open } from './lib/duckdb.mjs'
30
24
  import { version as VERSION } from './package.json'
31
- import {
25
+
26
+ # Database access via pure Bun FFI
27
+ duckdb = await import('./lib/duckdb.mjs')
28
+ open = duckdb.open
29
+ duckdbVersion = duckdb.version
30
+
31
+ # Binary serialization for DuckDB UI protocol
32
+ {
32
33
  serializeSuccessResult
33
34
  serializeErrorResult
34
35
  serializeEmptyResult
35
36
  serializeTokenizeResult
36
37
  tokenizeSQL
37
- inferType
38
- } from './lib/duckdb-binary.rip'
38
+ } = await import('./lib/duckdb-binary.rip')
39
39
 
40
- # Enable CORS for duck-ui and other clients
40
+ # Enable CORS for DuckDB UI and other clients
41
41
  use cors preflight: true
42
42
 
43
43
  # Log all requests
44
44
  use (c, next) ->
45
- start = Date.now()
45
+ t0 = Date.now()
46
46
  result = next!
47
- ms = Date.now() - start
47
+ ms = Date.now() - t0
48
48
  console.log "#{c.req.method} #{c.req.path} #{ms}ms"
49
49
  result
50
50
 
@@ -58,7 +58,7 @@ args = process.argv.slice(2)
58
58
  # Handle --help and --version
59
59
  if '--help' in args or '-h' in args
60
60
  console.log """
61
- rip-db v#{VERSION} — DuckDB Server (Bun-native)
61
+ rip-db v#{VERSION} — DuckDB Server (Pure Bun FFI)
62
62
 
63
63
  Usage: rip db.rip [database] [options]
64
64
 
@@ -70,24 +70,17 @@ if '--help' in args or '-h' in args
70
70
  --help Show this help
71
71
  --version Show version
72
72
 
73
- Environment variables (for rip-server):
74
- DB_PATH Path to DuckDB file
75
- DB_PORT Port to listen on
76
-
77
73
  Endpoints:
78
- POST /sql Execute SQL query or statement
79
- GET /health Health check (always ok)
80
- GET /status Database info and table list
81
- GET /tables List all tables
82
- GET /schema/:t Get schema for table t
74
+ GET / Official DuckDB UI
75
+ POST /sql Execute SQL (JSON API)
76
+ POST /ddb/run DuckDB UI binary protocol
77
+ GET /health Health check
78
+ GET /tables List all tables
83
79
 
84
80
  Examples:
85
81
  rip db.rip # In-memory database on port 4213
86
82
  rip db.rip mydb.duckdb # File-based database
87
83
  rip db.rip :memory: --port=8080
88
-
89
- # With rip-server (hot-reloading):
90
- DB_PATH=mydb.duckdb rip-server http db.rip
91
84
  """
92
85
  process.exit(0)
93
86
 
@@ -95,288 +88,277 @@ if '--version' in args or '-v' in args
95
88
  console.log "rip-db v#{VERSION}"
96
89
  process.exit(0)
97
90
 
98
- # Support both env vars (for rip-server) and CLI args (for rip db.rip)
91
+ # Database and port configuration
99
92
  path = process.env.DB_PATH or args.find((a) -> not a.startsWith('-')) or ':memory:'
100
- port = parseInt(process.env.DB_PORT or (args.find((a) -> a.startsWith('--port=')))?.split('=')[1]) or 4213
101
93
 
102
- # Open database
94
+ # Support both --port=N and --port N
95
+ portArg = do ->
96
+ for a, i in args
97
+ return a.split('=')[1] if a.startsWith('--port=')
98
+ return args[i + 1] if a is '--port' and args[i + 1]
99
+ null
100
+ port = parseInt(process.env.DB_PORT or portArg) or 4213
101
+
102
+ # Open database and create persistent connection
103
103
  db = open(path)
104
- console.log "rip-db: database=#{path} (bun-native)"
104
+ conn = db.connect()
105
+ console.log "rip-db: database=#{path} (bun-ffi)"
106
+ console.log "rip-db: DuckDB version=#{duckdbVersion()}"
105
107
 
106
108
  # ==============================================================================
107
109
  # Helpers
108
110
  # ==============================================================================
109
111
 
110
- # Extract column info from result
111
- getColumnInfo = (rows) ->
112
- if not rows?.length > 0
113
- return { columns: [], types: [] }
114
- first = rows[0]
115
- columns = Object.keys(first)
116
- types = columns.map (col) ->
117
- switch typeof val = first[col]
118
- when 'number' then Number.isInteger(val) ? 'INTEGER' : 'DOUBLE'
119
- when 'string' then 'VARCHAR'
120
- when 'boolean' then 'BOOLEAN'
121
- when 'bigint' then 'BIGINT'
122
- else val is null ? 'NULL' : 'UNKNOWN'
123
- { columns, types }
124
-
125
- # Convert row objects to arrays for compact format
126
- rowsToArrays = (rows, columns) ->
127
- rows.map (row) -> columns.map (col) -> row[col]
128
-
129
- # Check if SQL is a SELECT-type query
112
+ # Helper for binary responses
113
+ binaryResponse = (data) ->
114
+ new Response data, headers: { 'Content-Type': 'application/octet-stream' }
115
+
116
+ # Check if SQL is a query that returns results
130
117
  isSelectQuery = (sql) ->
131
118
  upper = sql.trim().toUpperCase()
132
119
  upper.startsWith('SELECT') or
133
120
  upper.startsWith('WITH') or
134
121
  upper.startsWith('SHOW') or
135
122
  upper.startsWith('DESCRIBE') or
123
+ upper.startsWith('EXPLAIN') or
136
124
  upper.startsWith('PRAGMA')
137
125
 
138
- # Execute SQL and return JSONCompact result
126
+ # Log SQL query (truncated if long)
127
+ logSQL = (sql) ->
128
+ preview = sql.replace(/\s+/g, ' ').trim()
129
+ preview = "#{preview.slice(0, 100)}..." if preview.length > 100
130
+ console.log "SQL: #{preview}"
131
+
132
+ # Execute SQL and return result (uses persistent connection)
139
133
  executeSQL = (sql, params = []) ->
134
+ logSQL sql
140
135
  startTime = Date.now()
141
- conn = db.connect()
142
136
 
143
137
  try
144
- rows = if params.length > 0
145
- stmt = conn.prepare(sql)
146
- stmt.query(...params)
147
- else
148
- conn.query(sql)
138
+ rows = await conn.query(sql, params)
139
+ columns = rows.columns or []
149
140
 
150
141
  if isSelectQuery(sql)
151
- { columns, types } = getColumnInfo(rows)
152
- data = rowsToArrays(rows, columns)
153
142
  {
154
- meta: columns.map((name, i) -> { name, type: types[i] })
155
- data: data
156
- rows: data.length
143
+ meta: columns.map((col) -> { name: col.name, type: col.typeName })
144
+ data: rows.map((row) -> columns.map((col) -> row[col.name]))
145
+ rows: rows.length
157
146
  time: (Date.now() - startTime) / 1000
158
147
  }
159
148
  else
160
149
  { meta: [], data: [], rows: 0, time: (Date.now() - startTime) / 1000 }
161
150
  catch err
162
151
  { error: err.message }
163
- finally
164
- conn.close()
165
152
 
166
153
  # ==============================================================================
167
- # Endpoints
154
+ # JSON Endpoints (for custom apps)
168
155
  # ==============================================================================
169
156
 
170
- # POST / — duck-ui compatible (raw SQL in body)
157
+ # POST /sqlExecute SQL with optional parameters
158
+ post '/sql' ->
159
+ contentType = @req.header('content-type') or ''
160
+ if contentType.includes('application/json')
161
+ body = read()
162
+ sql = body?.sql or body
163
+ params = body?.params or []
164
+ else
165
+ sql = read 'body', 'string'
166
+ params = []
167
+ return { error: 'Empty query' } unless sql
168
+ executeSQL sql, params
169
+
170
+ # POST / — Raw SQL in body (duck-ui compatible)
171
171
  post '/' ->
172
172
  sql = read 'body', 'string'
173
- if not sql
174
- return { error: 'Empty query' }
173
+ return { error: 'Empty query' } unless sql
175
174
  executeSQL sql
176
175
 
177
- # POST /sqlJSON body with optional params
178
- post '/sql' ->
179
- { sql, params } = read()
180
- if not sql
181
- return { error: 'Missing required field: sql' }
182
- executeSQL sql, (params or [])
183
-
184
- # GET /health — Simple health check (no DB query)
185
- get '/health', ->
186
- { ok: true }
187
-
188
- # GET /status — Database info and table list
189
- get '/status', ->
190
- conn = db.connect()
191
- try
192
- tables = conn.query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'main'")
193
- {
194
- ok: true
195
- database: path
196
- tables: tables.map((t) -> t.table_name)
197
- time: new Date().toISOString()
198
- }
199
- catch err
200
- { ok: false, error: err.message }
201
- finally
202
- conn.close()
176
+ # GET /healthHealth check
177
+ get '/health' ->
178
+ { status: 'ok', version: VERSION }
203
179
 
204
180
  # GET /tables — List all tables
205
- get '/tables', ->
206
- conn = db.connect()
207
- try
208
- tables = conn.query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'main' ORDER BY table_name")
209
- { ok: true, tables: tables.map((t) -> t.table_name) }
210
- catch err
211
- { ok: false, error: err.message }
212
- finally
213
- conn.close()
181
+ get '/tables' ->
182
+ rows = await conn.query "SELECT table_name FROM information_schema.tables WHERE table_schema = 'main'"
183
+ { tables: rows.map((r) -> r.table_name) }
214
184
 
215
185
  # GET /schema/:table — Get table schema
216
- get '/schema/:table', ->
217
- table = read 'table', 'string!'
218
- conn = db.connect()
186
+ get '/schema/:table' ->
187
+ table = @req.params.table.replace(/[^a-zA-Z0-9_]/g, '') # sanitize
219
188
  try
220
- stmt = conn.prepare("SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = ? ORDER BY ordinal_position")
221
- columns = stmt.query(table)
222
-
223
- if columns.length is 0
224
- { ok: false, error: "Table '#{table}' not found" }
225
- else
226
- { ok: true, table: table, columns: columns }
189
+ rows = await conn.query "DESCRIBE \"#{table}\""
190
+ { schema: rows }
227
191
  catch err
228
- { ok: false, error: err.message }
229
- finally
230
- conn.close()
231
-
232
- # GET /ui — Built-in SQL console
233
- get '/ui', -> new Response Bun.file(import.meta.dir + '/db.html')
192
+ { error: err.message }
234
193
 
235
194
  # ==============================================================================
236
- # DuckDB UI Protocol — Binary endpoints for official UI compatibility
237
- # ==============================================================================
238
- #
239
- # These endpoints implement the binary protocol used by DuckDB's official UI.
240
- # This allows you to use the beautiful DuckDB UI with rip-db!
241
- #
242
- # The official UI expects these endpoints:
243
- # POST /ddb/run - Execute SQL (binary response)
244
- # POST /ddb/interrupt - Cancel running query
245
- # POST /ddb/tokenize - Tokenize SQL for syntax highlighting
246
- # GET /info - Server version info
247
- #
248
- # To use the DuckDB UI:
249
- # 1. Start rip-db: rip-db mydb.duckdb --port 4000
250
- # 2. Open: http://localhost:4000 (UI proxied from ui.duckdb.org)
251
- #
195
+ # DuckDB UI Binary Protocol
252
196
  # ==============================================================================
253
197
 
198
+ # Remote UI URL for proxying assets
199
+ UI_REMOTE_URL = 'https://ui.duckdb.org'
200
+
201
+ # Helper to extract parameters from DuckDB UI headers (base64 encoded)
202
+ # The official C++ extension passes all params as strings (VARCHAR).
203
+ # DuckDB handles implicit casting to target types.
204
+ getUIParams = (req) ->
205
+ count = parseInt(req.header('x-duckdb-ui-parameter-count') or '0')
206
+ return [] if count is 0
207
+
208
+ params = []
209
+ for i in [0...count]
210
+ encoded = req.header("x-duckdb-ui-parameter-value-#{i}")
211
+ if encoded
212
+ try
213
+ decoded = atob(encoded)
214
+ # The UI sends literal "null" for NULL values
215
+ if decoded is 'null'
216
+ params.push null
217
+ else
218
+ params.push decoded
219
+ catch
220
+ params.push ''
221
+ else
222
+ params.push null
223
+ params
224
+
254
225
  # POST /ddb/run — Execute SQL and return binary result (DuckDB UI protocol)
255
226
  post '/ddb/run' ->
256
227
  try
257
228
  sql = read 'body', 'string'
258
- if not sql
259
- return binaryResponse serializeErrorResult 'Empty query'
229
+ return binaryResponse serializeErrorResult 'Empty query' unless sql
260
230
 
261
- # Parse row limit from DuckDB UI header
231
+ logSQL sql
262
232
  rowLimit = parseInt(@req.header('x-duckdb-ui-result-row-limit') or '10000')
233
+ params = getUIParams(@req)
263
234
 
264
- # Execute query
265
- conn = db.connect()
266
- try
267
- rows = conn.query sql
235
+ rows = await conn.query sql, params
236
+ columns = rows.columns or []
268
237
 
269
- # Build column metadata from first row
270
- columns = []
271
- if rows?.length > 0
272
- first = rows[0]
273
- for key of first
274
- columns.push { name: key, type: inferType first[key] }
238
+ # Build column info for serialization
239
+ colInfo = columns.map (col) -> { name: col.name, type: col.typeName }
275
240
 
276
- # Limit rows and convert to array format
277
- limitedRows = rows?.slice(0, rowLimit) or []
278
- arrayRows = limitedRows.map (row) ->
279
- columns.map (col) -> row[col.name]
241
+ # Convert rows to arrays (ensure undefined becomes null for serialization)
242
+ limitedRows = rows.slice(0, rowLimit)
243
+ arrayRows = limitedRows.map (row) ->
244
+ columns.map (col) ->
245
+ val = row[col.name]
246
+ if val is undefined then null else val
280
247
 
281
- binaryResponse serializeSuccessResult columns, arrayRows
282
- finally
283
- conn.close()
248
+ binaryResponse serializeSuccessResult colInfo, arrayRows
284
249
 
285
250
  catch err
286
- console.error "POST /ddb/run error:", err?.message
287
- binaryResponse serializeErrorResult err?.message or 'Unknown error'
251
+ msg = err?.message or 'Unknown error'
252
+ # Handle _duckdb_ui catalog queries - return empty success instead of error
253
+ if msg.includes('_duckdb_ui')
254
+ return binaryResponse serializeSuccessResult [], []
255
+ console.error "POST /ddb/run error:", msg
256
+ binaryResponse serializeErrorResult msg
288
257
 
289
258
  # POST /ddb/interrupt — Cancel running query
290
- post '/ddb/interrupt', ->
291
- # In a real implementation, this would cancel the running query
292
- # For now, just return empty result
259
+ post '/ddb/interrupt' ->
293
260
  binaryResponse serializeEmptyResult()
294
261
 
295
262
  # POST /ddb/tokenize — Tokenize SQL for syntax highlighting
296
263
  post '/ddb/tokenize' ->
297
264
  sql = read 'body', 'string'
298
- tokens = tokenizeSQL sql
299
- binaryResponse serializeTokenizeResult tokens
265
+ tokens = tokenizeSQL(sql or '')
266
+ binaryResponse serializeTokenizeResult(tokens)
267
+
268
+ # ==============================================================================
269
+ # DuckDB UI Compatibility Endpoints
270
+ # (Responses match the official DuckDB UI extension exactly)
271
+ # ==============================================================================
300
272
 
301
- # GET /version — Tell UI we're running in local/HTTP mode (not WASM)
273
+ # UI extension version — must match what /config advertises for our DuckDB version
274
+ UI_EXT_VERSION = '150-5582dfaffc'
275
+
276
+ # Detect platform for DuckDB headers
277
+ PLATFORM = switch "#{process.platform}_#{process.arch}"
278
+ when 'darwin_arm64' then 'osx_arm64'
279
+ when 'darwin_x64' then 'osx_amd64'
280
+ when 'linux_arm64' then 'linux_arm64'
281
+ when 'linux_x64' then 'linux_amd64'
282
+ else "#{process.platform}_#{process.arch}"
283
+
284
+ # GET /version — Tell UI we're running in host/HTTP mode (not WASM)
302
285
  get '/version' ->
303
- { origin: 'local', version: '139-944c08a214' }
286
+ { origin: 'host', version: "main@e6517d259ec5f27ab713e5755a29d775a7750dc5" }
304
287
 
305
- # GET /localToken — Return empty token for local mode (no MotherDuck)
288
+ # GET /localToken — Return 401 to skip MotherDuck auth (matches official)
306
289
  get '/localToken' ->
307
- new Response '', { status: 200, headers: { 'Content-Type': 'text/plain' } }
290
+ new Response null, { status: 401 }
308
291
 
309
- # GET /info — Server version info (DuckDB UI checks this)
310
- # The UI checks X-DuckDB-UI-Extension-Version to decide HTTP vs WASM mode
311
- # Version format must match desiredDuckDBUIExtensionVersions in /config
292
+ # GET /info — Server version info (headers only, empty body — matches official)
312
293
  get '/info' ->
313
294
  @body '', 200,
314
295
  'Access-Control-Allow-Origin': '*'
315
- 'X-DuckDB-Version': '1.4.1'
316
- 'X-DuckDB-Platform': 'rip-db'
317
- 'X-DuckDB-UI-Extension-Version': '139-944c08a214'
318
-
319
- # Helper to create binary response
320
- binaryResponse = (buffer) ->
321
- new Response buffer,
322
- headers: { 'Content-Type': 'application/octet-stream' }
323
-
324
- # ==============================================================================
325
- # DuckDB UI Proxy — Serve official UI assets from ui.duckdb.org
326
- # ==============================================================================
327
- #
328
- # The official DuckDB UI is a React app hosted at https://ui.duckdb.org
329
- # We proxy these assets so you can use the beautiful UI with rip-db!
330
- #
331
- # The UI makes API requests to relative URLs (/ddb/run, /ddb/tokenize, etc.)
332
- # which we handle above. This proxy just serves the static UI assets.
333
- #
334
- # ==============================================================================
335
-
336
- UI_REMOTE_URL = 'https://ui.duckdb.org'
296
+ 'Content-Type': 'text/plain'
297
+ 'X-DuckDB-Version': duckdbVersion()
298
+ 'X-DuckDB-Platform': PLATFORM
299
+ 'X-DuckDB-UI-Extension-Version': UI_EXT_VERSION
300
+
301
+ # GET /config — Proxy from ui.duckdb.org with DuckDB version headers
302
+ # The UI checks these HEADERS (not just /info) to decide HTTP vs WASM mode
303
+ get '/config' ->
304
+ try
305
+ response = fetch! "#{UI_REMOTE_URL}/config",
306
+ headers: { 'User-Agent': "rip-db/#{VERSION}" }
307
+ body = response.text!
308
+ new Response body,
309
+ status: response.status
310
+ headers:
311
+ 'Content-Type': 'application/json'
312
+ 'X-DuckDB-Version': duckdbVersion()
313
+ 'X-DuckDB-Platform': PLATFORM
314
+ 'X-DuckDB-UI-Extension-Version': UI_EXT_VERSION
315
+ catch
316
+ new Response JSON.stringify({
317
+ globalApiUrl: "http://localhost:#{port}"
318
+ apiUrl: "http://localhost:#{port}"
319
+ momApiUrl: "http://localhost:#{port}/mom"
320
+ logging: { enabled: false }
321
+ datadog: { applicationId: '', clientToken: '', site: '', env: 'local', allowedTracingOrigins: [] }
322
+ defaultFeatureValues: {}
323
+ desiredDuckDBUIExtensionVersions: { "#{duckdbVersion()}": UI_EXT_VERSION }
324
+ }),
325
+ headers:
326
+ 'Content-Type': 'application/json'
327
+ 'X-DuckDB-Version': duckdbVersion()
328
+ 'X-DuckDB-Platform': PLATFORM
329
+ 'X-DuckDB-UI-Extension-Version': UI_EXT_VERSION
337
330
 
338
- # GET /localEvents — Server-sent events for catalog updates
331
+ # GET /localEvents — SSE stream with keepalive (matches official extension)
332
+ # The official blocks 5s per iteration, sends ":\r\r" keepalive if no events.
339
333
  get '/localEvents' ->
340
- # Create SSE response with keep-alive
341
- encoder = new TextEncoder()
342
- intervalId = null
343
-
344
- stream = new ReadableStream
334
+ keepalive = null
335
+ new Response new ReadableStream({
345
336
  start: (controller) ->
346
- # Send initial connection event
347
- controller.enqueue encoder.encode "event: connected\ndata: {}\n\n"
348
-
349
- # Keep connection alive with periodic heartbeats
350
- intervalId = setInterval ->
337
+ # Send keepalive every 5 seconds (SSE comment, matches official ":\r\r")
338
+ keepalive = setInterval ->
351
339
  try
352
- controller.enqueue encoder.encode ": heartbeat\n\n"
340
+ controller.enqueue ":\r\r"
353
341
  catch
354
- clearInterval intervalId
355
- , 30000
356
-
342
+ clearInterval keepalive
343
+ , 5000
357
344
  cancel: ->
358
- clearInterval intervalId if intervalId
359
-
360
- new Response stream,
345
+ clearInterval keepalive if keepalive
346
+ }),
361
347
  headers:
362
348
  'Content-Type': 'text/event-stream'
363
- 'Cache-Control': 'no-cache'
364
- 'Connection': 'keep-alive'
349
+
350
+ # GET /mom/* — MotherDuck API stubs (prevent OAuth, return 401)
351
+ get '/mom/*' ->
352
+ new Response null, { status: 401 }
365
353
 
366
354
  # GET /* — Proxy UI assets from ui.duckdb.org (catch-all, must be last)
367
355
  get '/*' ->
368
- path = @req.path
369
-
370
- # Skip if this is one of our API endpoints (shouldn't reach here, but safety)
371
- return { error: 'Not found' } if path in ['/health', '/status', '/tables', '/info', '/ui']
356
+ reqPath = @req.path
372
357
 
373
358
  try
374
- # Fetch from remote UI server
375
- response = fetch! "#{UI_REMOTE_URL}#{path}",
376
- headers:
377
- 'User-Agent': "rip-db/#{VERSION}"
359
+ response = fetch! "#{UI_REMOTE_URL}#{reqPath}",
360
+ headers: { 'User-Agent': "rip-db/#{VERSION}" }
378
361
 
379
- # Get response body and content type
380
362
  body = response.arrayBuffer!
381
363
  contentType = response.headers.get('Content-Type') or 'application/octet-stream'
382
364
 
@@ -384,18 +366,17 @@ get '/*' ->
384
366
  headers =
385
367
  'Content-Type': contentType
386
368
  'Cache-Control': response.headers.get('Cache-Control') or 'public, max-age=3600'
369
+ # COOP/COEP required for SharedArrayBuffer (DuckDB WASM fallback)
370
+ 'Cross-Origin-Opener-Policy': 'same-origin'
371
+ 'Cross-Origin-Embedder-Policy': 'credentialless'
387
372
 
388
- # For /config endpoint, add DuckDB version headers (UI uses these to detect mode)
389
- if path is '/config'
390
- headers['X-DuckDB-Version'] = '1.4.1'
391
- headers['X-DuckDB-Platform'] = 'rip-db'
392
- headers['X-DuckDB-UI-Extension-Version'] = '139-944c08a214'
393
-
394
- new Response body, { status: response.status, headers }
373
+ new Response body,
374
+ status: response.status
375
+ headers: headers
395
376
 
396
377
  catch err
397
- console.error "Proxy error for #{path}:", err?.message
398
- new Response "Failed to fetch UI asset: #{path}", { status: 502 }
378
+ console.error "Proxy error for #{reqPath}:", err?.message
379
+ new Response "Failed to fetch UI asset: #{reqPath}", { status: 502 }
399
380
 
400
381
  # ==============================================================================
401
382
  # Start Server
@@ -404,5 +385,4 @@ get '/*' ->
404
385
  start port: port
405
386
 
406
387
  console.log "rip-db: listening on http://localhost:#{port}"
407
- console.log "rip-db: Official DuckDB UI available at http://localhost:#{port}/"
408
- console.log "rip-db: Built-in console at http://localhost:#{port}/ui"
388
+ console.log "rip-db: DuckDB UI available at http://localhost:#{port}/"