From f701e2e4561aca371290cedbf819d7fe475c1b01 Mon Sep 17 00:00:00 2001 From: Kush Bisen Date: Thu, 9 Apr 2026 16:53:53 +0200 Subject: [PATCH] repo: harden runtime and simplify repository layout --- .github/workflows/fast-pr.yml | 18 +- GETTING_STARTED.md | 264 +--- README.md | 11 +- START_HERE.md | 79 +- docs/COMPLETE_SOLUTION.md | 10 - docs/DOCUMENTATION_INDEX.md | 26 +- docs/FINAL_TEST.md | 100 -- docs/FIXES_APPLIED.md | 98 -- docs/HTTP_API.md | 847 ----------- docs/HTTP_API_IMPLEMENTATION.md | 562 -------- docs/LIVE_STREAMING_READY.md | 326 ----- docs/MVP_QUICKSTART.md | 841 ----------- docs/QUICKSTART_HTTP_API.md | 285 ---- docs/QUICK_REFERENCE.md | 39 +- docs/README.md | 10 +- docs/README_HTTP_API.md | 465 ------ docs/RUNTIME_FIX_SUMMARY.md | 117 -- docs/SETUP_GUIDE.md | 530 ------- docs/TEST_HISTORICAL.md | 130 -- docs/TIMING_GUIDE.md | 220 --- janus-dashboard/.gitignore | 24 - janus-dashboard/README.md | 47 - janus-dashboard/index.html | 13 - janus-dashboard/package-lock.json | 1490 -------------------- janus-dashboard/package.json | 24 - janus-dashboard/public/vite.svg | 1 - janus-dashboard/src/App.svelte | 336 ----- janus-dashboard/src/app.css | 79 -- janus-dashboard/src/assets/svelte.svg | 1 - janus-dashboard/src/lib/Query.svelte | 56 - janus-dashboard/src/lib/StreamChart.svelte | 309 ---- janus-dashboard/src/main.ts | 9 - janus-dashboard/svelte.config.js | 8 - janus-dashboard/tsconfig.app.json | 21 - janus-dashboard/tsconfig.json | 7 - janus-dashboard/tsconfig.node.json | 26 - janus-dashboard/vite.config.ts | 7 - src/api/janus_api.rs | 55 +- src/execution/historical_executor.rs | 15 - src/http/server.rs | 11 +- src/querying/kolibrie_adapter.rs | 44 - src/querying/main.rs | 43 - src/querying/mod.rs | 2 - src/registry/query_registry.rs | 16 + src/sources/mod.rs | 1 - src/sources/stream_ingestion_pipeline.rs | 41 - tests/http_server_integration_test.rs | 3 +- tests/janus_api_integration_test.rs | 47 + 48 files changed, 234 insertions(+), 7480 deletions(-) delete mode 100644 docs/COMPLETE_SOLUTION.md delete mode 100644 docs/FINAL_TEST.md delete mode 100644 docs/FIXES_APPLIED.md delete mode 100644 docs/HTTP_API.md delete mode 100644 docs/HTTP_API_IMPLEMENTATION.md delete mode 100644 docs/LIVE_STREAMING_READY.md delete mode 100644 docs/MVP_QUICKSTART.md delete mode 100644 docs/QUICKSTART_HTTP_API.md delete mode 100644 docs/README_HTTP_API.md delete mode 100644 docs/RUNTIME_FIX_SUMMARY.md delete mode 100644 docs/SETUP_GUIDE.md delete mode 100644 docs/TEST_HISTORICAL.md delete mode 100644 docs/TIMING_GUIDE.md delete mode 100644 janus-dashboard/.gitignore delete mode 100644 janus-dashboard/README.md delete mode 100644 janus-dashboard/index.html delete mode 100644 janus-dashboard/package-lock.json delete mode 100644 janus-dashboard/package.json delete mode 100644 janus-dashboard/public/vite.svg delete mode 100644 janus-dashboard/src/App.svelte delete mode 100644 janus-dashboard/src/app.css delete mode 100644 janus-dashboard/src/assets/svelte.svg delete mode 100644 janus-dashboard/src/lib/Query.svelte delete mode 100644 janus-dashboard/src/lib/StreamChart.svelte delete mode 100644 janus-dashboard/src/main.ts delete mode 100644 janus-dashboard/svelte.config.js delete mode 100644 janus-dashboard/tsconfig.app.json delete mode 100644 janus-dashboard/tsconfig.json delete mode 100644 janus-dashboard/tsconfig.node.json delete mode 100644 janus-dashboard/vite.config.ts delete mode 100644 src/querying/kolibrie_adapter.rs delete mode 100644 src/querying/main.rs delete mode 100644 src/sources/stream_ingestion_pipeline.rs diff --git a/.github/workflows/fast-pr.yml b/.github/workflows/fast-pr.yml index c538132..d7d7a3b 100644 --- a/.github/workflows/fast-pr.yml +++ b/.github/workflows/fast-pr.yml @@ -48,8 +48,8 @@ jobs: - name: Run Clippy run: cargo clippy --all-targets --all-features -- -D warnings - test: - name: Test Suite + smoke: + name: Integration Smoke runs-on: ubuntu-latest steps: - name: Checkout code @@ -61,8 +61,14 @@ jobs: - name: Cache Rust build artifacts uses: Swatinem/rust-cache@v2 - - name: Run tests - run: cargo test --all-features --verbose + - name: Run Janus API integration smoke test + run: cargo test --test janus_api_integration_test --all-features - - name: Run doc tests - run: cargo test --doc --all-features --verbose + - name: Run HTTP server integration smoke test + run: cargo test --test http_server_integration_test --all-features + + - name: Run stream bus CLI smoke test + run: cargo test --test stream_bus_cli_test --all-features + + - name: Build HTTP client example + run: cargo build --example http_client_example --all-features diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 87b6f30..e624174 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -1,265 +1,89 @@ # Getting Started with Janus -Welcome to Janus! This guide will help you get up and running with the Janus RDF Stream Processing Engine. - -## What is Janus? - -Janus is a hybrid engine for unified Live and Historical RDF Stream Processing, written in Rust. It allows you to seamlessly process both historical RDF data stored in databases and live RDF streams in real-time using a single query language. +Janus is a Rust engine for querying historical and live RDF data through one +Janus-QL model and one HTTP/WebSocket API. ## Prerequisites -Before you begin, ensure you have the following installed: - -- **Rust** (1.70.0 or later) - [Install from rustup.rs](https://rustup.rs/) -- **Cargo** (comes with Rust) -- **Git** (for cloning the repository) -- **Docker** (optional, for running RDF stores) - -### Installing Rust +- Rust stable with Cargo +- Docker and Docker Compose if you want the MQTT-backed replay flow -If you don't have Rust installed, run: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` +## Fastest Working Path -After installation, restart your terminal and verify: +### 1. Build and test ```bash -rustc --version -cargo --version +make build +make test ``` -## Quick Start - -### 1. Clone the Repository +### 2. Start the HTTP server ```bash -git clone https://github.com/yourusername/janus.git -cd janus +cargo run --bin http_server -- --host 127.0.0.1 --port 8080 --storage-dir ./data/storage ``` -### 2. Build the Project +Verify it is up: ```bash -# Debug build (faster compilation, slower execution) -cargo build - -# Release build (slower compilation, faster execution) -cargo build --release +curl http://127.0.0.1:8080/health ``` -### 3. Run Tests +### 3. Exercise the API -Verify everything is working correctly: +The quickest end-to-end client is the example binary: ```bash -cargo test +cargo run --example http_client_example ``` -### 4. Run the Example +That example covers query registration, start, stop, replay control, and +WebSocket result consumption. -```bash -cargo run --example basic -``` - -You should see output explaining the steps Janus takes to process RDF streams. +## Optional Local Demo UI -### 5. Run the CLI Tool +This repository keeps a small static demo at +`examples/demo_dashboard.html` for manual browser testing. -```bash -cargo run -``` +The maintained Svelte dashboard lives in the separate +`SolidLabResearch/janus-dashboard` repository. -This will display the version and basic information about Janus. +## Main Binaries -## Project Structure +- `http_server`: REST and WebSocket API for query lifecycle and replay control +- `stream_bus_cli`: replay and ingestion CLI for RDF event files -Understanding the project structure will help you navigate the codebase: - -``` -janus/ -├── src/ # Source code -│ ├── lib.rs # Library entry point -│ ├── main.rs # Binary entry point -│ ├── core/ # Core engine logic (to be implemented) -│ ├── store/ # RDF store adapters (to be implemented) -│ ├── stream/ # Stream processing (to be implemented) -│ ├── query/ # Query engine (to be implemented) -│ └── config/ # Configuration (to be implemented) -├── examples/ # Usage examples -│ └── basic.rs # Basic example -├── tests/ # Integration tests -│ └── integration_test.rs -├── benches/ # Performance benchmarks -├── fuseki-config/ # Apache Jena Fuseki configuration -├── Cargo.toml # Project metadata and dependencies -├── Makefile # Common development tasks -└── README.md # Project overview -``` - -## Using the Makefile - -The project includes a `Makefile` with common development tasks: +## Common Commands ```bash -# See all available commands -make help - -# Build the project make build - -# Run tests +make release make test - -# Format code make fmt - -# Run linter +make fmt-check make lint - -# Run all checks make check - -# Generate documentation -make doc - -# Run benchmarks -make bench - -# Start Docker services (Oxigraph + Jena) -make docker-start - -# Stop Docker services -make docker-stop -``` - -## Development Workflow - -### 1. Set Up Your Development Environment - -```bash -# Install development tools -make setup - -# Verify everything is installed -make setup-check -``` - -### 2. Make Changes - -Edit files in the `src/` directory. The main areas to implement are: - -- `src/core/` - Core engine logic -- `src/store/` - RDF store adapters -- `src/stream/` - Stream processing -- `src/query/` - Query parsing and execution - -### 3. Test Your Changes - -```bash -# Run tests -cargo test - -# Run tests with output -cargo test -- --nocapture - -# Run specific test -cargo test test_name -``` - -### 4. Format and Lint - -```bash -# Format code -cargo fmt - -# Check formatting -cargo fmt --check - -# Run linter -cargo clippy +make ci-check ``` -### 5. Build and Run - -```bash -# Build -cargo build - -# Run -cargo run +## Repository Layout -# Run example -cargo run --example basic -``` - -## Working with RDF Stores - -Janus is designed to work with multiple RDF stores. Here's how to set them up for development: - -### Oxigraph - -Start Oxigraph using Docker: - -```bash -docker run -d -p 7878:7878 --name oxigraph-server oxigraph/oxigraph -``` - -Or use the Makefile: - -```bash -make docker-oxigraph -``` +- `src/api`: query lifecycle orchestration +- `src/http`: REST and WebSocket server +- `src/parsing`: Janus-QL parsing +- `src/execution`: historical execution +- `src/stream`: live stream processing +- `src/storage`: segmented RDF storage +- `src/bin`: executable binaries +- `examples`: runnable examples and a minimal static demo +- `tests`: integration coverage +- `docs`: current docs plus older design notes -Oxigraph will be available at `http://localhost:7878` +## Where to Read Next -### Apache Jena Fuseki - -Start Jena Fuseki using Docker: - -```bash -docker run -d -p 3030:3030 --platform linux/amd64 \ - -v $(pwd)/fuseki-config:/fuseki/configuration \ - -v $(pwd)/fuseki-config/shiro.ini:/fuseki/shiro.ini \ - --name jena-server stain/jena-fuseki -``` - -Or use the Makefile: - -```bash -make docker-jena -``` - -Fuseki will be available at `http://localhost:3030` - -### Starting Both Services - -```bash -make docker-start -``` - -### Stopping Services - -```bash -make docker-stop -``` - -## Adding Dependencies - -To add a new dependency, edit `Cargo.toml`: - -```toml -[dependencies] -# Add your dependency here -tokio = { version = "1.35", features = ["full"] } -``` - -Then run: - -```bash -cargo build -``` +- `README.md` +- `START_HERE.md` +- `docs/DOCUMENTATION_INDEX.md` ## Common Rust Commands diff --git a/README.md b/README.md index ff2c4ff..15637fc 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,15 @@ This example demonstrates: - replay control - WebSocket result consumption +### Frontend Boundary + +The maintained web dashboard lives in the separate +`SolidLabResearch/janus-dashboard` repository. + +This repository keeps a small static demo at +[`examples/demo_dashboard.html`](./examples/demo_dashboard.html) for manual API +testing, but frontend development should happen in the dedicated dashboard repo. + ## Development ### Common Commands @@ -129,7 +138,7 @@ The repository includes runnable examples under [`examples/`](./examples), inclu - [`examples/http_client_example.rs`](./examples/http_client_example.rs) - [`examples/comparator_demo.rs`](./examples/comparator_demo.rs) -- [`examples/demo_dashboard.html`](./examples/demo_dashboard.html) +- [`examples/demo_dashboard.html`](./examples/demo_dashboard.html) for a minimal local demo ## Project Layout diff --git a/START_HERE.md b/START_HERE.md index 091c8fb..6fc541b 100644 --- a/START_HERE.md +++ b/START_HERE.md @@ -1,75 +1,30 @@ -# Janus HTTP API - START HERE +# Janus Start Here -## Quick Start (30 seconds) +## Quick Start ```bash -# 1. Setup (one time) - ./scripts/test_setup.sh -# 2. Start MQTT docker-compose up -d mosquitto - -# 3. Start Server -cargo run --bin http_server - -# 4. Open Dashboard -open examples/demo_dashboard.html +cargo run --bin http_server -- --host 127.0.0.1 --port 8080 --storage-dir ./data/storage +curl http://127.0.0.1:8080/health ``` -Then click: **Start Replay** → **Start Query** - -## What This Does - -1. **Start Replay**: Loads RDF data from `data/sensors.nq`, publishes to MQTT, stores locally -2. **Start Query**: Executes a JanusQL query, streams results via WebSocket to dashboard - -## Documentation - -- **QUICK_REFERENCE.md** - One-page cheat sheet -- **RUNTIME_FIX_SUMMARY.md** - How the runtime issue was fixed -- **COMPLETE_SOLUTION.md** - Full implementation details -- **SETUP_GUIDE.md** - Detailed setup instructions -- **README_HTTP_API.md** - Complete API documentation -- **FINAL_TEST.md** - Verification steps - -## Key Points - -✅ **No more runtime panics** - Fixed by spawning StreamBus in separate thread -✅ **Correct JanusQL syntax** - All examples updated to match parser -✅ **MQTT integration** - Full broker setup with Docker Compose -✅ **Two-button demo** - Interactive dashboard for easy testing -✅ **Production-ready** - Stable, tested, documented - -⚠️ **Known limitation**: Replay metrics show status but not event counts (acceptable trade-off) - -## Troubleshooting +In another terminal, run: ```bash -# Server won't start (port in use) -lsof -ti:8080 | xargs kill -9 - -# MQTT not running -docker-compose up -d mosquitto - -# Check if working -curl http://localhost:8080/health +cargo run --example http_client_example ``` -## Success Indicators - -When everything works correctly: -1. Server starts with clean output (no panics) -2. Dashboard shows "Connected to Janus HTTP API server" -3. Replay button → Status changes to "Running" -4. Query button → WebSocket connects, results appear -5. Results tagged as "historical" or "live" - -## Need Help? +## What To Use -1. Read **QUICK_REFERENCE.md** for common commands -2. Check **FINAL_TEST.md** for verification steps -3. See **RUNTIME_FIX_SUMMARY.md** if you see panics -4. Review **SETUP_GUIDE.md** for detailed instructions +- `http_server` is the main backend entry point +- `stream_bus_cli` is the ingestion and replay CLI +- `examples/demo_dashboard.html` is a minimal manual demo +- the maintained Svelte dashboard lives in the separate `janus-dashboard` repository ---- +## Current Docs -**Everything is ready. Just run the Quick Start commands above!** 🚀 +- `README.md` +- `GETTING_STARTED.md` +- `docs/DOCUMENTATION_INDEX.md` +- `docs/HTTP_API_CURRENT.md` +- `docs/QUICK_REFERENCE.md` diff --git a/docs/COMPLETE_SOLUTION.md b/docs/COMPLETE_SOLUTION.md deleted file mode 100644 index 0404f5d..0000000 --- a/docs/COMPLETE_SOLUTION.md +++ /dev/null @@ -1,10 +0,0 @@ - -## Known Limitations - -### Replay Metrics -Currently, the `/api/replay/status` endpoint shows basic status (running/not running, elapsed time) but not detailed event counts. This is because `StreamBus` creates its own Tokio runtime which conflicts with the async HTTP server runtime. - -**Workaround**: Check storage directory size or MQTT topic activity for progress indication. - -**Future Fix**: Refactor `StreamBus` to accept an external runtime or use shared atomic counters. - diff --git a/docs/DOCUMENTATION_INDEX.md b/docs/DOCUMENTATION_INDEX.md index 26713f7..c374401 100644 --- a/docs/DOCUMENTATION_INDEX.md +++ b/docs/DOCUMENTATION_INDEX.md @@ -5,11 +5,14 @@ This is the shortest path to understanding the current Janus implementation. ## Core Reading Order 1. [../README.md](../README.md) -2. [JANUSQL.md](./JANUSQL.md) -3. [QUERY_EXECUTION.md](./QUERY_EXECUTION.md) -4. [BASELINES.md](./BASELINES.md) -5. [HTTP_API_CURRENT.md](./HTTP_API_CURRENT.md) -6. [ANOMALY_DETECTION.md](./ANOMALY_DETECTION.md) +2. [../GETTING_STARTED.md](../GETTING_STARTED.md) +3. [../START_HERE.md](../START_HERE.md) +4. [JANUSQL.md](./JANUSQL.md) +5. [QUERY_EXECUTION.md](./QUERY_EXECUTION.md) +6. [BASELINES.md](./BASELINES.md) +7. [HTTP_API_CURRENT.md](./HTTP_API_CURRENT.md) +8. [ANOMALY_DETECTION.md](./ANOMALY_DETECTION.md) +9. [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) ## What Each File Covers @@ -36,22 +39,27 @@ This is the shortest path to understanding the current Janus implementation. - current REST endpoints - WebSocket result flow - request and response shapes - - `baseline_mode` registration fallback + - persisted query lifecycle status - [ANOMALY_DETECTION.md](./ANOMALY_DETECTION.md) - when extension functions are enough - when baseline state helps - recommended query patterns +- [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) + - common local commands + - query lifecycle endpoints + - replay endpoints + - smoke-test flow + ## Legacy Material The following files remain useful as background, but they are not the main entrypoint for the current code: - [ARCHITECTURE.md](./ARCHITECTURE.md) - [EXECUTION_ARCHITECTURE.md](./EXECUTION_ARCHITECTURE.md) -- [HTTP_API.md](./HTTP_API.md) -- [README_HTTP_API.md](./README_HTTP_API.md) -- [SETUP_GUIDE.md](./SETUP_GUIDE.md) +- [MVP_ARCHITECTURE.md](./MVP_ARCHITECTURE.md) +- [MVP_TODO.md](./MVP_TODO.md) ## Related Code diff --git a/docs/FINAL_TEST.md b/docs/FINAL_TEST.md deleted file mode 100644 index 6bb1d86..0000000 --- a/docs/FINAL_TEST.md +++ /dev/null @@ -1,100 +0,0 @@ -# Final Test Verification - -## Issue Fixed - -The runtime conflict has been resolved. The server no longer panics with: -``` -Cannot drop a runtime in a context where blocking is not allowed -``` - -## What Changed - -The `start_replay` endpoint now spawns `StreamBus` in a separate blocking thread, avoiding nested Tokio runtime conflicts. - -## Test Steps - -### 1. Kill any existing server -```bash -killall http_server 2>/dev/null || true -lsof -ti:8080 | xargs kill -9 2>/dev/null || true -``` - -### 2. Start MQTT -```bash -docker-compose up -d mosquitto -``` - -### 3. Start Server -```bash -cargo run --bin http_server -# Should see clean startup without panics -``` - -### 4. Test Health -```bash -curl http://localhost:8080/health -# Should return: {"message":"Janus HTTP API is running"} -``` - -### 5. Test Dashboard -```bash -open examples/demo_dashboard.html -# Click "Start Replay" - should work without errors -# Click "Start Query" - should connect WebSocket -``` - -## Expected Behavior - -✅ Server starts without panics -✅ Health endpoint responds -✅ Replay can be started -✅ Queries can be registered and started -✅ WebSocket connections work -⚠️ Replay metrics show basic status only (elapsed time, not event counts) - -## Current Limitation - -The `/api/replay/status` endpoint shows: -- `is_running`: true/false -- `elapsed_seconds`: actual elapsed time -- Event counts: always 0 (due to thread isolation) - -This is acceptable for MVP - the replay IS working, we just can't track detailed metrics from the HTTP API. - -## Verification Commands - -```bash -# 1. Start server (in terminal 1) -cargo run --bin http_server - -# 2. Health check (in terminal 2) -curl http://localhost:8080/health - -# 3. List queries -curl http://localhost:8080/api/queries - -# 4. Register query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{"query_id":"test","janusql":"PREFIX ex: REGISTER RStream ex:o AS SELECT ?s ?p ?o FROM NAMED WINDOW ex:w ON STREAM ex:s [START 1 END 999999999] WHERE { WINDOW ex:w { ?s ?p ?o . } }"}' - -# 5. Start replay (will run in background) -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{"input_file":"data/sensors.nq","broker_type":"mqtt","topics":["sensors"],"rate_of_publishing":1000,"mqtt_config":{"host":"localhost","port":1883,"client_id":"test","keep_alive_secs":30}}' - -# 6. Check status -curl http://localhost:8080/api/replay/status -``` - -## Success Criteria - -- [x] No runtime panics -- [x] Server starts cleanly -- [x] All endpoints respond -- [x] Replay runs in background -- [x] Queries can be executed -- [x] WebSocket streaming works -- [x] MQTT integration functional - -**Status: COMPLETE AND WORKING** ✅ diff --git a/docs/FIXES_APPLIED.md b/docs/FIXES_APPLIED.md deleted file mode 100644 index 18c5324..0000000 --- a/docs/FIXES_APPLIED.md +++ /dev/null @@ -1,98 +0,0 @@ -# Fixes Applied - Summary - -## Issues Found - -1. ❌ Timestamp mismatch: Query used 2024 dates but data has Jan 1970 timestamps -2. ❌ MQTT errors: Broker connection issues during quick replay - -## Fixes Applied - -### 1. Dashboard Query Updated -```sparql -# OLD (wrong range) -[START 1704067200 END 1735689599] // 2024 dates - -# NEW (correct range) -[START 1 END 10000000] // Covers Jan 1970 data -``` - -### 2. Replay Config Updated -```javascript -// OLD (caused MQTT errors) -broker_type: "mqtt", -loop_file: true, - -// NEW (works reliably) -broker_type: "none", // Storage only, no MQTT -loop_file: false, // Complete once -``` - -## What Changed in Dashboard - -**File:** `examples/demo_dashboard.html` - -1. Query timestamp: `[START 1 END 10000000]` ✅ -2. Replay broker: `"none"` instead of `"mqtt"` ✅ -3. No looping for quick test ✅ -4. Faster rate: 5000 events/sec ✅ - -## Why This Works - -Your data timestamps are around **1.8 million milliseconds** (Jan 21, 1970). - -The query range `[START 1 END 10000000]` covers: -- 1ms to 10,000,000ms -- Equals 0 to ~2.7 hours -- Includes your data at ~1.8M ms ✅ - -## Test Now - -```bash -# 1. Clear old data -rm -rf data/storage/* - -# 2. Kill old server -killall http_server 2>/dev/null - -# 3. Start fresh -cargo run --bin http_server - -# 4. Open dashboard -open examples/demo_dashboard.html - -# 5. Click buttons -# "Start Replay" → Wait 3 seconds → "Start Query" -``` - -## Expected Behavior - -✅ Replay completes without MQTT errors -✅ Data stored in `data/storage/` -✅ Query returns historical results -✅ Results appear in dashboard WebSocket panel -✅ Tagged as "source": "historical" - -## For MQTT/Live Later - -Once historical works, switch back to MQTT for live: - -```javascript -{ - "broker_type": "mqtt", - "loop_file": true, - "mqtt_config": { ... } -} -``` - -And use LIVE window query: -```sparql -[RANGE 5000 STEP 1000] // Not START/END -``` - -## Files to Use - -- Dashboard: `examples/demo_dashboard.html` (updated) -- Data: `data/sensors_correct.nq` (clean test data) -- Guide: `TEST_HISTORICAL.md` (step-by-step) - -**Everything should work now!** 🎉 diff --git a/docs/HTTP_API.md b/docs/HTTP_API.md deleted file mode 100644 index caa0edd..0000000 --- a/docs/HTTP_API.md +++ /dev/null @@ -1,847 +0,0 @@ -# Janus HTTP API Documentation - -## Overview - -The Janus HTTP API provides REST endpoints for query management and WebSocket streaming for real-time results. It also includes stream bus replay control endpoints for demo and testing purposes. - -**Base URL:** `http://localhost:8080` - -## Quick Start - -### 1. Start the HTTP Server - -```bash -# Build and run the HTTP server -cargo run --bin http_server - -# With custom configuration -cargo run --bin http_server -- --host 0.0.0.0 --port 8080 --storage-dir ./data/storage -``` - -### 2. Run the Example Client - -```bash -# Run the comprehensive client example -cargo run --example http_client_example -``` - -## Architecture - -The HTTP API server provides: - -- **REST Endpoints**: JSON-based HTTP endpoints for query registration, lifecycle management, and replay control -- **WebSocket Streaming**: Real-time streaming of query results (both historical and live) -- **CORS Support**: Cross-Origin Resource Sharing enabled for dashboard integration -- **Thread-Safe State**: Shared state using `Arc` for concurrent access across async tasks - -## API Endpoints - -### Health Check - -#### `GET /health` - -Health check endpoint to verify server is running. - -**Response:** -```json -{ - "message": "Janus HTTP API is running" -} -``` - ---- - -### Query Management - -#### `POST /api/queries` - -Register a new JanusQL query. - -**Request Body:** -```json -{ - "query_id": "sensor_query_1", - "janusql": "PREFIX ex: SELECT ?sensor ?temp FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153600] WHERE { WINDOW ex:histWindow { ?sensor ex:temperature ?temp . } }" -} -``` - -**Response (200 OK):** -```json -{ - "query_id": "sensor_query_1", - "query_text": "SELECT ?sensor ?temp FROM...", - "registered_at": 1704067200, - "message": "Query registered successfully" -} -``` - -**Error Response (400 Bad Request):** -```json -{ - "error": "Parse Error: Failed to parse JanusQL query: ..." -} -``` - ---- - -#### `GET /api/queries` - -List all registered queries. - -**Response:** -```json -{ - "queries": [ - "sensor_query_1", - "live_sensor_query", - "historical_analysis" - ], - "total": 3 -} -``` - ---- - -#### `GET /api/queries/:id` - -Get details for a specific query. - -**Parameters:** -- `id` (path): Query identifier - -**Response:** -```json -{ - "query_id": "sensor_query_1", - "query_text": "SELECT ?sensor ?temp FROM...", - "registered_at": 1704067200, - "execution_count": 5, - "is_running": true, - "status": "Running" -} -``` - -**Status Values:** -- `Registered` - Query registered but not started -- `Running` - Query is currently executing -- `Stopped` - Query was stopped -- `Failed` - Query execution failed -- `Completed` - Query execution completed - -**Error Response (404 Not Found):** -```json -{ - "error": "Query 'nonexistent' not found" -} -``` - ---- - -#### `POST /api/queries/:id/start` - -Start executing a registered query. - -**Parameters:** -- `id` (path): Query identifier - -**Response:** -```json -{ - "message": "Query 'sensor_query_1' started successfully" -} -``` - -**Error Responses:** - -Already Running (400): -```json -{ - "error": "Execution Error: Query 'sensor_query_1' is already running" -} -``` - -Not Found (404): -```json -{ - "error": "Query 'sensor_query_1' not found" -} -``` - ---- - -#### `DELETE /api/queries/:id` - -Stop a running query. - -**Parameters:** -- `id` (path): Query identifier - -**Response:** -```json -{ - "message": "Query 'sensor_query_1' stopped successfully" -} -``` - -**Error Response (400 Bad Request):** -```json -{ - "error": "Execution Error: Query 'sensor_query_1' is not running" -} -``` - ---- - -#### `WS /api/queries/:id/results` - -WebSocket endpoint for streaming query results in real-time. - -**Connection URL:** -``` -ws://localhost:8080/api/queries/sensor_query_1/results -``` - -**Message Format:** -```json -{ - "query_id": "sensor_query_1", - "timestamp": 1704067200000, - "source": "historical", - "bindings": [ - { - "sensor": "http://example.org/sensor1", - "temp": "23.5" - } - ] -} -``` - -**Source Types:** -- `historical` - Results from historical data processing -- `live` - Results from live stream processing - -**JavaScript Example:** -```javascript -const ws = new WebSocket('ws://localhost:8080/api/queries/sensor_query_1/results'); - -ws.onmessage = (event) => { - const result = JSON.parse(event.data); - console.log(`[${result.source}] Query: ${result.query_id}`); - console.log(`Timestamp: ${result.timestamp}`); - console.log('Bindings:', result.bindings); -}; - -ws.onerror = (error) => { - console.error('WebSocket error:', error); -}; - -ws.onclose = () => { - console.log('WebSocket connection closed'); -}; -``` - ---- - -### Stream Bus Replay Control - -#### `POST /api/replay/start` - -Start the stream bus replay for ingesting RDF data. - -**Request Body:** -```json -{ - "input_file": "data/sensors.nq", - "broker_type": "none", - "topics": ["sensors"], - "rate_of_publishing": 1000, - "loop_file": false, - "add_timestamps": true, - "kafka_config": null, - "mqtt_config": null -} -``` - -**Request Parameters:** -- `input_file` (required): Path to the N-Quads input file -- `broker_type` (optional, default: "none"): Broker type - "kafka", "mqtt", or "none" -- `topics` (optional, default: ["janus"]): List of topic names -- `rate_of_publishing` (optional, default: 1000): Events per second rate limit -- `loop_file` (optional, default: false): Whether to loop the file continuously -- `add_timestamps` (optional, default: true): Add timestamps to events -- `kafka_config` (optional): Kafka broker configuration -- `mqtt_config` (optional): MQTT broker configuration - -**Kafka Config:** -```json -{ - "kafka_config": { - "bootstrap_servers": "localhost:9092", - "client_id": "janus_client", - "message_timeout_ms": "5000" - } -} -``` - -**MQTT Config:** -```json -{ - "mqtt_config": { - "host": "localhost", - "port": 1883, - "client_id": "janus_client", - "keep_alive_secs": 30 - } -} -``` - -**Response:** -```json -{ - "message": "Stream bus replay started with file: data/sensors.nq" -} -``` - -**Error Response (400 Bad Request):** -```json -{ - "error": "Replay is already running" -} -``` - ---- - -#### `POST /api/replay/stop` - -Stop the currently running stream bus replay. - -**Response:** -```json -{ - "message": "Stream bus replay stopped" -} -``` - -**Error Response (400 Bad Request):** -```json -{ - "error": "Replay is not running" -} -``` - ---- - -#### `GET /api/replay/status` - -Get the current status of the stream bus replay. - -**Response (Running):** -```json -{ - "is_running": true, - "events_read": 15420, - "events_published": 15420, - "events_stored": 15420, - "publish_errors": 0, - "storage_errors": 0, - "events_per_second": 1543.2, - "elapsed_seconds": 10.0 -} -``` - -**Response (Not Running):** -```json -{ - "is_running": false, - "events_read": 0, - "events_published": 0, - "events_stored": 0, - "publish_errors": 0, - "storage_errors": 0, - "events_per_second": 0.0, - "elapsed_seconds": 0.0 -} -``` - ---- - -## Usage Examples - -### cURL Examples - -#### Register a Query -```bash -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{ - "query_id": "temp_query", - "janusql": "PREFIX ex: SELECT ?sensor ?temp FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153600] WHERE { WINDOW ex:histWindow { ?sensor ex:temperature ?temp . } }" - }' -``` - -#### List All Queries -```bash -curl http://localhost:8080/api/queries -``` - -#### Get Query Details -```bash -curl http://localhost:8080/api/queries/temp_query -``` - -#### Start a Query -```bash -curl -X POST http://localhost:8080/api/queries/temp_query/start -``` - -#### Stop a Query -```bash -curl -X DELETE http://localhost:8080/api/queries/temp_query -``` - -#### Start Replay -```bash -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{ - "input_file": "data/sensors.nq", - "broker_type": "none", - "topics": ["sensors"], - "rate_of_publishing": 1000, - "loop_file": false, - "add_timestamps": true - }' -``` - -#### Get Replay Status -```bash -curl http://localhost:8080/api/replay/status -``` - -#### Stop Replay -```bash -curl -X POST http://localhost:8080/api/replay/stop -``` - ---- - -### Python Example - -```python -import requests -import json -from websocket import create_connection - -BASE_URL = "http://localhost:8080" - -# Register a query -response = requests.post( - f"{BASE_URL}/api/queries", - json={ - "query_id": "my_query", - "janusql": "PREFIX ex: SELECT ?s ?p ?o FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153600] WHERE { WINDOW ex:histWindow { ?s ?p ?o . } }" - } -) -print(f"Register: {response.json()}") - -# Start the query -response = requests.post(f"{BASE_URL}/api/queries/my_query/start") -print(f"Start: {response.json()}") - -# Connect to WebSocket for results -ws = create_connection(f"ws://localhost:8080/api/queries/my_query/results") - -# Receive results -for i in range(10): - result = ws.recv() - print(f"Result: {json.loads(result)}") - -ws.close() - -# Stop the query -response = requests.delete(f"{BASE_URL}/api/queries/my_query") -print(f"Stop: {response.json()}") -``` - ---- - -### JavaScript/Node.js Example - -```javascript -const axios = require('axios'); -const WebSocket = require('ws'); - -const BASE_URL = 'http://localhost:8080'; - -async function demo() { - // Register a query - const registerResponse = await axios.post(`${BASE_URL}/api/queries`, { - query_id: 'js_query', - janusql: 'PREFIX ex: SELECT ?s ?p ?o FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153600] WHERE { WINDOW ex:histWindow { ?s ?p ?o . } }' - }); - console.log('Registered:', registerResponse.data); - - // Start the query - const startResponse = await axios.post(`${BASE_URL}/api/queries/js_query/start`); - console.log('Started:', startResponse.data); - - // Connect to WebSocket - const ws = new WebSocket(`ws://localhost:8080/api/queries/js_query/results`); - - ws.on('message', (data) => { - const result = JSON.parse(data); - console.log('Result:', result); - }); - - ws.on('error', (error) => { - console.error('WebSocket error:', error); - }); - - // Wait for results... - await new Promise(resolve => setTimeout(resolve, 10000)); - - ws.close(); - - // Stop the query - const stopResponse = await axios.delete(`${BASE_URL}/api/queries/js_query`); - console.log('Stopped:', stopResponse.data); -} - -demo().catch(console.error); -``` - ---- - -## Dashboard Integration - -### Two-Button Demo Interface - -For a simple demo dashboard with "Start Replay" and "Start Query" buttons: - -```html - - - - Janus Demo Dashboard - - - -

Janus RDF Stream Processing - Demo

- - - -
- - - -
-
- - - - -``` - ---- - -## Error Handling - -All error responses follow this format: - -```json -{ - "error": "Descriptive error message" -} -``` - -### HTTP Status Codes - -- `200 OK` - Successful GET request -- `201 Created` - Successful resource creation -- `400 Bad Request` - Invalid request or operation not allowed -- `404 Not Found` - Resource not found -- `500 Internal Server Error` - Server-side error - ---- - -## Configuration - -### Server Options - -```bash -Usage: http_server [OPTIONS] - -Options: - -H, --host - Server host address [default: 127.0.0.1] - - -p, --port - Server port [default: 8080] - - -s, --storage-dir - Storage directory path [default: ./data/storage] - - --max-batch-size-bytes - Maximum batch size in bytes [default: 10485760] - - --flush-interval-ms - Flush interval in milliseconds [default: 5000] - - --max-total-memory-mb - Maximum total memory in MB [default: 1024] -``` - ---- - -## Performance Considerations - -1. **WebSocket Connections**: Each active query can have multiple WebSocket connections. Results are broadcast to all connected clients. - -2. **Query Handles**: Query handles are stored in memory. Consider resource limits when running many concurrent queries. - -3. **Stream Bus Replay**: Running replay at high rates (>10,000 events/sec) may impact query performance. Adjust `rate_of_publishing` accordingly. - -4. **CORS**: CORS is configured to allow all origins. In production, restrict this to specific domains. - ---- - -## Security Notes - -**WARNING**: This API is designed for local development and demos. For production use: - -1. Add authentication/authorization -2. Restrict CORS to specific origins -3. Add rate limiting -4. Use HTTPS/WSS instead of HTTP/WS -5. Validate and sanitize all inputs -6. Add request size limits -7. Implement proper session management - ---- - -## Troubleshooting - -### WebSocket Connection Fails - -**Issue**: Cannot connect to WebSocket endpoint - -**Solutions**: -- Ensure query is registered and started before connecting -- Check that the query ID in the WebSocket URL matches the registered query -- Verify the server is running and accessible -- Check browser console for CORS or connection errors - -### Query Results Not Appearing - -**Issue**: WebSocket connects but no results received - -**Solutions**: -- Verify stream bus replay is running (`GET /api/replay/status`) -- Check query syntax is valid -- Ensure historical data exists for the specified time window -- For live queries, ensure live stream is producing events - -### Replay Won't Start - -**Issue**: Replay start returns error - -**Solutions**: -- Check that `input_file` path exists and is accessible -- Verify no other replay is currently running -- Ensure broker configuration is correct if using Kafka/MQTT -- Check server logs for detailed error messages - ---- - -## Additional Resources - -- [JanusQL Query Language Documentation](./JANUSQL.md) -- [Stream Bus CLI Documentation](./STREAM_BUS.md) -- [Architecture Overview](./ARCHITECTURE.md) -- [Benchmark Results](./BENCHMARK_RESULTS.md) - ---- - -## Support - -For issues, feature requests, or questions: -- GitHub Issues: https://github.com/SolidLabResearch/janus/issues -- Documentation: https://github.com/SolidLabResearch/janus diff --git a/docs/HTTP_API_IMPLEMENTATION.md b/docs/HTTP_API_IMPLEMENTATION.md deleted file mode 100644 index 3b6c0fa..0000000 --- a/docs/HTTP_API_IMPLEMENTATION.md +++ /dev/null @@ -1,562 +0,0 @@ -# Janus HTTP API - Implementation Summary - -## Overview - -This document describes the complete HTTP API implementation for Janus, providing REST endpoints for query management and WebSocket streaming for real-time results. - -## Implementation Status: COMPLETE ✓ - -The HTTP API is fully implemented and production-ready with the following components: - -### Core Components - -1. **HTTP Server Module** (`src/http/`) - - `server.rs` - Main server implementation with all endpoints - - `mod.rs` - Module exports - -2. **Binary Executable** (`src/bin/http_server.rs`) - - Standalone HTTP server with configurable options - - Graceful shutdown support - - Comprehensive initialization logging - -3. **Client Example** (`examples/http_client_example.rs`) - - Full demonstration of all API endpoints - - WebSocket streaming example - - Error handling patterns - -4. **Demo Dashboard** (`examples/demo_dashboard.html`) - - Interactive web interface - - Two-button demo (Start Replay / Start Query) - - Real-time result display - - Status monitoring - -## Architecture - -### Technology Stack - -- **Web Framework**: Axum 0.7 - - Modern, performant, type-safe - - Built on Tokio async runtime - - Native WebSocket support - -- **CORS**: Tower-HTTP - - Configured to allow all origins (development mode) - - Ready for production restriction - -- **Serialization**: Serde JSON - - Automatic request/response serialization - - Type-safe DTOs - -- **WebSocket**: Tokio-Tungstenite - - Low-latency streaming - - Non-blocking message delivery - -### State Management - -```rust -pub struct AppState { - pub janus_api: Arc, // Query execution engine - pub registry: Arc, // Query registry - pub storage: Arc, // RDF storage - pub replay_state: Arc>, // Replay control - pub query_handles: Arc>>>>, // Active queries -} -``` - -All state is wrapped in `Arc` for thread-safe sharing across async tasks. - -## Implemented Endpoints - -### Query Management (REST) - -#### POST /api/queries -**Register a new JanusQL query** - -Request: -```json -{ - "query_id": "sensor_query_1", - "janusql": "PREFIX ex: SELECT ?sensor ?temp FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153600] WHERE { WINDOW ex:histWindow { ?sensor ex:temperature ?temp . } }" -} -``` - -Response (200): -```json -{ - "query_id": "sensor_query_1", - "query_text": "SELECT ?sensor ?temp FROM...", - "registered_at": 1704067200, - "message": "Query registered successfully" -} -``` - -#### GET /api/queries -**List all registered queries** - -Response: -```json -{ - "queries": ["sensor_query_1", "live_query"], - "total": 2 -} -``` - -#### GET /api/queries/:id -**Get query details** - -Response: -```json -{ - "query_id": "sensor_query_1", - "query_text": "SELECT...", - "registered_at": 1704067200, - "execution_count": 5, - "is_running": true, - "status": "Running" -} -``` - -#### POST /api/queries/:id/start -**Start query execution** - -Response: -```json -{ - "message": "Query 'sensor_query_1' started successfully" -} -``` - -#### DELETE /api/queries/:id -**Stop query execution** - -Response: -```json -{ - "message": "Query 'sensor_query_1' stopped successfully" -} -``` - -### Result Streaming (WebSocket) - -#### WS /api/queries/:id/results -**Stream query results in real-time** - -Connection: `ws://localhost:8080/api/queries/sensor_query_1/results` - -Message Format: -```json -{ - "query_id": "sensor_query_1", - "timestamp": 1704067200000, - "source": "historical", - "bindings": [ - { - "sensor": "http://example.org/sensor1", - "temp": "23.5" - } - ] -} -``` - -Source types: -- `"historical"` - Results from historical data processing -- `"live"` - Results from live stream processing - -### Stream Bus Replay Control - -#### POST /api/replay/start -**Start stream bus replay for data ingestion** - -Request: -```json -{ - "input_file": "data/sensors.nq", - "broker_type": "none", - "topics": ["sensors"], - "rate_of_publishing": 1000, - "loop_file": true, - "add_timestamps": true, - "kafka_config": null, - "mqtt_config": null -} -``` - -Broker types: `"kafka"`, `"mqtt"`, `"none"` - -Response: -```json -{ - "message": "Stream bus replay started with file: data/sensors.nq" -} -``` - -#### POST /api/replay/stop -**Stop the running replay** - -Response: -```json -{ - "message": "Stream bus replay stopped" -} -``` - -#### GET /api/replay/status -**Get current replay status** - -Response (running): -```json -{ - "is_running": true, - "events_read": 15420, - "events_published": 15420, - "events_stored": 15420, - "publish_errors": 0, - "storage_errors": 0, - "events_per_second": 1543.2, - "elapsed_seconds": 10.0 -} -``` - -### Health Check - -#### GET /health -**Server health check** - -Response: -```json -{ - "message": "Janus HTTP API is running" -} -``` - -## Error Handling - -All errors return consistent JSON format: - -```json -{ - "error": "Descriptive error message" -} -``` - -HTTP Status Codes: -- `200 OK` - Successful GET request -- `201 Created` - Resource created -- `400 Bad Request` - Invalid request -- `404 Not Found` - Resource not found -- `500 Internal Server Error` - Server error - -### Custom Error Types - -```rust -pub enum ApiError { - JanusError(JanusApiError), - NotFound(String), - BadRequest(String), - InternalError(String), -} -``` - -Automatic conversion from internal errors to HTTP responses. - -## Usage Examples - -### Starting the Server - -```bash -# Default configuration -cargo run --bin http_server - -# Custom configuration -cargo run --bin http_server -- \ - --host 0.0.0.0 \ - --port 8080 \ - --storage-dir ./data/storage \ - --max-batch-size-bytes 10485760 \ - --flush-interval-ms 5000 -``` - -### Server Options - -| Flag | Default | Description | -|------|---------|-------------| -| `--host` | 127.0.0.1 | Server bind address | -| `--port` | 8080 | Server port | -| `--storage-dir` | ./data/storage | Storage directory | -| `--max-batch-size-bytes` | 10485760 | Max batch size (10MB) | -| `--flush-interval-ms` | 5000 | Flush interval (5s) | - -### Demo Dashboard - -Open `examples/demo_dashboard.html` in a browser for an interactive demo with: -- Start/Stop Replay buttons -- Start/Stop Query buttons -- Real-time status monitoring -- Live result streaming display -- Color-coded historical vs. live results - -### Client Example - -```bash -cargo run --example http_client_example -``` - -Demonstrates: -1. Health check -2. Query registration -3. Query listing -4. Query details -5. Replay start/stop -6. Query execution -7. WebSocket streaming -8. Complete error handling - -## Integration Patterns - -### JavaScript/Browser - -```javascript -// Register query -const response = await fetch('http://localhost:8080/api/queries', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - query_id: 'my_query', - janusql: 'PREFIX ex: SELECT ?s ?p ?o FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] WHERE { WINDOW ex:histWindow { ?s ?p ?o . } }' - }) -}); - -// Start query -await fetch('http://localhost:8080/api/queries/my_query/start', { - method: 'POST' -}); - -// Stream results -const ws = new WebSocket('ws://localhost:8080/api/queries/my_query/results'); -ws.onmessage = (event) => { - const result = JSON.parse(event.data); - console.log(result); -}; -``` - -### Python - -```python -import requests -import websocket -import json - -# Register query -requests.post('http://localhost:8080/api/queries', json={ - 'query_id': 'my_query', - 'janusql': 'PREFIX ex: SELECT ?s ?p ?o FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] WHERE { WINDOW ex:histWindow { ?s ?p ?o . } }' -}) - -# Start query -requests.post('http://localhost:8080/api/queries/my_query/start') - -# Stream results -def on_message(ws, message): - result = json.loads(message) - print(result) - -ws = websocket.WebSocketApp( - 'ws://localhost:8080/api/queries/my_query/results', - on_message=on_message -) -ws.run_forever() -``` - -### cURL - -```bash -# Register -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{"query_id": "test", "janusql": "PREFIX ex: SELECT ?s ?p ?o FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] WHERE { WINDOW ex:histWindow { ?s ?p ?o . } }"}' - -# Start -curl -X POST http://localhost:8080/api/queries/test/start - -# Status -curl http://localhost:8080/api/queries/test - -# Stop -curl -X DELETE http://localhost:8080/api/queries/test -``` - -## Key Features - -### Thread Safety -- All shared state uses `Arc>` or `Arc>` -- Non-blocking WebSocket message delivery -- Concurrent query execution support - -### Graceful Shutdown -- CTRL+C signal handling -- Clean resource cleanup -- Connection draining - -### Performance -- Async/await throughout -- Zero-copy WebSocket streaming where possible -- Efficient query handle management - -### CORS Support -- Configured for cross-origin requests -- Ready for dashboard integration -- Production-ready with restriction options - -### Extensibility -- Clean separation of concerns -- Easy to add new endpoints -- DTOs for all requests/responses -- Type-safe routing - -## File Structure - -``` -janus/ -├── src/ -│ ├── http/ -│ │ ├── mod.rs # Module exports -│ │ └── server.rs # Server implementation (537 lines) -│ ├── bin/ -│ │ └── http_server.rs # Binary executable (111 lines) -│ └── lib.rs # Export http module -├── examples/ -│ ├── http_client_example.rs # Client demo (370 lines) -│ └── demo_dashboard.html # Web dashboard (629 lines) -├── Cargo.toml # Dependencies added -├── HTTP_API.md # Full API documentation (847 lines) -├── QUICKSTART_HTTP_API.md # Quick start guide (285 lines) -└── HTTP_API_IMPLEMENTATION.md # This document - -Total: ~2,779 lines of new code + documentation -``` - -## Dependencies Added - -```toml -[dependencies] -axum = { version = "0.7", features = ["ws"] } -tower-http = { version = "0.5", features = ["cors", "trace"] } -tokio-tungstenite = "0.21" -reqwest = { version = "0.11", features = ["json"] } -futures-util = "0.3" -tokio = { version = "1.48.0", features = ["full"] } -``` - -## Testing - -### Manual Testing -1. Start server: `cargo run --bin http_server` -2. Open dashboard: `open examples/demo_dashboard.html` -3. Click "Start Replay" then "Start Query" -4. Observe live results streaming - -### Automated Testing -```bash -# Terminal 1 -cargo run --bin http_server - -# Terminal 2 -cargo run --example http_client_example -``` - -### API Testing with cURL -See `QUICKSTART_HTTP_API.md` for comprehensive cURL examples. - -## Production Considerations - -### Security (NOT IMPLEMENTED - Development Only) -For production deployment, add: -- [ ] Authentication/Authorization (JWT, OAuth2) -- [ ] Rate limiting -- [ ] Request size limits -- [ ] Input validation/sanitization -- [ ] HTTPS/WSS instead of HTTP/WS -- [ ] Restrict CORS to specific origins -- [ ] API keys for external access - -### Performance Tuning -- Adjust `--max-batch-size-bytes` for throughput -- Configure `--flush-interval-ms` for latency -- Monitor WebSocket connection count -- Consider connection pooling for high load - -### Monitoring -- Add structured logging (tracing) -- Metrics collection (Prometheus) -- Health check with detailed status -- Error rate tracking - -### Deployment -- Use `--release` build for production -- Set appropriate `--host` (0.0.0.0 for external access) -- Configure firewall rules -- Use reverse proxy (nginx/traefik) for SSL termination - -## Known Limitations - -1. **No Authentication**: Open access to all endpoints -2. **Single Server**: No clustering/load balancing support -3. **In-Memory Query Handles**: Restart loses running queries -4. **Limited Error Recovery**: No automatic retry mechanisms -5. **No Persistence**: Replay state lost on restart - -## Future Enhancements - -- [ ] Persistent query state across restarts -- [ ] Multi-tenancy support -- [ ] Query result pagination -- [ ] GraphQL endpoint -- [ ] OpenAPI/Swagger documentation -- [ ] Prometheus metrics endpoint -- [ ] Distributed query execution -- [ ] Result caching -- [ ] Query optimization hints API - -## Troubleshooting - -### Port Already in Use -```bash -lsof -i :8080 -cargo run --bin http_server -- --port 8081 -``` - -### WebSocket Connection Fails -- Ensure query is registered AND started -- Check query ID matches WebSocket URL -- Verify server is accessible (CORS, firewall) - -### No Query Results -- Check replay is running: `GET /api/replay/status` -- Verify data file exists and is valid N-Quads -- Check query syntax with simple test query -- Monitor server logs for errors - -## Documentation - -- **Quick Start**: `QUICKSTART_HTTP_API.md` -- **Full API Reference**: `HTTP_API.md` -- **This Document**: `HTTP_API_IMPLEMENTATION.md` -- **Code Examples**: `examples/http_client_example.rs` -- **Interactive Demo**: `examples/demo_dashboard.html` - -## Summary - -The Janus HTTP API is fully implemented and ready for use. It provides: - -✓ REST endpoints for query management -✓ WebSocket streaming for real-time results -✓ Stream bus replay control -✓ Complete error handling -✓ Thread-safe concurrent access -✓ CORS support for dashboards -✓ Comprehensive documentation -✓ Working examples and demo - -The implementation follows Rust best practices, uses modern async patterns, and integrates seamlessly with the existing Janus architecture. - -**Ready for testing and integration with external dashboards and agents.** diff --git a/docs/LIVE_STREAMING_READY.md b/docs/LIVE_STREAMING_READY.md deleted file mode 100644 index 02d95a5..0000000 --- a/docs/LIVE_STREAMING_READY.md +++ /dev/null @@ -1,326 +0,0 @@ -# Live Streaming Integration - Ready to Test! 🚀 - -## What Was Done - -I've successfully integrated MQTT subscription into Janus's live stream processing. The system now supports **full hybrid queries** that combine historical data retrieval with real-time MQTT streaming. - -### New Components Added - -1. **`src/stream/mqtt_subscriber.rs`** (250 lines) - - MQTT subscriber that receives RDF events from message broker - - Feeds events to LiveStreamProcessing in real-time - - Handles connection errors and automatic parsing - -2. **Updated `src/api/janus_api.rs`** - - Spawns MQTT subscribers when live queries start - - Shares LiveStreamProcessing instance between subscriber and worker - - Auto-cleanup when queries stop - -3. **Test Scripts & Documentation** - - `test_live_streaming.sh` - Automated end-to-end test - - `LIVE_STREAMING_GUIDE.md` - Complete usage guide - - `start_http_server.sh` - Easy server startup script - -### Architecture Flow - -``` -File (sensors_correct.nq) - ↓ -StreamBus (reads & publishes) - ↓ ↓ -MQTT Broker Storage (flush to disk) - ↓ ↓ -MqttSubscriber HistoricalExecutor - ↓ ↓ -LiveStreamProcessing Query Results (historical) - ↓ -Query Results (live) - ↓ -WebSocket → Dashboard -``` - -## How to Test (Step-by-Step) - -### Option 1: Automated Test Script - -```bash -cd /Users/kushbisen/Code/janus -./scripts/test_live_streaming.sh -``` - -This runs a complete test cycle and shows you if everything works. - -### Option 2: Manual Dashboard Test (Recommended) - -#### Step 1: Start the Server - -```bash -cd /Users/kushbisen/Code/janus -./scripts/start_http_server.sh --clean -``` - -You should see: -``` -╔════════════════════════════════════════════════════════════════╗ -║ Janus RDF Stream Processing Engine ║ -║ HTTP API Server ║ -╚════════════════════════════════════════════════════════════════╝ - -Initializing storage at: ./data/storage -... -Server listening on 127.0.0.1:8080 -``` - -**Keep this terminal open** - you'll see logs here. - -#### Step 2: Open the Dashboard - -1. Open your web browser -2. Navigate to: `file:///Users/kushbisen/Code/janus/examples/demo_dashboard.html` -3. You should see the Janus dashboard interface - -#### Step 3: Start Replay (Publishes to MQTT + Storage) - -1. Click the **"Start Replay"** button -2. Watch the server terminal - you should see: - ``` - Starting the Stream Bus - Input: data/sensors_correct.nq - Broker: Mqtt - Topics: ["sensors"] - Connecting to the MQTT Server at localhost:1883 - Connected to MQTT! - ✓ Read: 10 | Published: 10 | Stored: 10 - ``` - -3. Dashboard should show: - - Status: Running - - Input File: sensors_correct.nq - - Broker: MQTT + Storage - - Elapsed Time: counting up - -#### Step 4: Wait for Storage Flush - -**IMPORTANT:** Wait 10 seconds for data to flush to disk. - -You can monitor this in the server terminal - look for lines like: -``` -✓ Read: 20 | Published: 20 | Stored: 20 -``` - -#### Step 5: Start Query (Auto-spawns MQTT Subscriber) - -1. Click the **"Start Query"** button -2. Watch the server terminal - you should see: - ``` - Starting MQTT subscriber... - Host: localhost:1883 - Topic: sensors - Stream URI: http://example.org/sensorStream - ✓ Subscribed to topic: sensors - Listening for events... - ``` - -3. Dashboard should show: - - Query Status: Running - - Connection: Connected - - Results Received: counting up - -#### Step 6: Observe Results - -You should now see **TWO types of results** in the dashboard: - -**Historical Results** (appears once): -```json -{ - "source": "historical", - "timestamp": "...", - "bindings": [ - {"sensor": "http://example.org/sensor1", "temp": "\"23.5\""}, - {"sensor": "http://example.org/sensor2", "temp": "\"26.8\""}, - ... - ] -} -``` - -**Live Results** (appears continuously): -```json -{ - "source": "live", - "timestamp": "...", - "bindings": [ - {"sensor": "http://example.org/sensor1", "temp": "\"23.5\""} - ] -} -``` - -The live results will keep coming because `loop_file: true` continuously replays the data. - -## What to Expect - -### ✓ Working Correctly - -- **Historical results:** Appear once, 1-3 seconds after starting query - - Should show all 5 sensors with temperatures - - Source: "historical" - - Bindings show full URIs like "http://example.org/sensor1" - -- **Live results:** Appear continuously every ~1-2 seconds - - Should show individual sensor readings as they arrive via MQTT - - Source: "live" - - Bindings show real-time data - -- **Dashboard:** Updates automatically with new results - - Results counter increments - - Timestamps are current (2024/2025) - -### ✗ Common Issues & Fixes - -#### Issue: Empty sensor values `"sensor": ""` - -**Fix:** -```bash -# Stop everything -# Clean storage -rm -rf data/storage/* -# Restart server -./scripts/start_http_server.sh -``` - -#### Issue: Only historical results, no live results - -**Check:** -1. Is MQTT broker running? - ```bash - docker ps | grep mosquitto - ``` - If not: `docker-compose up -d mosquitto` - -2. Check server logs for "Starting MQTT subscriber" - - If you don't see this, the query didn't spawn subscriber - -3. Monitor MQTT messages: - ```bash - docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v - ``` - You should see RDF data flowing - -#### Issue: No historical results, only live results - -**Cause:** Storage hasn't flushed yet or timestamp mismatch - -**Fix:** -- Wait longer (15-20 seconds) before starting query -- Check `data/storage/` has files: - ```bash - ls -lh data/storage/ - ``` - -#### Issue: No results at all - -**Check:** -1. Server running? `ps aux | grep http_server` -2. Dashboard connected to correct URL? (http://127.0.0.1:8080) -3. Open browser console (F12) and check for errors -4. Check server logs at `/tmp/janus_server.log` - -## Verifying MQTT Integration - -### Monitor MQTT Traffic - -In a separate terminal: -```bash -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v -``` - -You should see messages like: -``` -sensors "23.5" . -sensors "26.8" . -... -``` - -### Check Server Logs - -```bash -tail -f /tmp/janus_server.log -``` - -Look for: -- "Starting MQTT subscriber..." -- "✓ Subscribed to topic: sensors" -- "✓ Received N events" - -## Testing Different Scenarios - -### Scenario 1: Historical Only - -Use `broker_type: "none"` in replay config: -```json -{ - "broker_type": "none", - ... -} -``` - -You should get only historical results, no live results. - -### Scenario 2: Live Only - -Modify the query to remove historical window (keep only RANGE/STEP window). - -### Scenario 3: Multiple Sensors - -The default data has 5 sensors. You should see all 5 in historical results, and random ones in live results. - -## Performance Metrics - -Expected performance: -- **Historical query:** ~50-100ms to return all results -- **Live latency:** ~10-50ms from MQTT publish to result -- **Throughput:** 500 events/sec with current rate_of_publishing setting - -## Next Steps After Testing - -1. **If it works:** You have full hybrid query capability! - - Try modifying the query in the dashboard - - Experiment with different window ranges - - Create your own data files - -2. **If issues persist:** - - Check `LIVE_STREAMING_GUIDE.md` for detailed troubleshooting - - Review server logs for errors - - Verify MQTT broker connectivity - -3. **Future enhancements:** - - Add topic mapping (stream URI → MQTT topic) - - Support multiple MQTT brokers - - Add Kafka subscriber - - Expose MQTT subscriber metrics in API - -## Files Changed - -- `src/stream/mqtt_subscriber.rs` (new) -- `src/stream/mod.rs` (updated exports) -- `src/api/janus_api.rs` (MQTT integration) -- `examples/demo_dashboard.html` (query updated) -- `data/sensors_correct.nq` (converted to N-Triples) -- `test_live_streaming.sh` (new) -- `LIVE_STREAMING_GUIDE.md` (new) - -## Summary - -The live streaming integration is **COMPLETE and READY TO TEST**. You now have: - -✓ MQTT subscriber component -✓ Automatic subscription when queries start -✓ Hybrid historical + live query execution -✓ Real-time WebSocket result streaming -✓ Clean shutdown and resource cleanup -✓ Comprehensive documentation and test scripts - -**Start with the manual dashboard test above** - it will give you the most visibility into what's happening. - -The system is designed to "just work" - start the server, open the dashboard, click two buttons, and watch both historical and live results stream in. - -Good luck! 🎉 \ No newline at end of file diff --git a/docs/MVP_QUICKSTART.md b/docs/MVP_QUICKSTART.md deleted file mode 100644 index f191472..0000000 --- a/docs/MVP_QUICKSTART.md +++ /dev/null @@ -1,841 +0,0 @@ -# Janus MVP Quick Start Implementation Guide - -## TL;DR - What You Need to Do - -You asked: *"What is left to be done so that I can send a Janus-QL Query for the first MVP so that the historical and live processing is done and the results are returned as an output?"* - -**Answer:** Implement 4 critical missing pieces (in this order): - -1. **Fix SPARQL result format** (~1 hour) -2. **Create historical query executor** (~4 hours) -3. **Create event bus for live integration** (~3 hours) -4. **Wire it all together in `start_query()`** (~6 hours) - -Then add a CLI and test (another ~6 hours). **Total: ~20 hours of focused work.** - ---- - -## What You Already Have (✅ Working) - -| Component | Status | What It Does | -|-----------|--------|--------------| -| **Storage** | ✅ Complete | Stores 2.6M+ quads/sec, dictionary-encoded, background flush | -| **Parser** | ✅ Complete | Parses JanusQL → RSP-QL + SPARQL queries | -| **Registry** | ✅ Complete | Registers queries with metadata | -| **Live Processing** | ✅ Complete | RSP-QL execution via rsp-rs engine | -| **SPARQL Engine** | ✅ Complete | Executes SPARQL on quads (but format needs fix) | -| **Stream Bus** | ✅ Complete | Ingests RDF to storage/brokers | -| **Ingestion CLI** | ✅ Complete | `stream_bus_cli` for data ingestion | - ---- - -## What's Missing (❌ Gaps) - -``` -┌─────────────────────────────────────────────┐ -│ User sends JanusQL query │ -│ "Show me temp readings from last hour │ -│ AND keep showing live updates" │ -└──────────────────┬──────────────────────────┘ - │ - ▼ - ┌─────────────────────┐ - │ JanusApi │ - │ register_query() ✅│ - │ start_query() ❌ │ <-- MISSING! - └─────────────────────┘ - │ - ┌──────────┴──────────┐ - │ │ - ▼ ▼ - Historical Path Live Path - ❌ ❌ - │ │ - Need executor Need event bus - to query storage to feed live engine -``` - ---- - -## Implementation Roadmap - -### Task 1: Fix SPARQL Result Format (1 hour) 🟢 - -**Why:** OxigraphAdapter returns `Vec` with debug format. Need structured bindings. - -**File:** `src/querying/oxigraph_adapter.rs` - -**Add this method:** - -```rust -fn execute_query_bindings( - &self, - query: &str, - container: &QuadContainer, -) -> Result>, Self::EngineError> { - let store = Store::new()?; - for quad in &container.elements { - store.insert(quad)?; - } - - let evaluator = SparqlEvaluator::new(); - let parsed_query = evaluator.parse_query(query) - .map_err(|e| OxigraphError(e.to_string()))?; - let results = parsed_query.on_store(&store).execute()?; - - let mut bindings_list = Vec::new(); - - if let QueryResults::Solutions(solutions) = results { - for solution in solutions { - let solution = solution?; - let mut binding = HashMap::new(); - - for (var, term) in solution.iter() { - binding.insert( - var.as_str().to_string(), - term.to_string() - ); - } - - bindings_list.push(binding); - } - } - - Ok(bindings_list) -} -``` - -**Test it:** -```bash -cargo test --test integration_tests oxigraph -``` - ---- - -### Task 2: Create Historical Executor (4 hours) 🟡 - -**Why:** Need to query storage and execute SPARQL for historical windows. - -**File:** `src/api/historical_executor.rs` (new file) - -**Implementation:** - -```rust -use crate::{ - api::janus_api::{JanusApiError, QueryResult, ResultSource}, - core::RDFEvent, - parsing::janusql_parser::WindowDefinition, - querying::oxigraph_adapter::OxigraphAdapter, - registry::query_registry::QueryId, - storage::segmented_storage::StreamingSegmentedStorage, -}; -use oxigraph::model::{GraphName, NamedNode, Quad, Term}; -use rsp_rs::QuadContainer; -use std::{collections::HashMap, sync::Arc}; - -pub struct HistoricalExecutor { - storage: Arc, -} - -impl HistoricalExecutor { - pub fn new(storage: Arc) -> Self { - Self { storage } - } - - pub fn execute_window( - &self, - query_id: &QueryId, - window: &WindowDefinition, - sparql_query: &str, - ) -> Result, JanusApiError> { - // 1. Extract time range from window - let (start_ts, end_ts) = self.extract_time_range(window)?; - - // 2. Query storage - let events = self.storage.read_range(start_ts, end_ts) - .map_err(|e| JanusApiError::StorageError(e.to_string()))?; - - // 3. Decode Event → RDFEvent - let rdf_events: Vec = events.iter() - .filter_map(|event| { - self.storage.dictionary.read().ok() - .and_then(|dict| dict.decode(event).ok()) - }) - .collect(); - - // 4. Convert RDFEvent → Quad - let quads: Vec = rdf_events.iter() - .filter_map(|rdf_event| self.rdf_event_to_quad(rdf_event).ok()) - .collect(); - - // 5. Build QuadContainer - let container = QuadContainer::new( - quads.into_iter().collect(), - end_ts.try_into().unwrap_or(0) - ); - - // 6. Execute SPARQL - let adapter = OxigraphAdapter::new(); - let bindings = adapter.execute_query_bindings(sparql_query, &container) - .map_err(|e| JanusApiError::ExecutionError(e.to_string()))?; - - // 7. Convert to QueryResult - let results = bindings.into_iter() - .map(|binding| QueryResult { - query_id: query_id.clone(), - timestamp: end_ts, - source: ResultSource::Historical, - bindings: vec![binding], - }) - .collect(); - - Ok(results) - } - - fn extract_time_range(&self, window: &WindowDefinition) - -> Result<(u64, u64), JanusApiError> { - // For MVP: use current time - range_ms as start - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() as u64; - - let end_ts = now; - let start_ts = now.saturating_sub(window.range_ms); - - Ok((start_ts, end_ts)) - } - - fn rdf_event_to_quad(&self, event: &RDFEvent) - -> Result { - let subject = NamedNode::new(&event.subject) - .map_err(|e| JanusApiError::ExecutionError( - format!("Invalid subject: {}", e) - ))?; - - let predicate = NamedNode::new(&event.predicate) - .map_err(|e| JanusApiError::ExecutionError( - format!("Invalid predicate: {}", e) - ))?; - - let object = if event.object.starts_with("http://") || - event.object.starts_with("https://") { - Term::NamedNode(NamedNode::new(&event.object) - .map_err(|_| JanusApiError::ExecutionError( - "Invalid object URI".into() - ))?) - } else { - Term::Literal(oxigraph::model::Literal::new_simple_literal( - &event.object - )) - }; - - let graph = if event.graph.is_empty() || event.graph == "default" { - GraphName::DefaultGraph - } else { - GraphName::NamedNode(NamedNode::new(&event.graph) - .map_err(|e| JanusApiError::ExecutionError( - format!("Invalid graph: {}", e) - ))?) - }; - - Ok(Quad::new(subject, predicate, object, graph)) - } -} -``` - -**Add to `src/api/mod.rs`:** -```rust -pub mod historical_executor; -``` - -**Test it:** -```bash -cargo test --lib historical_executor -``` - ---- - -### Task 3: Create Event Bus (3 hours) 🟡 - -**Why:** Need to broadcast events from StreamBus to LiveStreamProcessing. - -**File:** `src/stream/event_bus.rs` (new file) - -**Implementation:** - -```rust -use crate::core::RDFEvent; -use std::sync::{mpsc, Arc, Mutex}; - -/// Event broadcasting system for live stream processing -pub struct EventBus { - subscribers: Arc>>>, -} - -impl EventBus { - pub fn new() -> Self { - Self { - subscribers: Arc::new(Mutex::new(Vec::new())), - } - } - - /// Subscribe to events. Returns a receiver channel. - pub fn subscribe(&self) -> mpsc::Receiver { - let (tx, rx) = mpsc::channel(); - self.subscribers.lock().unwrap().push(tx); - rx - } - - /// Publish an event to all subscribers. - pub fn publish(&self, event: RDFEvent) { - let mut subscribers = self.subscribers.lock().unwrap(); - - // Remove disconnected subscribers - subscribers.retain(|tx| tx.send(event.clone()).is_ok()); - } - - /// Get current subscriber count - pub fn subscriber_count(&self) -> usize { - self.subscribers.lock().unwrap().len() - } -} - -impl Default for EventBus { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::time::Duration; - - #[test] - fn test_event_bus_publish_subscribe() { - let bus = EventBus::new(); - let rx = bus.subscribe(); - - let event = RDFEvent::new( - 1000, - "http://ex.org/s", - "http://ex.org/p", - "o", - "http://ex.org/g" - ); - - bus.publish(event.clone()); - - let received = rx.recv_timeout(Duration::from_millis(100)).unwrap(); - assert_eq!(received.subject, event.subject); - } -} -``` - -**Add to `src/stream/mod.rs`:** -```rust -pub mod event_bus; -pub use event_bus::EventBus; -``` - -**Integrate with StreamBus** in `src/stream_bus/stream_bus.rs`: - -```rust -// Add to StreamBusConfig -pub struct StreamBusConfig { - // ... existing fields ... - pub event_bus: Option>, -} - -// In process_line() method, after writing to storage: -if let Some(ref event_bus) = self.config.event_bus { - event_bus.publish(rdf_event.clone()); -} -``` - -**Test it:** -```bash -cargo test --lib event_bus -``` - ---- - -### Task 4: Wire Everything in `start_query()` (6 hours) 🔴 - -**Why:** This is the coordinator that makes it all work. - -**File:** `src/api/janus_api.rs` - -**Uncomment and implement lines 128-140:** - -```rust -pub fn start_query(&self, query_id: &QueryId) -> Result { - // 1. Validate query exists - let metadata = self.registry.get(query_id).ok_or_else(|| - JanusApiError::RegistryError("Query not found".into()) - )?; - - // 2. Check not already running - { - let running_map = self.running.lock().unwrap(); - if running_map.contains_key(query_id) { - return Err(JanusApiError::ExecutionError( - "Query already running".into() - )); - } - } - - // 3. Create channels - let (result_tx, result_rx) = mpsc::channel::(); - let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>(); - - // 4. Spawn historical worker - let historical_handle = { - let storage = Arc::clone(&self.storage); - let metadata = metadata.clone(); - let result_tx = result_tx.clone(); - let shutdown_rx_clone = shutdown_rx; - - std::thread::spawn(move || { - let executor = HistoricalExecutor::new(storage); - - for window in &metadata.parsed.historical_windows { - // Check shutdown signal - if shutdown_rx_clone.try_recv().is_ok() { - break; - } - - for sparql in &metadata.parsed.sparql_queries { - match executor.execute_window( - &metadata.query_id, - window, - sparql - ) { - Ok(results) => { - for result in results { - result_tx.send(result).ok(); - } - } - Err(e) => eprintln!("Historical error: {}", e), - } - } - } - }) - }; - - // 5. Spawn live worker - let live_handle = { - let metadata = metadata.clone(); - let result_tx = result_tx.clone(); - - std::thread::spawn(move || { - // Initialize live processor - let mut processor = match LiveStreamProcessing::new( - metadata.parsed.rspql_query.clone() - ) { - Ok(p) => p, - Err(e) => { - eprintln!("Failed to init live processor: {}", e); - return; - } - }; - - // Register streams - for window in &metadata.parsed.live_windows { - if let Err(e) = processor.register_stream(&window.stream_uri) { - eprintln!("Failed to register stream: {}", e); - } - } - - // Start processing - if let Err(e) = processor.start_processing() { - eprintln!("Failed to start processing: {}", e); - return; - } - - // TODO: Subscribe to EventBus here - // For MVP, this will be added after EventBus integration - - // Poll for results - loop { - if let Some(result) = processor.try_receive_result() { - let qr = QueryResult { - query_id: metadata.query_id.clone(), - timestamp: result.timestamp as u64, - source: ResultSource::Live, - bindings: vec![result.bindings.into_iter() - .map(|(k, v)| (k, v.to_string())) - .collect()], - }; - result_tx.send(qr).ok(); - } - - std::thread::sleep(std::time::Duration::from_millis(10)); - } - }) - }; - - // 6. Store running query - { - let running_query = RunningQuery { - metadata: metadata.clone(), - status: Arc::new(RwLock::new(ExecutionStatus::Running)), - primary_sender: result_tx.clone(), - subscribers: Vec::new(), - historical_handle: Some(historical_handle), - live_handle: Some(live_handle), - shutdown_sender: vec![shutdown_tx], - }; - - self.running.lock().unwrap().insert( - query_id.clone(), - running_query - ); - } - - // 7. Increment execution count - self.registry.increment_execution_count(query_id).ok(); - - // 8. Return handle - Ok(QueryHandle { - query_id: query_id.clone(), - receiver: result_rx, - }) -} -``` - -**Test it:** -```bash -cargo test --lib janus_api -``` - ---- - -### Task 5: Create Query CLI (4 hours) 🟡 - -**File:** `src/bin/query_cli.rs` (new file) - -**Basic implementation:** - -```rust -use clap::{Parser, Subcommand}; -use janus::{ - api::janus_api::JanusApi, - parsing::janusql_parser::JanusQLParser, - registry::query_registry::QueryRegistry, - storage::{segmented_storage::StreamingSegmentedStorage, util::StreamingConfig}, -}; -use std::sync::Arc; - -#[derive(Parser)] -#[command(name = "query_cli")] -#[command(about = "Janus Query Execution CLI")] -struct Cli { - /// Storage path - #[arg(short, long, default_value = "./data/janus_storage")] - storage: String, - - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - Register { - #[arg(short, long)] - id: String, - - #[arg(short, long)] - query_file: String, - }, - - Execute { - #[arg(short, long)] - id: String, - - #[arg(short, long, default_value = "10")] - limit: usize, - }, -} - -fn main() -> Result<(), Box> { - let cli = Cli::parse(); - - // Initialize components - let storage = Arc::new(StreamingSegmentedStorage::new( - &cli.storage, - StreamingConfig::default(), - )?); - - let parser = JanusQLParser::new(); - let registry = Arc::new(QueryRegistry::new()); - let api = JanusApi::new(parser, registry, storage)?; - - match cli.command { - Commands::Register { id, query_file } => { - let query = std::fs::read_to_string(query_file)?; - let metadata = api.register_query(id.clone(), &query)?; - println!("✓ Registered query: {}", id); - println!(" RSP-QL: {}", metadata.parsed.rspql_query); - println!(" SPARQL queries: {}", metadata.parsed.sparql_queries.len()); - } - - Commands::Execute { id, limit } => { - println!("Starting query: {}", id); - let handle = api.start_query(&id)?; - - println!("Receiving results (limit: {})...\n", limit); - - for i in 0..limit { - if let Some(result) = handle.receive() { - println!("Result {} [{}]:", i + 1, - if result.source == ResultSource::Historical { - "Historical" - } else { - "Live" - } - ); - - for binding in result.bindings { - println!(" {:?}", binding); - } - } else { - break; - } - } - } - } - - Ok(()) -} -``` - -**Add to `Cargo.toml`:** -```toml -[[bin]] -name = "query_cli" -path = "src/bin/query_cli.rs" -``` - -**Test it:** -```bash -cargo build --bin query_cli -./target/debug/query_cli --help -``` - ---- - -### Task 6: Write Integration Test (2 hours) 🟡 - -**File:** `tests/mvp_integration_test.rs` - -```rust -use janus::{ - api::janus_api::{JanusApi, ResultSource}, - core::RDFEvent, - parsing::janusql_parser::JanusQLParser, - registry::query_registry::QueryRegistry, - storage::{segmented_storage::StreamingSegmentedStorage, util::StreamingConfig}, -}; -use std::sync::Arc; -use tempfile::tempdir; - -#[test] -fn test_mvp_hybrid_query_execution() { - // Setup - let temp_dir = tempdir().unwrap(); - let storage_path = temp_dir.path().join("storage"); - - let storage = Arc::new( - StreamingSegmentedStorage::new(&storage_path, StreamingConfig::default()) - .unwrap() - ); - - let parser = JanusQLParser::new(); - let registry = Arc::new(QueryRegistry::new()); - let api = JanusApi::new(parser, registry, Arc::clone(&storage)).unwrap(); - - // Ingest historical data - let events = vec![ - RDFEvent::new( - 1000, - "http://example.org/sensor1", - "http://example.org/temperature", - "23.5", - "http://example.org/graph1" - ), - ]; - - storage.write(&events).unwrap(); - std::thread::sleep(std::time::Duration::from_millis(200)); - - // Register query - let query = r#" - PREFIX ex: - REGISTER RStream AS - SELECT ?s ?temp - FROM NAMED WINDOW ex:w1 ON STREAM ex:stream1 [RANGE 60000 STEP 10000] - WHERE { - WINDOW ex:w1 { ?s ex:temperature ?temp } - } - "#; - - api.register_query("test_query".to_string(), query).unwrap(); - - // Start query - let handle = api.start_query(&"test_query".to_string()).unwrap(); - - // Receive results - let mut historical_count = 0; - for _ in 0..10 { - if let Some(result) = handle.try_receive() { - if result.source == ResultSource::Historical { - historical_count += 1; - } - } else { - break; - } - } - - assert!(historical_count > 0, "Should receive historical results"); -} -``` - -**Run it:** -```bash -cargo test --test mvp_integration_test -``` - ---- - -## Testing Your MVP - -### Step 1: Prepare Test Data - -```bash -# Create test data file -cat > data/test_sensors.nq << 'EOF' - "23.5" . - "24.1" . - "22.8" . -EOF -``` - -### Step 2: Ingest Historical Data - -```bash -cargo run --bin stream_bus_cli -- \ - --input data/test_sensors.nq \ - --broker none \ - --add-timestamps \ - --storage-path ./data/janus_storage -``` - -### Step 3: Create Query File - -```bash -cat > data/test_query.janusql << 'EOF' -PREFIX ex: -REGISTER RStream AS -SELECT ?sensor ?temp -FROM NAMED WINDOW ex:historical ON STREAM ex:stream1 [RANGE 3600000 STEP 600000] -WHERE { - WINDOW ex:historical { ?sensor ex:temperature ?temp } -} -EOF -``` - -### Step 4: Register Query - -```bash -cargo run --bin query_cli -- \ - --storage ./data/janus_storage \ - register \ - --id temp_monitor \ - --query-file data/test_query.janusql -``` - -### Step 5: Execute Query - -```bash -cargo run --bin query_cli -- \ - --storage ./data/janus_storage \ - execute \ - --id temp_monitor \ - --limit 10 -``` - -**Expected output:** -``` -Starting query: temp_monitor -Receiving results (limit: 10)... - -Result 1 [Historical]: - {"?sensor": "http://example.org/sensor1", "?temp": "23.5"} -Result 2 [Historical]: - {"?sensor": "http://example.org/sensor2", "?temp": "24.1"} -Result 3 [Historical]: - {"?sensor": "http://example.org/sensor3", "?temp": "22.8"} -``` - ---- - -## Troubleshooting - -### "Query not found" -- Make sure you registered the query first -- Check storage path is consistent - -### No historical results -- Verify data was ingested: `ls -lh data/janus_storage/` -- Check time ranges in query match ingested data timestamps -- Add debug logging to `HistoricalExecutor` - -### No live results -- EventBus integration not complete yet (Phase 2) -- For MVP, focus on historical path first - -### SPARQL errors -- Check query syntax in generated SPARQL -- Print `metadata.parsed.sparql_queries` in CLI - ---- - -## Success Criteria Checklist - -- [ ] Task 1: SPARQL bindings format fixed -- [ ] Task 2: HistoricalExecutor implemented -- [ ] Task 3: EventBus created -- [ ] Task 4: `start_query()` working -- [ ] Task 5: Query CLI functional -- [ ] Task 6: Integration test passing -- [ ] Can register query via CLI -- [ ] Can execute query via CLI -- [ ] Receive historical results -- [ ] Results formatted correctly -- [ ] No panics or crashes - ---- - -## After MVP Works - -Once you have historical queries working: - -1. **Add EventBus to live worker** in `start_query()` -2. **Test live processing** by streaming new data -3. **Add HTTP/WebSocket API** for Flutter dashboard -4. **Docker Compose** for Kafka/MQTT testing -5. **Production hardening** (logging, monitoring, error handling) - ---- - -## Questions? - -Refer to: -- **`MVP_TODO.md`** - Detailed task breakdown -- **`MVP_ARCHITECTURE.md`** - Architecture diagrams -- **`STREAM_BUS_CLI.md`** - Data ingestion docs -- **`.github/copilot-instructions.md`** - Code conventions - -**Key insight:** You're 80% there! The storage, parser, and engines all work. You just need the coordinator (`start_query()`) to orchestrate them. Start with historical path (easier), then add live. \ No newline at end of file diff --git a/docs/QUICKSTART_HTTP_API.md b/docs/QUICKSTART_HTTP_API.md deleted file mode 100644 index 960753e..0000000 --- a/docs/QUICKSTART_HTTP_API.md +++ /dev/null @@ -1,285 +0,0 @@ -# Janus HTTP API - Quick Start Guide - -Get started with the Janus HTTP API in under 5 minutes. - -## Prerequisites - -- Rust 1.70+ installed -- Data file for testing (e.g., `data/sensors.nq`) - -## 1. Start the HTTP Server - -```bash -# Clone and navigate to the project -cd janus - -# Build and run the HTTP server -cargo run --bin http_server - -# Server will start on http://127.0.0.1:8080 -``` - -**Custom Configuration:** -```bash -cargo run --bin http_server -- \ - --host 0.0.0.0 \ - --port 8080 \ - --storage-dir ./data/storage \ - --max-batch-size-bytes 10485760 \ - --flush-interval-ms 5000 -``` - -## 2. Open the Demo Dashboard - -Open `examples/demo_dashboard.html` in your browser: - -```bash -# macOS -open examples/demo_dashboard.html - -# Linux -xdg-open examples/demo_dashboard.html - -# Windows -start examples/demo_dashboard.html -``` - -The dashboard provides two main buttons: -- **Start Replay**: Begins ingesting RDF data from file into storage -- **Start Query**: Executes a JanusQL query and streams results - -## 3. Quick Test with cURL - -### Register a Query -```bash -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{ - "query_id": "test_query", - "janusql": "PREFIX ex: SELECT ?s ?p ?o FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] WHERE { WINDOW ex:histWindow { ?s ?p ?o . } }" - }' -``` - -### Start Stream Replay -```bash -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{ - "input_file": "data/sensors.nq", - "broker_type": "none", - "topics": ["sensors"], - "rate_of_publishing": 1000, - "loop_file": false, - "add_timestamps": true - }' -``` - -### Start Query Execution -```bash -curl -X POST http://localhost:8080/api/queries/test_query/start -``` - -### Get Replay Status -```bash -curl http://localhost:8080/api/replay/status -``` - -### List All Queries -```bash -curl http://localhost:8080/api/queries -``` - -### Stop Query -```bash -curl -X DELETE http://localhost:8080/api/queries/test_query -``` - -## 4. WebSocket Streaming Example - -### JavaScript (Browser Console) -```javascript -const ws = new WebSocket('ws://localhost:8080/api/queries/test_query/results'); - -ws.onmessage = (event) => { - const result = JSON.parse(event.data); - console.log('Query Result:', result); - console.log(' Source:', result.source); // 'historical' or 'live' - console.log(' Timestamp:', result.timestamp); - console.log(' Bindings:', result.bindings); -}; - -ws.onerror = (error) => console.error('WebSocket Error:', error); -ws.onclose = () => console.log('WebSocket Closed'); -``` - -### Python -```python -import websocket -import json - -def on_message(ws, message): - result = json.loads(message) - print(f"Result: {result}") - -def on_error(ws, error): - print(f"Error: {error}") - -def on_close(ws, close_status_code, close_msg): - print("Connection closed") - -ws = websocket.WebSocketApp( - "ws://localhost:8080/api/queries/test_query/results", - on_message=on_message, - on_error=on_error, - on_close=on_close -) - -ws.run_forever() -``` - -## 5. Run the Complete Example - -```bash -# Terminal 1: Start the server -cargo run --bin http_server - -# Terminal 2: Run the example client -cargo run --example http_client_example -``` - -The example demonstrates: -- Registering queries -- Starting/stopping replay -- Starting/stopping queries -- WebSocket result streaming -- All API endpoints - -## API Endpoints Summary - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | `/health` | Health check | -| `POST` | `/api/queries` | Register a query | -| `GET` | `/api/queries` | List all queries | -| `GET` | `/api/queries/:id` | Get query details | -| `POST` | `/api/queries/:id/start` | Start query | -| `DELETE` | `/api/queries/:id` | Stop query | -| `WS` | `/api/queries/:id/results` | Stream results | -| `POST` | `/api/replay/start` | Start replay | -| `POST` | `/api/replay/stop` | Stop replay | -| `GET` | `/api/replay/status` | Replay status | - -## Common Workflows - -### Workflow 1: Historical Data Analysis -```bash -# 1. Start server -cargo run --bin http_server - -# 2. Load data into storage -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{"input_file": "data/sensors.nq", "broker_type": "none", "rate_of_publishing": 10000}' - -# 3. Wait for data ingestion (check status) -curl http://localhost:8080/api/replay/status - -# 4. Register and start query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{"query_id": "analysis", "janusql": "PREFIX ex: SELECT ?sensor ?temp FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] WHERE { WINDOW ex:histWindow { ?sensor ex:temperature ?temp . FILTER(?temp > 25.0) } }"}' - -curl -X POST http://localhost:8080/api/queries/analysis/start - -# 5. Connect WebSocket to get results -# (Use browser console or WebSocket client) -``` - -### Workflow 2: Live Stream Processing -```bash -# 1. Register live query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{"query_id": "live_monitor", "janusql": "PREFIX ex: SELECT ?sensor ?temp FROM NAMED WINDOW ex:liveWindow ON STREAM ex:sensorStream [RANGE 10000 STEP 5000] WHERE { WINDOW ex:liveWindow { ?sensor ex:temperature ?temp . } }"}' - -# 2. Start query (before replay to catch all events) -curl -X POST http://localhost:8080/api/queries/live_monitor/start - -# 3. Start replay with looping for continuous stream -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{"input_file": "data/sensors.nq", "broker_type": "none", "rate_of_publishing": 100, "loop_file": true}' - -# 4. Connect WebSocket to stream live results -``` - -### Workflow 3: Hybrid (Historical + Live) -```bash -# Register hybrid query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{"query_id": "hybrid", "janusql": "PREFIX ex: REGISTER RStream ex:output AS SELECT ?s ?p ?o FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153600] FROM NAMED WINDOW ex:liveWindow ON STREAM ex:sensorStream [RANGE 30000 STEP 10000] WHERE { WINDOW ex:histWindow { ?s ?p ?o . } WINDOW ex:liveWindow { ?s ?p ?o . } }"}' - -# Start replay first to populate historical data -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{"input_file": "data/sensors.nq", "broker_type": "none", "rate_of_publishing": 5000, "loop_file": true}' - -# Start query - will process historical first, then live -curl -X POST http://localhost:8080/api/queries/hybrid/start - -# WebSocket will receive both historical and live results -# Results tagged with "source": "historical" or "source": "live" -``` - -## Troubleshooting - -### Server won't start -```bash -# Check if port 8080 is already in use -lsof -i :8080 - -# Use a different port -cargo run --bin http_server -- --port 8081 -``` - -### No results from query -- Ensure replay is running: `curl http://localhost:8080/api/replay/status` -- Check query syntax is valid -- Verify data file exists and is valid N-Quads format -- Check server logs for errors - -### WebSocket connection fails -- Ensure query is registered AND started before connecting -- Check browser console for CORS errors -- Verify WebSocket URL matches the query ID -- Try `ws://` not `wss://` for local testing - -### Data not persisting -- Check storage directory exists and is writable -- Verify `--storage-dir` path is correct -- Check disk space availability - -## Next Steps - -1. Read the full [HTTP API Documentation](HTTP_API.md) -2. Learn [JanusQL Query Language](JANUSQL.md) -3. Explore [Stream Bus Configuration](STREAM_BUS.md) -4. Review [Architecture Overview](ARCHITECTURE.md) -5. Check [Benchmark Results](BENCHMARK_RESULTS.md) - -## Example Data Format - -If you need test data, create `data/sensors.nq`: - -```nquads - "23.5"^^ . - "2024-01-01T12:00:00Z"^^ . - "26.8"^^ . - "2024-01-01T12:00:01Z"^^ . -``` - -## Support - -- GitHub Issues: https://github.com/SolidLabResearch/janus/issues -- Documentation: See `HTTP_API.md` for complete API reference diff --git a/docs/QUICK_REFERENCE.md b/docs/QUICK_REFERENCE.md index 0aaf743..90d58cd 100644 --- a/docs/QUICK_REFERENCE.md +++ b/docs/QUICK_REFERENCE.md @@ -1,20 +1,22 @@ -# Janus HTTP API - Quick Reference +# Janus Quick Reference -## Setup (3 Commands) +## Setup ```bash -./scripts/test_setup.sh # One-time setup -docker-compose up -d mosquitto # Start MQTT -cargo run --bin http_server # Start server +docker-compose up -d mosquitto +cargo run --bin http_server -- --host 127.0.0.1 --port 8080 --storage-dir ./data/storage +cargo run --example http_client_example ``` -## Demo Dashboard +## Optional Manual Demo ```bash open examples/demo_dashboard.html -# Click: Start Replay → Start Query ``` +The static HTML demo is only for local manual testing. The maintained web +dashboard lives in the separate `janus-dashboard` repository. + ## API Endpoints ```bash @@ -26,7 +28,8 @@ POST /api/queries # Register GET /api/queries # List all GET /api/queries/:id # Details POST /api/queries/:id/start # Start -DELETE /api/queries/:id # Stop +POST /api/queries/:id/stop # Stop +DELETE /api/queries/:id # Delete stopped query WS /api/queries/:id/results # Stream # Replay @@ -96,27 +99,13 @@ docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v docker-compose restart mosquitto ``` -## File Locations - -``` -janus/ -├── examples/demo_dashboard.html # Interactive UI -├── COMPLETE_SOLUTION.md # Full explanation -├── SETUP_GUIDE.md # Detailed setup -├── README_HTTP_API.md # API guide -└── ./scripts/test_setup.sh # Automated setup -``` - ## Success Checklist - [ ] MQTT running: `docker ps | grep mosquitto` - [ ] Server running: `curl localhost:8080/health` -- [ ] Data exists: `ls data/sensors.nq` -- [ ] Dashboard opens: `open examples/demo_dashboard.html` -- [ ] Replay works: Click "Start Replay" -- [ ] Query works: Click "Start Query" -- [ ] Results appear in dashboard +- [ ] Example client runs: `cargo run --example http_client_example` +- [ ] Optional demo opens: `open examples/demo_dashboard.html` --- -**Quick Start:** `./scripts/test_setup.sh` then `cargo run --bin http_server` +**Quick Start:** `cargo run --bin http_server` then `cargo run --example http_client_example` diff --git a/docs/README.md b/docs/README.md index 190afd8..1674139 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,8 @@ This directory contains the project documentation for Janus. -Some older files in this directory are design notes, implementation logs, or milestone-specific writeups. The files below are the current starting point for understanding how Janus works today. +Some files here are current product documentation. Others are older design or +milestone notes kept only for background context. ## Start Here @@ -12,15 +13,16 @@ Some older files in this directory are design notes, implementation logs, or mil - [BASELINES.md](./BASELINES.md): `USING BASELINE`, `LAST`, `AGGREGATE`, and async warm-up - [HTTP_API_CURRENT.md](./HTTP_API_CURRENT.md): current REST and WebSocket API - [ANOMALY_DETECTION.md](./ANOMALY_DETECTION.md): recommended anomaly-detection patterns and limitations +- [QUICK_REFERENCE.md](./QUICK_REFERENCE.md): short operational commands and endpoint summary ## Supporting Material - [ARCHITECTURE.md](./ARCHITECTURE.md): older high-level architecture notes - [EXECUTION_ARCHITECTURE.md](./EXECUTION_ARCHITECTURE.md): historical execution design notes -- [HTTP_API.md](./HTTP_API.md): earlier HTTP API writeup - [BENCHMARK_RESULTS.md](./BENCHMARK_RESULTS.md): benchmark data ## Notes -- The canonical docs above are intended to describe the current implementation on `main` once merged. -- Older files are still useful for background, but they may describe previous milestones or implementation states. +- The files listed under Start Here are the current sources of truth for `main`. +- Frontend development does not happen in this repository. The maintained web + dashboard lives in the separate `janus-dashboard` repository. diff --git a/docs/README_HTTP_API.md b/docs/README_HTTP_API.md deleted file mode 100644 index b79dbb9..0000000 --- a/docs/README_HTTP_API.md +++ /dev/null @@ -1,465 +0,0 @@ -# Janus HTTP API - Complete Guide - -> Unified Live and Historical RDF Stream Processing via HTTP/WebSocket - -## Overview - -The Janus HTTP API provides REST endpoints and WebSocket streaming for managing and executing RDF stream queries. It supports both historical data analysis and live stream processing through a unified interface. - -## Quick Start (3 Steps) - -### 1. Start MQTT Broker - -```bash -docker-compose up -d mosquitto -``` - -### 2. Start HTTP Server - -```bash -cargo run --bin http_server -``` - -### 3. Open Demo Dashboard - -```bash -open examples/demo_dashboard.html -``` - -Click "Start Replay" then "Start Query" to see live results. - -## Complete Setup - -### Prerequisites - -- Rust 1.70+ -- Docker & Docker Compose -- Sample data file (provided) - -### Installation - -```bash -# Clone repository -git clone https://github.com/SolidLabResearch/janus.git -cd janus - -# Run automated setup -./scripts/test_setup.sh - -# Start HTTP server (in new terminal) -cargo run --bin http_server - -# Open dashboard -open examples/demo_dashboard.html -``` - -## JanusQL Query Syntax - -### Historical Query - -```sparql -PREFIX ex: -REGISTER RStream ex:output AS -SELECT ?sensor ?temp ?time -FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] -WHERE { - WINDOW ex:histWindow { - ?sensor ex:temperature ?temp . - ?sensor ex:timestamp ?time . - } -} -``` - -### Live Query - -```sparql -PREFIX ex: -REGISTER RStream ex:output AS -SELECT ?sensor ?temp -FROM NAMED WINDOW ex:liveWindow ON STREAM ex:sensorStream [RANGE 10000 STEP 5000] -WHERE { - WINDOW ex:liveWindow { - ?sensor ex:temperature ?temp . - } -} -``` - -### Hybrid Query (Historical + Live) - -```sparql -PREFIX ex: -REGISTER RStream ex:output AS -SELECT ?sensor ?temp -FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153599] -FROM NAMED WINDOW ex:liveWindow ON STREAM ex:sensorStream [RANGE 30000 STEP 10000] -WHERE { - WINDOW ex:histWindow { - ?sensor ex:temperature ?temp . - } - WINDOW ex:liveWindow { - ?sensor ex:temperature ?temp . - } -} -``` - -## HTTP API Endpoints - -### Query Management - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/queries` | Register a query | -| GET | `/api/queries` | List all queries | -| GET | `/api/queries/:id` | Get query details | -| POST | `/api/queries/:id/start` | Start query execution | -| DELETE | `/api/queries/:id` | Stop query | -| WS | `/api/queries/:id/results` | Stream results | - -### Stream Replay - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/replay/start` | Start data replay | -| POST | `/api/replay/stop` | Stop replay | -| GET | `/api/replay/status` | Get replay metrics | - -## Usage Examples - -### Register and Start Query - -```bash -# Register query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{ - "query_id": "sensor_analysis", - "janusql": "PREFIX ex: REGISTER RStream ex:output AS SELECT ?sensor ?temp FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] WHERE { WINDOW ex:histWindow { ?sensor ex:temperature ?temp . } }" - }' - -# Start query -curl -X POST http://localhost:8080/api/queries/sensor_analysis/start -``` - -### Start Replay with MQTT - -```bash -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{ - "input_file": "data/sensors.nq", - "broker_type": "mqtt", - "topics": ["sensors"], - "rate_of_publishing": 1000, - "loop_file": true, - "mqtt_config": { - "host": "localhost", - "port": 1883, - "client_id": "janus_client", - "keep_alive_secs": 30 - } - }' -``` - -### WebSocket Streaming (JavaScript) - -```javascript -const ws = new WebSocket('ws://localhost:8080/api/queries/sensor_analysis/results'); - -ws.onmessage = (event) => { - const result = JSON.parse(event.data); - console.log('Source:', result.source); // "historical" or "live" - console.log('Timestamp:', result.timestamp); - console.log('Bindings:', result.bindings); -}; -``` - -## Architecture - -### Components - -``` -┌─────────────────┐ -│ Web Dashboard │ -│ (Browser) │ -└────────┬────────┘ - │ HTTP/WebSocket - ▼ -┌─────────────────┐ -│ HTTP Server │ -│ (Axum/Tokio) │ -└────────┬────────┘ - │ - ┌────┴─────┐ - │ │ - ▼ ▼ -┌─────────┐ ┌──────────┐ -│ Storage │ │ JanusAPI │ -└─────────┘ └─────┬────┘ - │ - ┌────────┴────────┐ - │ │ - ▼ ▼ - ┌──────────┐ ┌──────────┐ - │Historical│ │ Live │ - │ Executor │ │Processor │ - └──────────┘ └─────┬────┘ - │ - ▼ - ┌──────────┐ - │ MQTT │ - │ Broker │ - └──────────┘ -``` - -### Data Flow - -1. **Historical Processing**: - - Data loaded into storage via replay - - Query executes against stored data - - Results returned via WebSocket - -2. **Live Processing**: - - Data published to MQTT topic - - Live processor subscribes to topic - - Results streamed in real-time via WebSocket - -3. **Hybrid Processing**: - - Historical results sent first - - Live results streamed continuously - - All tagged with source type - -## Configuration - -### Server Options - -```bash -cargo run --bin http_server -- \ - --host 0.0.0.0 \ - --port 8080 \ - --storage-dir ./data/storage \ - --max-batch-size-bytes 10485760 \ - --flush-interval-ms 5000 -``` - -### MQTT Configuration - -Edit `docker/mosquitto/config/mosquitto.conf`: - -```conf -listener 1883 -allow_anonymous true -persistence true -persistence_location /mosquitto/data/ -``` - -## Troubleshooting - -### MQTT Broker Issues - -```bash -# Check if running -docker ps | grep mosquitto - -# View logs -docker-compose logs -f mosquitto - -# Restart -docker-compose restart mosquitto -``` - -### No Live Query Results - -**Checklist:** -1. MQTT broker is running -2. Replay using `broker_type: "mqtt"` -3. Query started before replay (or replay is looping) -4. MQTT topic matches stream name in query - -**Debug:** -```bash -# Subscribe to MQTT topic to verify messages -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v -``` - -### WebSocket Connection Fails - -**Checklist:** -1. Query is registered: `GET /api/queries` -2. Query is started: `POST /api/queries/:id/start` -3. Correct URL: `ws://localhost:8080/api/queries/:id/results` - -**Test in browser console:** -```javascript -const ws = new WebSocket('ws://localhost:8080/api/queries/your_id/results'); -ws.onopen = () => console.log('Connected'); -ws.onerror = (e) => console.error('Error:', e); -``` - -## Demo Dashboard Features - -The interactive dashboard (`examples/demo_dashboard.html`) provides: - -- **Start Replay** - Begins data ingestion with MQTT publishing -- **Start Query** - Executes query and streams results -- **Real-time Metrics** - Events read, stored, processing rate -- **Live Results** - Color-coded historical vs. live results -- **Status Monitoring** - Connection status, error handling - -## Example Client - -Run the complete example demonstrating all endpoints: - -```bash -cargo run --example http_client_example -``` - -This demonstrates: -- Health check -- Query registration -- Query lifecycle management -- Stream replay control -- WebSocket result streaming -- Error handling - -## Performance - -### Benchmarks - -- **Write Throughput**: 2.6-3.14 Million quads/sec -- **Query Latency**: Sub-millisecond point queries -- **Compression**: 40% reduction (40 bytes → 24 bytes per quad) -- **WebSocket**: Low-latency streaming (<10ms) - -### Tuning - -**High Throughput:** -```bash -cargo run --bin http_server -- \ - --max-batch-size-bytes 52428800 \ - --flush-interval-ms 1000 -``` - -**Low Latency:** -```bash -cargo run --bin http_server -- \ - --max-batch-size-bytes 1048576 \ - --flush-interval-ms 100 -``` - -## Production Deployment - -### Security Recommendations - -- Add authentication (JWT, OAuth2) -- Enable HTTPS/WSS -- Restrict CORS origins -- Add rate limiting -- Enable MQTT authentication -- Use firewall rules - -### Example nginx Configuration - -```nginx -server { - listen 443 ssl; - server_name janus.example.com; - - ssl_certificate /path/to/cert.pem; - ssl_certificate_key /path/to/key.pem; - - location / { - proxy_pass http://localhost:8080; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } -} -``` - -## Documentation - -- **[SETUP_GUIDE.md](SETUP_GUIDE.md)** - Detailed setup instructions -- **[HTTP_API_IMPLEMENTATION.md](HTTP_API_IMPLEMENTATION.md)** - Implementation details -- **[ARCHITECTURE.md](ARCHITECTURE.md)** - System architecture -- **[BENCHMARK_RESULTS.md](BENCHMARK_RESULTS.md)** - Performance metrics - -## Testing - -```bash -# Run tests -cargo test - -# Build and run server -cargo run --bin http_server - -# Run example client -cargo run --example http_client_example - -# Format code -make fmt - -# Lint -make clippy -``` - -## Common Workflows - -### Analyze Historical Data - -1. Start server and MQTT -2. Load data: `POST /api/replay/start` (broker_type: "none" or "mqtt") -3. Register query: `POST /api/queries` -4. Start query: `POST /api/queries/:id/start` -5. Connect WebSocket for results - -### Process Live Streams - -1. Start server and MQTT -2. Register live query: `POST /api/queries` -3. Start query: `POST /api/queries/:id/start` -4. Start replay: `POST /api/replay/start` (broker_type: "mqtt", loop_file: true) -5. Receive live results via WebSocket - -### Hybrid Analysis - -1. Start server and MQTT -2. Register hybrid query (historical + live windows) -3. Start replay with MQTT -4. Wait for historical data to load -5. Start query -6. Receive historical results, then live results (both tagged) - -## Support - -- **GitHub**: https://github.com/SolidLabResearch/janus -- **Issues**: https://github.com/SolidLabResearch/janus/issues -- **Documentation**: Complete API docs in `docs/` directory - -## License - -MIT - -## Citation - -If you use Janus in your research, please cite: - -```bibtex -@software{janus2024, - title = {Janus: Unified Live and Historical RDF Stream Processing}, - author = {Bisen, Kush}, - year = {2024}, - url = {https://github.com/SolidLabResearch/janus} -} -``` - -## Contributors - -See [CONTRIBUTORS.md](CONTRIBUTORS.md) - ---- - -**Ready to process RDF streams!** 🚀 - -For questions or issues, please open a GitHub issue or refer to the comprehensive documentation in the `docs/` directory. \ No newline at end of file diff --git a/docs/RUNTIME_FIX_SUMMARY.md b/docs/RUNTIME_FIX_SUMMARY.md deleted file mode 100644 index dcad573..0000000 --- a/docs/RUNTIME_FIX_SUMMARY.md +++ /dev/null @@ -1,117 +0,0 @@ -# Runtime Conflict Fix - Summary - -## Problem - -When starting the HTTP server, it crashed with: -``` -thread 'tokio-runtime-worker' panicked at: -Cannot drop a runtime in a context where blocking is not allowed. -This happens when a runtime is dropped from within an asynchronous context. -``` - -## Root Cause - -`StreamBus::new()` creates its own Tokio runtime internally. When called from within the HTTP server's async context (which also uses Tokio), this created a nested runtime situation that Tokio doesn't allow. - -## Solution - -Modified `src/http/server.rs` to spawn `StreamBus` in a separate blocking thread: - -```rust -// Before (caused panic) -let stream_bus = StreamBus::new(bus_config, Arc::clone(&state.storage)); -stream_bus.start()?; - -// After (works correctly) -std::thread::spawn(move || { - let stream_bus = StreamBus::new(bus_config, storage); - if let Err(e) = stream_bus.start() { - eprintln!("Stream bus replay error: {}", e); - } -}); -``` - -## Trade-off - -**Lost**: Real-time event counter metrics from `/api/replay/status` -**Gained**: Stable, non-crashing server that actually works - -The replay still functions correctly - it reads data, publishes to MQTT, and stores quads. We just can't track detailed metrics from the HTTP API because the thread boundary prevents shared access to atomic counters. - -## What Works Now - -✅ Server starts without panics -✅ Health endpoint responds -✅ Replay runs in background thread -✅ Data flows to MQTT and storage -✅ Queries execute against data -✅ WebSocket streaming works -✅ Demo dashboard functional - -## What Shows Limited Info - -⚠️ `/api/replay/status` shows: -- `is_running`: ✅ Accurate -- `elapsed_seconds`: ✅ Accurate -- `events_read`: ⚠️ Always 0 -- `events_published`: ⚠️ Always 0 -- `events_stored`: ⚠️ Always 0 - -## Alternative Verification Methods - -### Check MQTT Activity -```bash -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v -# You'll see messages flowing if replay is working -``` - -### Check Storage Directory -```bash -ls -lh data/storage/ -# New files appear as data is stored -``` - -### Monitor Logs -```bash -# StreamBus prints progress to stdout -# Watch terminal where server is running -``` - -## Future Improvement - -To restore detailed metrics, refactor `StreamBus` to: -1. Accept optional external runtime instead of creating its own -2. Use channels to communicate metrics back to HTTP server -3. Or expose shared atomic counters that can be read across threads - -For now, the current solution is **production-ready for MVP** - the replay works, queries execute, and results stream correctly. - -## Testing - -```bash -# 1. Start server -cargo run --bin http_server - -# 2. Open dashboard -open examples/demo_dashboard.html - -# 3. Click "Start Replay" -# - Button disables -# - Status shows "Running" -# - Elapsed time increments - -# 4. Verify MQTT activity -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v -# Should see RDF quads flowing - -# 5. Click "Start Query" -# - WebSocket connects -# - Results appear in panel -# - Tagged as "historical" or "live" -``` - -## Status - -**FIXED** ✅ - -Server is stable and functional. The metric limitation is documented and has acceptable workarounds. diff --git a/docs/SETUP_GUIDE.md b/docs/SETUP_GUIDE.md deleted file mode 100644 index 765ff24..0000000 --- a/docs/SETUP_GUIDE.md +++ /dev/null @@ -1,530 +0,0 @@ -# Janus HTTP API - Complete Setup Guide - -This guide will walk you through setting up Janus with MQTT for both historical and live stream processing. - -## Prerequisites - -- Rust 1.70+ (`rustup update`) -- Docker and Docker Compose (for MQTT broker) -- Git - -## Quick Start (5 minutes) - -### Step 1: Start MQTT Broker - -```bash -# Navigate to janus directory -cd janus - -# Start Mosquitto MQTT broker with Docker Compose -docker-compose up -d - -# Verify MQTT is running -docker-compose ps -``` - -Expected output: -``` -NAME STATUS PORTS -janus-mosquitto Up 0.0.0.0:1883->1883/tcp, 0.0.0.0:9001->9001/tcp -``` - -### Step 2: Start Janus HTTP Server - -```bash -# In the janus directory -cargo run --bin http_server - -# Server will start on http://127.0.0.1:8080 -``` - -### Step 3: Open Demo Dashboard - -```bash -# Open in your default browser -open examples/demo_dashboard.html - -# Or manually navigate to: -# file:///path/to/janus/examples/demo_dashboard.html -``` - -### Step 4: Test the System - -1. **Click "Start Replay"** button - - Loads data from `data/sensors.nq` - - Publishes to MQTT topic `sensors` - - Stores in local storage - - Watch the metrics update in real-time - -2. **Click "Start Query"** button - - Registers and starts a historical query - - Connects WebSocket for results - - Watch results appear in the panel below - -## Detailed Setup - -### 1. Clone and Build - -```bash -# Clone the repository -git clone https://github.com/SolidLabResearch/janus.git -cd janus - -# Build the project -cargo build --release - -# Verify build -./target/release/http_server --help -``` - -### 2. MQTT Broker Setup - -#### Option A: Docker Compose (Recommended) - -```bash -# Start MQTT broker -docker-compose up -d mosquitto - -# Check logs -docker-compose logs -f mosquitto - -# Stop when done -docker-compose down -``` - -#### Option B: Local Mosquitto Installation - -**macOS:** -```bash -brew install mosquitto -mosquitto -c /usr/local/etc/mosquitto/mosquitto.conf -``` - -**Linux (Ubuntu/Debian):** -```bash -sudo apt-get install mosquitto mosquitto-clients -sudo systemctl start mosquitto -sudo systemctl enable mosquitto -``` - -**Windows:** -Download from https://mosquitto.org/download/ - -Configuration file (`mosquitto.conf`): -```conf -listener 1883 -allow_anonymous true -``` - -#### Option C: Public MQTT Broker (Testing Only) - -You can use a public broker for testing: -- `test.mosquitto.org:1883` -- `broker.hivemq.com:1883` - -**Note:** Public brokers are NOT recommended for production or sensitive data. - -### 3. Prepare Test Data - -Create `data/sensors.nq` with sample RDF data: - -```bash -mkdir -p data - -cat > data/sensors.nq << 'EOF' - "23.5"^^ . - "2024-01-01T12:00:00Z"^^ . - "26.8"^^ . - "2024-01-01T12:00:01Z"^^ . - "21.2"^^ . - "2024-01-01T12:00:02Z"^^ . -EOF -``` - -### 4. Start HTTP Server - -```bash -# Default configuration (localhost:8080) -cargo run --bin http_server - -# Custom configuration -cargo run --bin http_server -- \ - --host 0.0.0.0 \ - --port 8080 \ - --storage-dir ./data/storage \ - --max-batch-size-bytes 10485760 \ - --flush-interval-ms 5000 -``` - -Server options: -- `--host`: Bind address (default: 127.0.0.1) -- `--port`: Server port (default: 8080) -- `--storage-dir`: Storage directory (default: ./data/storage) -- `--max-batch-size-bytes`: Max batch size before flush (default: 10MB) -- `--flush-interval-ms`: Flush interval in milliseconds (default: 5000ms) - -### 5. Verify Setup - -#### Test MQTT Broker - -```bash -# Terminal 1: Subscribe to test topic -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v - -# Terminal 2: Publish test message -docker exec -it janus-mosquitto mosquitto_pub -t "sensors" -m "test message" -``` - -You should see "test message" in Terminal 1. - -#### Test HTTP Server - -```bash -# Health check -curl http://localhost:8080/health - -# Should return: {"message":"Janus HTTP API is running"} -``` - -## Usage Workflows - -### Workflow 1: Historical Query Only - -```bash -# Terminal 1: Start server -cargo run --bin http_server - -# Terminal 2: Register historical query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{ - "query_id": "historical_temps", - "janusql": "PREFIX ex: REGISTER RStream ex:output AS SELECT ?sensor ?temp FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1735689599] WHERE { WINDOW ex:histWindow { ?sensor ex:temperature ?temp . } }" - }' - -# Start replay (to populate storage) -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{ - "input_file": "data/sensors.nq", - "broker_type": "none", - "topics": ["sensors"], - "rate_of_publishing": 5000 - }' - -# Wait a few seconds, then start query -curl -X POST http://localhost:8080/api/queries/historical_temps/start - -# Connect WebSocket to get results (use browser console or websocket client) -``` - -### Workflow 2: Live Stream Processing - -```bash -# Ensure MQTT is running -docker-compose up -d mosquitto - -# Register live query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{ - "query_id": "live_temps", - "janusql": "PREFIX ex: REGISTER RStream ex:output AS SELECT ?sensor ?temp FROM NAMED WINDOW ex:liveWindow ON STREAM ex:sensorStream [RANGE 10000 STEP 5000] WHERE { WINDOW ex:liveWindow { ?sensor ex:temperature ?temp . } }" - }' - -# Start query (before replay to catch all events) -curl -X POST http://localhost:8080/api/queries/live_temps/start - -# Start replay with MQTT -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{ - "input_file": "data/sensors.nq", - "broker_type": "mqtt", - "topics": ["sensors"], - "rate_of_publishing": 100, - "loop_file": true, - "mqtt_config": { - "host": "localhost", - "port": 1883, - "client_id": "janus_client", - "keep_alive_secs": 30 - } - }' - -# Results will stream via WebSocket at ws://localhost:8080/api/queries/live_temps/results -``` - -### Workflow 3: Hybrid (Historical + Live) - -```bash -# Register hybrid query -curl -X POST http://localhost:8080/api/queries \ - -H "Content-Type: application/json" \ - -d '{ - "query_id": "hybrid_analysis", - "janusql": "PREFIX ex: REGISTER RStream ex:output AS SELECT ?sensor ?temp FROM NAMED WINDOW ex:histWindow ON STREAM ex:sensorStream [START 1704067200 END 1704153599] FROM NAMED WINDOW ex:liveWindow ON STREAM ex:sensorStream [RANGE 30000 STEP 10000] WHERE { WINDOW ex:histWindow { ?sensor ex:temperature ?temp . } WINDOW ex:liveWindow { ?sensor ex:temperature ?temp . } }" - }' - -# Start replay with MQTT -curl -X POST http://localhost:8080/api/replay/start \ - -H "Content-Type: application/json" \ - -d '{ - "input_file": "data/sensors.nq", - "broker_type": "mqtt", - "topics": ["sensors"], - "rate_of_publishing": 1000, - "loop_file": true, - "mqtt_config": { - "host": "localhost", - "port": 1883, - "client_id": "janus_hybrid", - "keep_alive_secs": 30 - } - }' - -# Wait for data to load into storage -sleep 5 - -# Start query - will process historical first, then live -curl -X POST http://localhost:8080/api/queries/hybrid_analysis/start - -# WebSocket will receive: -# - Historical results tagged with "source": "historical" -# - Live results tagged with "source": "live" -``` - -## Monitoring and Debugging - -### Monitor MQTT Messages - -```bash -# Subscribe to all topics -docker exec -it janus-mosquitto mosquitto_sub -t "#" -v - -# Subscribe to specific topic -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v -``` - -### Check Replay Status - -```bash -curl http://localhost:8080/api/replay/status | jq -``` - -### Check Query Status - -```bash -curl http://localhost:8080/api/queries/your_query_id | jq -``` - -### View Server Logs - -```bash -# Server logs are printed to stdout -# Look for: -# - "Janus HTTP API server listening on..." -# - Query registration confirmations -# - Error messages -``` - -### MQTT Broker Logs - -```bash -docker-compose logs -f mosquitto -``` - -## Troubleshooting - -### MQTT Broker Won't Start - -**Check if port 1883 is in use:** -```bash -lsof -i :1883 -``` - -**Solution:** Kill the process or use a different port -```bash -# Edit docker-compose.yml to change port -# Then restart -docker-compose down -docker-compose up -d -``` - -### No Data in MQTT Topic - -**Verify replay is publishing to MQTT:** -```bash -# Check replay status -curl http://localhost:8080/api/replay/status - -# Should show broker_type: "mqtt" -``` - -**Subscribe to topic to verify messages:** -```bash -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v -``` - -### Live Query Not Receiving Events - -**Checklist:** -1. MQTT broker is running: `docker-compose ps` -2. Replay is using `broker_type: "mqtt"` -3. Query is started BEFORE replay (or replay is looping) -4. MQTT topic matches the query's stream name -5. Live window specification is correct - -**Debug steps:** -```bash -# 1. Verify MQTT messages -docker exec -it janus-mosquitto mosquitto_sub -t "sensors" -v - -# 2. Check query status -curl http://localhost:8080/api/queries/your_query_id - -# 3. Check server logs for errors -``` - -### WebSocket Connection Fails - -**Checklist:** -1. Query is registered: `GET /api/queries` -2. Query is started: `POST /api/queries/:id/start` -3. Browser allows WebSocket connections -4. Correct WebSocket URL: `ws://localhost:8080/api/queries/:id/results` - -**Test WebSocket with browser console:** -```javascript -const ws = new WebSocket('ws://localhost:8080/api/queries/your_query_id/results'); -ws.onopen = () => console.log('Connected'); -ws.onmessage = (e) => console.log('Message:', JSON.parse(e.data)); -ws.onerror = (e) => console.error('Error:', e); -``` - -### Server Won't Start - -**Port already in use:** -```bash -lsof -i :8080 -# Use different port -cargo run --bin http_server -- --port 8081 -``` - -**Build errors:** -```bash -# Clean and rebuild -cargo clean -cargo build --release -``` - -### No Results from Query - -**Historical queries:** -- Ensure data is in storage (run replay first) -- Check time window matches your data timestamps -- Verify N-Quads file is valid - -**Live queries:** -- Ensure MQTT broker is running -- Verify replay is publishing to MQTT -- Check query window specification - -## Performance Tuning - -### For High Throughput - -```bash -cargo run --bin http_server -- \ - --max-batch-size-bytes 52428800 \ - --flush-interval-ms 1000 -``` - -### For Low Latency - -```bash -cargo run --bin http_server -- \ - --max-batch-size-bytes 1048576 \ - --flush-interval-ms 100 -``` - -### MQTT Broker Tuning - -Edit `docker/mosquitto/config/mosquitto.conf`: -```conf -max_connections 1000 -max_queued_messages 10000 -message_size_limit 0 -``` - -Restart broker: -```bash -docker-compose restart mosquitto -``` - -## Production Deployment - -### Security Checklist - -- [ ] Add authentication to HTTP API -- [ ] Enable MQTT authentication -- [ ] Use HTTPS/WSS instead of HTTP/WS -- [ ] Restrict CORS to specific origins -- [ ] Add rate limiting -- [ ] Use firewall rules -- [ ] Enable SSL/TLS for MQTT - -### MQTT with Authentication - -Edit `docker/mosquitto/config/mosquitto.conf`: -```conf -allow_anonymous false -password_file /mosquitto/config/passwd -``` - -Create password file: -```bash -docker exec -it janus-mosquitto mosquitto_passwd -c /mosquitto/config/passwd username -docker-compose restart mosquitto -``` - -### Reverse Proxy (nginx) - -```nginx -server { - listen 80; - server_name janus.example.com; - - location / { - proxy_pass http://localhost:8080; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - } -} -``` - -## Next Steps - -1. Read the [HTTP API Documentation](HTTP_API.md) -2. Learn [JanusQL Query Language](JANUSQL.md) -3. Explore [Example Client](examples/http_client_example.rs) -4. Review [Architecture](ARCHITECTURE.md) -5. Check [Benchmark Results](BENCHMARK_RESULTS.md) - -## Support - -- GitHub Issues: https://github.com/SolidLabResearch/janus/issues -- Documentation: Complete API reference in `HTTP_API.md` - -## Summary - -You now have: -- ✅ MQTT broker running (Mosquitto) -- ✅ Janus HTTP server running -- ✅ Demo dashboard ready to use -- ✅ Sample data prepared -- ✅ Both historical and live processing capabilities - -**Ready to process RDF streams!** \ No newline at end of file diff --git a/docs/TEST_HISTORICAL.md b/docs/TEST_HISTORICAL.md deleted file mode 100644 index 7f922d7..0000000 --- a/docs/TEST_HISTORICAL.md +++ /dev/null @@ -1,130 +0,0 @@ -# Testing Historical Queries - Quick Guide - -## Your Data Has Timestamps Around 1-2 Million (Jan 1970) - -When you see "1/21/1970, 3:08:09 AM", that's timestamp ~1,800,000 milliseconds. - -## Fix 1: Use Matching Timestamp Range - -Your query needs: -```sparql -[START 1 END 10000000] -``` - -This covers 0 to ~3 hours (10 million milliseconds = ~2.7 hours) - -## Fix 2: For Historical Only, Use broker_type: "none" - -MQTT errors happen because the replay completes before MQTT client fully connects. - -For historical testing, use: -```json -{ - "broker_type": "none" // Just stores to disk, no MQTT -} -``` - -## Updated Dashboard - -The dashboard now uses: -- Timestamp range: `[START 1 END 10000000]` ✅ -- Broker type: `"none"` ✅ -- No looping (completes quickly) ✅ - -## Test Steps - -1. **Kill any existing server** -```bash -killall http_server 2>/dev/null -``` - -2. **Clear old storage** -```bash -rm -rf data/storage/* -``` - -3. **Start server** -```bash -cargo run --bin http_server -``` - -4. **Open dashboard** -```bash -open examples/demo_dashboard.html -``` - -5. **Click "Start Replay"** -- Should complete quickly -- No MQTT errors -- Data goes to storage - -6. **Wait 3 seconds** (for flush) - -7. **Click "Start Query"** -- Should get historical results! -- Check WebSocket panel for results - -## Expected Results - -You should see results like: -```json -{ - "query_id": "demo_query", - "timestamp": 1800000, - "source": "historical", - "bindings": [ - { - "sensor": "http://example.org/sensor1", - "temp": "23.5" - } - ] -} -``` - -## If Still No Results - -Check storage was created: -```bash -ls -lh data/storage/ -# Should see segment files -``` - -Check the query is using the right predicate: -```bash -# Data has: -<...sensor1> "23.5" ... - -# Query must use: -?sensor ex:temperature ?temp -# OR -?sensor ?temp -``` - -## For Live Processing (MQTT) - -If you want live processing later: -1. Ensure MQTT is running: `docker ps | grep mosquitto` -2. Use `"broker_type": "mqtt"` -3. Add `"loop_file": true` for continuous stream -4. Register LIVE query (not historical): - -```sparql -PREFIX ex: -REGISTER RStream ex:output AS -SELECT ?sensor ?temp -FROM NAMED WINDOW ex:liveWindow ON STREAM ex:sensorStream [RANGE 5000 STEP 1000] -WHERE { - WINDOW ex:liveWindow { - ?sensor ex:temperature ?temp . - } -} -``` - -## Current Setup (Historical Only) - -✅ broker_type: "none" - No MQTT needed -✅ Timestamp range: [START 1 END 10000000] - Matches your data -✅ Quick replay - No looping -✅ Should work immediately! - -Try it now! diff --git a/docs/TIMING_GUIDE.md b/docs/TIMING_GUIDE.md deleted file mode 100644 index 9b5359f..0000000 --- a/docs/TIMING_GUIDE.md +++ /dev/null @@ -1,220 +0,0 @@ -# Timing Guide: When to Start Query After Replay - -## TL;DR -**Wait 5-10 seconds** between "Start Replay" and "Start Query" - -## Why the Wait? - -### What Happens During Replay - -1. **Read file** (instant) -2. **Write to storage** (buffered in memory) -3. **Publish to MQTT** (if enabled) -4. **Background flush to disk** (happens asynchronously) - -The critical step is **#4 - Background Flush** - -### Storage Flush Timing - -From your server config: -``` ---flush-interval-ms 5000 (5 seconds) ---max-batch-size-bytes 10485760 (10 MB) -``` - -**Flush happens when EITHER:** -- 5 seconds elapsed (flush interval) -- OR batch reaches 10 MB -- OR max events reached - -For your small test file (6 lines), it will flush after **5 seconds**. - -## Recommended Timing - -### For Small Test Files (<100 events) -``` -Start Replay - ↓ -Wait 5-10 seconds ← Storage flush completes - ↓ -Start Query ← Historical data is ready -``` - -### For Large Files (>1000 events) -``` -Start Replay - ↓ -Wait 2-3 seconds ← First batch flushes (size-based) - ↓ -Start Query ← Some historical available, more coming -``` - -### For Continuous Streaming (loop_file: true) -``` -Start Replay - ↓ -Wait 5-10 seconds ← First batch flushed - ↓ -Start Query ← Gets initial historical, then live -``` - -## How to Know It's Ready - -### Check Server Logs -Look for messages like: -``` -Flushed batch: X events -Segment created: ... -``` - -### Check Storage Directory -```bash -ls -lh data/storage/ -# Should see files appear after ~5 seconds -``` - -### Check File Sizes -```bash -watch -n 1 'ls -lh data/storage/' -# Watch files appear/grow -``` - -## Current Dashboard Setup - -With your config: -- Small file: 6 lines -- Flush interval: 5 seconds -- No loop (completes quickly) - -**Optimal timing:** -``` -1. Click "Start Replay" -2. Count to 8 (or watch for "Replay completed") -3. Click "Start Query" -``` - -## Visual Timing Guide - -``` -Time (seconds) What's Happening -0 Click "Start Replay" -0.1 File read complete -0.2 All events in buffer -0.5 Publishing to MQTT (if enabled) -1.0 Replay loop iteration -... -5.0 ← FLUSH TRIGGERED (interval elapsed) -5.5 Segment file written to disk -6.0 ← SAFE TO START QUERY -``` - -## Why Historical Needs This Wait - -Historical queries read from **disk storage**, not memory buffer. - -``` -Memory Buffer → Background Thread → Disk Storage - (instant) (every 5 seconds) (queryable) -``` - -The query can't see data until it's flushed to disk! - -## Live Queries Don't Need Wait - -Live queries read from **MQTT**, not storage. - -``` -Replay → MQTT → Live Query - ↓ ↓ ↓ -Fast Fast Fast -``` - -**For live-only queries:** -``` -1. Start Query (subscribes to MQTT) -2. Start Replay (publishes to MQTT) -3. Results appear immediately -``` - -## Hybrid Queries (Historical + Live) - -Current dashboard setup needs wait for historical part: - -``` -1. Click "Start Replay" - ↓ -2. Wait 5-10 seconds (for historical flush) - ↓ -3. Click "Start Query" - ↓ - → Historical results (from disk) - → Then live results (from MQTT) -``` - -## Automatic Detection (Future Enhancement) - -Could add to dashboard: -```javascript -// Check if storage has data -async function isStorageReady() { - const status = await fetch('/api/replay/status'); - const data = await status.json(); - return data.elapsed_seconds > 6; -} - -// Enable "Start Query" button only when ready -setInterval(async () => { - if (await isStorageReady()) { - enableQueryButton(); - } -}, 1000); -``` - -But for now, manual 5-10 second wait works fine. - -## Quick Reference - -| Scenario | Wait Time | Reason | -|----------|-----------|--------| -| Small file + historical | 5-10 sec | Flush interval | -| Large file + historical | 2-3 sec | Size-based flush | -| Live only | 0 sec | Reads from MQTT | -| Hybrid | 5-10 sec | Historical needs flush | -| Empty/test | 5 sec | Minimum flush interval | - -## Your Current Setup - -File: `data/sensors_correct.nq` (6 lines = ~500 bytes) -Config: Flush every 5 seconds OR 10 MB - -**Recommended:** -``` -Start Replay → Count to 8 → Start Query -``` - -This ensures: -- ✅ File read complete -- ✅ Buffer filled -- ✅ Background flush triggered -- ✅ Segment written to disk -- ✅ Historical data queryable -- ✅ MQTT streaming active - -## Test Script - -```bash -#!/bin/bash -echo "Starting replay..." -# Click "Start Replay" in dashboard - -echo "Waiting for storage flush..." -for i in {8..1}; do - echo "$i..." - sleep 1 -done - -echo "Storage should be ready!" -echo "Click 'Start Query' now" -``` - -**Bottom line: Wait 8-10 seconds to be safe!** ⏱️ diff --git a/janus-dashboard/.gitignore b/janus-dashboard/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/janus-dashboard/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/janus-dashboard/README.md b/janus-dashboard/README.md deleted file mode 100644 index e6cd94f..0000000 --- a/janus-dashboard/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Svelte + TS + Vite - -This template should help get you started developing with Svelte and TypeScript in Vite. - -## Recommended IDE Setup - -[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). - -## Need an official Svelte framework? - -Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. - -## Technical considerations - -**Why use this over SvelteKit?** - -- It brings its own routing solution which might not be preferable for some users. -- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. - -This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. - -Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. - -**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** - -Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. - -**Why include `.vscode/extensions.json`?** - -Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. - -**Why enable `allowJs` in the TS template?** - -While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. - -**Why is HMR not preserving my local component state?** - -HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). - -If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. - -```ts -// store.ts -// An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) -``` diff --git a/janus-dashboard/index.html b/janus-dashboard/index.html deleted file mode 100644 index 9b86771..0000000 --- a/janus-dashboard/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - janus-dashboard - - -
- - - diff --git a/janus-dashboard/package-lock.json b/janus-dashboard/package-lock.json deleted file mode 100644 index 04cb70f..0000000 --- a/janus-dashboard/package-lock.json +++ /dev/null @@ -1,1490 +0,0 @@ -{ - "name": "janus-dashboard", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "janus-dashboard", - "version": "0.0.0", - "dependencies": { - "echarts": "^6.0.0" - }, - "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.2.1", - "@tsconfig/svelte": "^5.0.6", - "@types/node": "^24.10.1", - "svelte": "^5.43.8", - "svelte-check": "^4.3.4", - "typescript": "~5.9.3", - "vite": "^7.2.4" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.7.tgz", - "integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", - "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "debug": "^4.4.1", - "deepmerge": "^4.3.1", - "magic-string": "^0.30.17", - "vitefu": "^1.1.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", - "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@tsconfig/svelte": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.6.tgz", - "integrity": "sha512-yGxYL0I9eETH1/DR9qVJey4DAsCdeau4a9wYPKuXfEhm8lFO8wg+LLYJjIpAm6Fw7HSlhepPhYPDop75485yWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/devalue": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", - "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/echarts": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", - "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "6.0.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.0.tgz", - "integrity": "sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", - "fsevents": "~2.3.2" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svelte": { - "version": "5.45.2", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.2.tgz", - "integrity": "sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "devalue": "^5.5.0", - "esm-env": "^1.2.1", - "esrap": "^2.2.0", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-check": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.4.tgz", - "integrity": "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": ">=5.0.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", - "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dev": true, - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/zrender": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", - "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" - } - } - } -} diff --git a/janus-dashboard/package.json b/janus-dashboard/package.json deleted file mode 100644 index 50224b9..0000000 --- a/janus-dashboard/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "janus-dashboard", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" - }, - "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.2.1", - "@tsconfig/svelte": "^5.0.6", - "@types/node": "^24.10.1", - "svelte": "^5.43.8", - "svelte-check": "^4.3.4", - "typescript": "~5.9.3", - "vite": "^7.2.4" - }, - "dependencies": { - "echarts": "^6.0.0" - } -} diff --git a/janus-dashboard/public/vite.svg b/janus-dashboard/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/janus-dashboard/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/janus-dashboard/src/App.svelte b/janus-dashboard/src/App.svelte deleted file mode 100644 index b1b7e39..0000000 --- a/janus-dashboard/src/App.svelte +++ /dev/null @@ -1,336 +0,0 @@ - - -
- - -
- -
-
- - diff --git a/janus-dashboard/src/app.css b/janus-dashboard/src/app.css deleted file mode 100644 index 76cda08..0000000 --- a/janus-dashboard/src/app.css +++ /dev/null @@ -1,79 +0,0 @@ -:root { - font-family: "Inter", system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light; - color: #333333; - background-color: #ffffff; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -.card { - padding: 2em; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/janus-dashboard/src/assets/svelte.svg b/janus-dashboard/src/assets/svelte.svg deleted file mode 100644 index c5e0848..0000000 --- a/janus-dashboard/src/assets/svelte.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/janus-dashboard/src/lib/Query.svelte b/janus-dashboard/src/lib/Query.svelte deleted file mode 100644 index 74313d3..0000000 --- a/janus-dashboard/src/lib/Query.svelte +++ /dev/null @@ -1,56 +0,0 @@ - - -
- - -
- - diff --git a/janus-dashboard/src/lib/StreamChart.svelte b/janus-dashboard/src/lib/StreamChart.svelte deleted file mode 100644 index 3cc741f..0000000 --- a/janus-dashboard/src/lib/StreamChart.svelte +++ /dev/null @@ -1,309 +0,0 @@ - - -
- - diff --git a/janus-dashboard/src/main.ts b/janus-dashboard/src/main.ts deleted file mode 100644 index 664a057..0000000 --- a/janus-dashboard/src/main.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { mount } from 'svelte' -import './app.css' -import App from './App.svelte' - -const app = mount(App, { - target: document.getElementById('app')!, -}) - -export default app diff --git a/janus-dashboard/svelte.config.js b/janus-dashboard/svelte.config.js deleted file mode 100644 index 96b3455..0000000 --- a/janus-dashboard/svelte.config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' - -/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ -export default { - // Consult https://svelte.dev/docs#compile-time-svelte-preprocess - // for more information about preprocessors - preprocess: vitePreprocess(), -} diff --git a/janus-dashboard/tsconfig.app.json b/janus-dashboard/tsconfig.app.json deleted file mode 100644 index 31c18cf..0000000 --- a/janus-dashboard/tsconfig.app.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "@tsconfig/svelte/tsconfig.json", - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2022", - "useDefineForClassFields": true, - "module": "ESNext", - "types": ["svelte", "vite/client"], - "noEmit": true, - /** - * Typecheck JS in `.svelte` and `.js` files by default. - * Disable checkJs if you'd like to use dynamic types in JS. - * Note that setting allowJs false does not prevent the use - * of JS in `.svelte` files. - */ - "allowJs": true, - "checkJs": true, - "moduleDetection": "force" - }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] -} diff --git a/janus-dashboard/tsconfig.json b/janus-dashboard/tsconfig.json deleted file mode 100644 index 1ffef60..0000000 --- a/janus-dashboard/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -} diff --git a/janus-dashboard/tsconfig.node.json b/janus-dashboard/tsconfig.node.json deleted file mode 100644 index 8a67f62..0000000 --- a/janus-dashboard/tsconfig.node.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", - "lib": ["ES2023"], - "module": "ESNext", - "types": ["node"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/janus-dashboard/vite.config.ts b/janus-dashboard/vite.config.ts deleted file mode 100644 index d32eba1..0000000 --- a/janus-dashboard/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import { svelte } from '@sveltejs/vite-plugin-svelte' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [svelte()], -}) diff --git a/src/api/janus_api.rs b/src/api/janus_api.rs index aaebd25..86ae114 100644 --- a/src/api/janus_api.rs +++ b/src/api/janus_api.rs @@ -99,7 +99,7 @@ struct RunningQuery { historical_handles: Vec>, baseline_handle: Option>, live_handle: Option>, - mqtt_subscriber_handle: Option>, + mqtt_subscriber_handles: Vec>, // shutdown sender signals used to stop the workers shutdown_senders: Vec>, // MQTT subscriber instances (for stopping) @@ -224,13 +224,13 @@ impl JanusApi { parsed.baseline.as_ref().map(|baseline| baseline.window_name.clone()); let mut historical_handles = Vec::new(); let mut shutdown_senders = Vec::new(); - let status = Arc::new(RwLock::new( + let initial_status = if !parsed.live_windows.is_empty() && !parsed.historical_windows.is_empty() { ExecutionStatus::WarmingBaseline } else { ExecutionStatus::Running - }, - )); + }; + let status = Arc::new(RwLock::new(initial_status.clone())); // 4. Spawn historical worker threads (one per historical window) for (i, window) in parsed.historical_windows.iter().enumerate() { @@ -308,7 +308,7 @@ impl JanusApi { // 5. Spawn live worker thread and MQTT subscribers (if there are live windows) let mut mqtt_subscribers = Vec::new(); - let mut mqtt_subscriber_handle = None; + let mut mqtt_subscriber_handles = Vec::new(); let mut baseline_handle = None; let live_handle = if !parsed.live_windows.is_empty() && !parsed.rspql_query.is_empty() { @@ -354,6 +354,8 @@ impl JanusApi { let parsed_clone = parsed.clone(); let processor_for_baseline = Arc::clone(&live_processor); let status_for_baseline = Arc::clone(&status); + let registry_for_baseline = Arc::clone(&self.registry); + let query_id_for_baseline = query_id.clone(); let baseline_mode = effective_baseline_mode; let baseline_window = effective_baseline_window.clone(); let (baseline_shutdown_tx, baseline_shutdown_rx) = mpsc::channel::<()>(); @@ -390,12 +392,16 @@ impl JanusApi { *state = ExecutionStatus::Running; } } + let _ = registry_for_baseline + .set_status(&query_id_for_baseline, "Running"); } Err(err) => { eprintln!("Async baseline warm-up error: {}", err); if let Ok(mut state) = status_for_baseline.write() { *state = ExecutionStatus::Failed(err.to_string()); } + let _ = registry_for_baseline + .set_status(&query_id_for_baseline, format!("Failed({err})")); } } })); @@ -431,7 +437,7 @@ impl JanusApi { }); mqtt_subscribers.push(subscriber); - mqtt_subscriber_handle = Some(sub_handle); + mqtt_subscriber_handles.push(sub_handle); } // Spawn live worker thread to receive results @@ -470,6 +476,21 @@ impl JanusApi { None }; + self.registry.increment_execution_count(query_id).map_err(|e| { + JanusApiError::RegistryError(format!( + "Failed to increment execution count for '{}': {}", + query_id, e + )) + })?; + self.registry + .set_status(query_id, format!("{:?}", initial_status)) + .map_err(|e| { + JanusApiError::RegistryError(format!( + "Failed to update query status for '{}': {}", + query_id, e + )) + })?; + // 6. Store running query information let running = RunningQuery { metadata, @@ -479,7 +500,7 @@ impl JanusApi { historical_handles, baseline_handle, live_handle, - mqtt_subscriber_handle, + mqtt_subscriber_handles, shutdown_senders, mqtt_subscribers, }; @@ -506,6 +527,7 @@ impl JanusApi { let running = running_map.remove(query_id).ok_or_else(|| { JanusApiError::ExecutionError(format!("Query '{}' is not running", query_id)) })?; + drop(running_map); // Send shutdown signals for shutdown_tx in running.shutdown_senders { @@ -521,6 +543,25 @@ impl JanusApi { if let Ok(mut status) = running.status.write() { *status = ExecutionStatus::Stopped; } + self.registry.set_status(query_id, "Stopped").map_err(|e| { + JanusApiError::RegistryError(format!( + "Failed to update query status for '{}': {}", + query_id, e + )) + })?; + + for handle in running.historical_handles { + let _ = handle.join(); + } + if let Some(handle) = running.baseline_handle { + let _ = handle.join(); + } + if let Some(handle) = running.live_handle { + let _ = handle.join(); + } + for handle in running.mqtt_subscriber_handles { + let _ = handle.join(); + } Ok(()) } diff --git a/src/execution/historical_executor.rs b/src/execution/historical_executor.rs index f207028..768e206 100644 --- a/src/execution/historical_executor.rs +++ b/src/execution/historical_executor.rs @@ -95,21 +95,6 @@ impl HistoricalExecutor { self.execute_sparql_on_events(&events, sparql_query) } - /// Execute a sliding window query that returns an iterator of results (bypassing operator). - /// - /// Note: This is a simplified implementation that queries storage directly. - /// For production use, consider implementing proper window sliding logic. - #[allow(dead_code)] - fn execute_fixed_window_with_operator( - &self, - window: &WindowDefinition, - sparql_query: &str, - ) -> Result>, JanusApiError> { - // Original operator-based implementation kept for reference - // Note: Requires Arc->Rc conversion which is currently problematic - unimplemented!("Operator-based execution requires refactoring window operators to use Arc") - } - /// Execute a sliding window query that returns an iterator of results. /// /// # Arguments diff --git a/src/http/server.rs b/src/http/server.rs index 8c1fead..babc977 100644 --- a/src/http/server.rs +++ b/src/http/server.rs @@ -296,15 +296,6 @@ async fn get_query( .ok_or_else(|| ApiError::NotFound(format!("Query '{}' not found", query_id)))?; let is_running = state.janus_api.is_running(&query_id); - let status = if is_running { - state - .janus_api - .get_query_status(&query_id) - .map(|s| format!("{:?}", s)) - .unwrap_or_else(|| "Unknown".to_string()) - } else { - "Registered".to_string() - }; Ok(Json(QueryDetailsResponse { query_id: metadata.query_id, @@ -313,7 +304,7 @@ async fn get_query( registered_at: metadata.registered_at, execution_count: metadata.execution_count, is_running, - status, + status: metadata.status, })) } diff --git a/src/querying/kolibrie_adapter.rs b/src/querying/kolibrie_adapter.rs deleted file mode 100644 index 470c8e4..0000000 --- a/src/querying/kolibrie_adapter.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::querying::query_processing::SparqlEngine; -use rsp_rs::QuadContainer; -use std::fmt; - -#[derive(Debug)] -pub struct KolibrieError; - -impl fmt::Display for KolibrieError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Kolibrie error") - } -} - -impl std::error::Error for KolibrieError {} - -pub struct KolibrieAdapter {} - -impl KolibrieAdapter { - pub fn new() -> Self { - KolibrieAdapter {} - } -} - -impl SparqlEngine for KolibrieAdapter { - type EngineError = KolibrieError; - - fn execute_query( - &self, - query: &str, - container: &QuadContainer, - ) -> Result, Self::EngineError> { - // Here you would implement the actual query execution using Kolibrie - // For now, we'll log the container size and return an empty result set - - #[cfg(debug_assertions)] - { - println!("Executing query on Kolibrie adapter with {} quads", container.len()); - println!("Query: {}", query); - } - - // TODO: Implement actual Kolibrie query execution - Ok(vec![]) - } -} diff --git a/src/querying/main.rs b/src/querying/main.rs deleted file mode 100644 index 0c0d377..0000000 --- a/src/querying/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::querying::kolibrie_adapter::KolibrieAdapter; -use crate::querying::oxigraph_adapter::OxigraphAdapter; -use crate::querying::query_processing::QueryProcessor; -use oxigraph::model::{GraphName, Literal, NamedNode, Quad}; -use rsp_rs::QuadContainer; -use std::collections::HashSet; - -#[allow(dead_code)] -fn main() { - let query = "SELECT ?s WHERE { ?s ?p ?o }"; - - // Create sample quads - let mut quads = HashSet::new(); - - // Add sample quad to the set - // Example: "Object1" in default graph - let subject = NamedNode::new("http://example.org/subject1").unwrap(); - let predicate = NamedNode::new("http://example.org/predicate1").unwrap(); - let object = Literal::new_simple_literal("Object1"); - let quad = Quad::new(subject, predicate, object, oxigraph::model::GraphName::DefaultGraph); - quads.insert(quad); - - // Create a QuadContainer with the quads and a timestamp - let timestamp = 1000; // milliseconds since epoch - let container = QuadContainer::new(quads, timestamp); - - let oxigraph_adapter = OxigraphAdapter::new(); - let kolibrie_adapter = KolibrieAdapter::new(); - - let query_processor_oxigraph = QueryProcessor::new(oxigraph_adapter); - let query_processor_kolibrie = QueryProcessor::new(kolibrie_adapter); - - // Pass the container to the query processor - match query_processor_oxigraph.process_query(query, &container) { - Ok(results) => println!("Oxigraph results: {:?}", results), - Err(e) => eprintln!("Oxigraph error: {}", e), - } - - match query_processor_kolibrie.process_query(query, &container) { - Ok(results) => println!("Kolibrie results: {:?}", results), - Err(e) => eprintln!("Kolibrie error: {}", e), - } -} diff --git a/src/querying/mod.rs b/src/querying/mod.rs index f3de775..9c10bc1 100644 --- a/src/querying/mod.rs +++ b/src/querying/mod.rs @@ -1,4 +1,2 @@ -pub mod kolibrie_adapter; -pub mod main; pub mod oxigraph_adapter; pub mod query_processing; diff --git a/src/registry/query_registry.rs b/src/registry/query_registry.rs index 3f0d716..1837410 100644 --- a/src/registry/query_registry.rs +++ b/src/registry/query_registry.rs @@ -15,6 +15,7 @@ pub struct QueryMetadata { pub baseline_mode: BaselineBootstrapMode, pub registered_at: u64, pub execution_count: u64, + pub status: String, pub subscribers: Vec, } @@ -102,6 +103,7 @@ impl QueryRegistry { baseline_mode, registered_at: Self::current_timestamp(), execution_count: 0, + status: "Registered".to_string(), subscribers: Vec::new(), }; @@ -145,6 +147,20 @@ impl QueryRegistry { Ok(()) } + pub fn set_status( + &self, + query_id: &QueryId, + status: impl Into, + ) -> Result<(), QueryRegistryError> { + let mut queries = self.queries.write().unwrap(); + let query = queries + .get_mut(query_id) + .ok_or_else(|| QueryRegistryError::QueryNotFound(query_id.clone()))?; + + query.status = status.into(); + Ok(()) + } + /// To remove a query from the registry pub fn unregister(&self, query_id: &QueryId) -> Result { let mut queries = self.queries.write().unwrap(); diff --git a/src/sources/mod.rs b/src/sources/mod.rs index 0d33144..6bf9526 100644 --- a/src/sources/mod.rs +++ b/src/sources/mod.rs @@ -1,3 +1,2 @@ pub mod mqtt_adapter; -pub mod stream_ingestion_pipeline; pub mod stream_source; diff --git a/src/sources/stream_ingestion_pipeline.rs b/src/sources/stream_ingestion_pipeline.rs deleted file mode 100644 index 645444b..0000000 --- a/src/sources/stream_ingestion_pipeline.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::core::RDFEvent; -use crate::sources::stream_source::StreamSource; -use crate::storage::segmented_storage::StreamingSegmentedStorage; -use std::sync::Arc; - -pub struct StreamIngestionPipeline { - storage: Arc, - sources: Vec>, -} - -impl StreamIngestionPipeline { - pub fn new(storage: Arc) -> Self { - StreamIngestionPipeline { storage, sources: Vec::new() } - } - - /// Adding a source for the stream ingestion pipeline (for example MQTT). - pub fn add_source(&mut self, source: Box) { - self.sources.push(source); - } - - /// Start the stream ingestion pipeline by subscribing to the sources and ingesting data - /// into storage as well as the live stream processing RSP Engine. - pub fn start(&self, topics: Vec) -> Result<(), Box> { - let storage = Arc::clone(&self.storage); - - // Shared callback writes to the storage (handles both storage and live processing) - let callback: Arc = Arc::new(move |event: RDFEvent| { - // Storage will handle the background flushing. - // TODO: Add live stream processing here as a process. - if let Err(e) = storage.write_rdf_event(event) { - eprintln!("Error writing to storage: {:?}", e); - } - }); - - for source in &self.sources { - source.subscribe(topics.clone(), Arc::clone(&callback))?; - } - - Ok(()) - } -} diff --git a/tests/http_server_integration_test.rs b/tests/http_server_integration_test.rs index c0fbb8a..e27ea30 100644 --- a/tests/http_server_integration_test.rs +++ b/tests/http_server_integration_test.rs @@ -226,7 +226,8 @@ async fn test_stop_route_stops_running_query_and_delete_requires_stop() { assert!(get_response.status().is_success()); let get_body: Value = get_response.json().await.expect("invalid get response"); assert_eq!(get_body["is_running"], false); - assert_eq!(get_body["status"], "Registered"); + assert_eq!(get_body["status"], "Stopped"); + assert_eq!(get_body["execution_count"], 1); let delete_response = server .client diff --git a/tests/janus_api_integration_test.rs b/tests/janus_api_integration_test.rs index 1a52003..ad6db96 100644 --- a/tests/janus_api_integration_test.rs +++ b/tests/janus_api_integration_test.rs @@ -355,6 +355,53 @@ fn test_stop_query() { ); } +#[test] +fn test_execution_count_and_status_update_across_lifecycle() { + let storage = Arc::new( + StreamingSegmentedStorage::new(StreamingConfig::default()) + .expect("Failed to create storage"), + ); + let parser = JanusQLParser::new().expect("Failed to create parser"); + let registry = Arc::new(QueryRegistry::new()); + + let api = JanusApi::new(parser, Arc::clone(®istry), storage).expect("Failed to create API"); + + let janusql = r#" + PREFIX ex: + SELECT ?s + FROM NAMED WINDOW ex:w ON STREAM ex:stream1 [RANGE 1000 STEP 200] + WHERE { WINDOW ex:w { ?s ?p ?o } } + "#; + + let metadata = api + .register_query("lifecycle_query".into(), janusql) + .expect("Failed to register query"); + assert_eq!(metadata.execution_count, 0); + assert_eq!(metadata.status, "Registered"); + + let _handle = api.start_query(&"lifecycle_query".into()).expect("Failed to start query"); + + let after_start = + registry.get(&"lifecycle_query".into()).expect("query should exist after start"); + assert_eq!(after_start.execution_count, 1); + assert_eq!(after_start.status, "Running"); + + api.stop_query(&"lifecycle_query".into()).expect("Failed to stop query"); + + let after_stop = + registry.get(&"lifecycle_query".into()).expect("query should exist after stop"); + assert_eq!(after_stop.execution_count, 1); + assert_eq!(after_stop.status, "Stopped"); + + let _handle = api.start_query(&"lifecycle_query".into()).expect("Failed to restart query"); + + let after_restart = registry + .get(&"lifecycle_query".into()) + .expect("query should exist after restart"); + assert_eq!(after_restart.execution_count, 2); + assert_eq!(after_restart.status, "Running"); +} + #[test] fn test_multiple_queries_concurrent() { let storage = create_test_storage_with_data().expect("Failed to create storage");