gcharanteja commited on
Commit ·
419fce7
1
Parent(s): 3d4271b
Enhance README and API functionality with new endpoints and error handling; add package.json and package-lock.json for Node.js testing
Browse files- .gitignore +3 -0
- README.md +156 -10
- app.py +71 -15
- package-lock.json +106 -0
- package.json +14 -0
- test.js +226 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/venv
|
| 2 |
+
|
| 3 |
+
/node_modules
|
README.md
CHANGED
|
@@ -1,10 +1,156 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SQLite Server on HF Spaces
|
| 2 |
+
|
| 3 |
+
A persistent SQLite database server running on Hugging Face Spaces with FastAPI wrapper.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
✅ **Persistent Storage** - Data stored in `/data/sqlite.db` (survives restarts)
|
| 8 |
+
✅ **REST API** - Full SQLite CRUD operations via HTTP
|
| 9 |
+
✅ **Dual Services** - SQLite server (port 8000) + FastAPI wrapper (port 7860)
|
| 10 |
+
✅ **Schema Management** - Query schema and list tables
|
| 11 |
+
✅ **HF Spaces Ready** - Docker setup for Hugging Face deployment
|
| 12 |
+
|
| 13 |
+
## Architecture
|
| 14 |
+
|
| 15 |
+
```
|
| 16 |
+
┌─────────────────────────────────────────────┐
|
| 17 |
+
│ HF Spaces Container │
|
| 18 |
+
├─────────────────────────────────────────────┤
|
| 19 |
+
│ Port 7860 (exposed) │
|
| 20 |
+
│ ├── FastAPI Wrapper (app.py) │
|
| 21 |
+
│ │ └── Proxies to SQLite Server │
|
| 22 |
+
│ │ │
|
| 23 |
+
│ Port 8000 (internal) │
|
| 24 |
+
│ └── SQLite Server (sqlite_server.py) │
|
| 25 |
+
│ └── /data/sqlite.db (persistent) │
|
| 26 |
+
└─────────────────────────────────────────────┘
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
## API Endpoints
|
| 30 |
+
|
| 31 |
+
### Health Check
|
| 32 |
+
```bash
|
| 33 |
+
GET /health
|
| 34 |
+
GET /api/v1/heartbeat
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Query Data (SELECT)
|
| 38 |
+
```bash
|
| 39 |
+
POST /api/v1/query
|
| 40 |
+
Content-Type: application/json
|
| 41 |
+
|
| 42 |
+
{
|
| 43 |
+
"sql": "SELECT * FROM users"
|
| 44 |
+
}
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
### Execute (INSERT/UPDATE/DELETE)
|
| 48 |
+
```bash
|
| 49 |
+
POST /api/v1/execute
|
| 50 |
+
Content-Type: application/json
|
| 51 |
+
|
| 52 |
+
{
|
| 53 |
+
"sql": "INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')"
|
| 54 |
+
}
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### Schema & Tables
|
| 58 |
+
```bash
|
| 59 |
+
GET /api/v1/tables
|
| 60 |
+
GET /api/v1/schema
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
## Deployment to HF Spaces
|
| 64 |
+
|
| 65 |
+
1. **Create a new Space:**
|
| 66 |
+
- Go to https://huggingface.co/new-space
|
| 67 |
+
- Select "Docker" as SDK
|
| 68 |
+
- Upload/push this repo
|
| 69 |
+
|
| 70 |
+
2. **Files needed:**
|
| 71 |
+
- `Dockerfile` - Container setup
|
| 72 |
+
- `start.sh` - Startup script
|
| 73 |
+
- `app.py` - FastAPI wrapper
|
| 74 |
+
- `sqlite_server.py` - SQLite API server
|
| 75 |
+
- `requirements.txt` - Python dependencies
|
| 76 |
+
|
| 77 |
+
3. **Persistent Storage:**
|
| 78 |
+
- HF Spaces automatically mounts `/data` as persistent volume
|
| 79 |
+
- SQLite database is stored at `/data/sqlite.db`
|
| 80 |
+
- Data persists across Space restarts
|
| 81 |
+
|
| 82 |
+
## Local Testing
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
# Install dependencies
|
| 86 |
+
npm install
|
| 87 |
+
pip install -r requirements.txt
|
| 88 |
+
|
| 89 |
+
# Start the servers
|
| 90 |
+
source venv/bin/activate
|
| 91 |
+
python3 sqlite_server.py & # Port 8000
|
| 92 |
+
python3 app.py & # Port 7860
|
| 93 |
+
|
| 94 |
+
# Run tests
|
| 95 |
+
npm run test
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
## File Structure
|
| 99 |
+
|
| 100 |
+
```
|
| 101 |
+
.
|
| 102 |
+
├── app.py # FastAPI wrapper (port 7860)
|
| 103 |
+
├── sqlite_server.py # SQLite API server (port 8000)
|
| 104 |
+
├── start.sh # Startup script
|
| 105 |
+
├── Dockerfile # Container configuration
|
| 106 |
+
├── requirements.txt # Python dependencies
|
| 107 |
+
├── package.json # Node.js test config
|
| 108 |
+
├── test.js # Test suite
|
| 109 |
+
└── README.md # This file
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
## Example Usage
|
| 113 |
+
|
| 114 |
+
### Create a table
|
| 115 |
+
```bash
|
| 116 |
+
curl -X POST https://your-space.hf.space/api/v1/execute \
|
| 117 |
+
-H "Content-Type: application/json" \
|
| 118 |
+
-d '{
|
| 119 |
+
"sql": "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)"
|
| 120 |
+
}'
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
### Insert data
|
| 124 |
+
```bash
|
| 125 |
+
curl -X POST https://your-space.hf.space/api/v1/execute \
|
| 126 |
+
-H "Content-Type: application/json" \
|
| 127 |
+
-d '{
|
| 128 |
+
"sql": "INSERT INTO users (name, email) VALUES ('"'"'Alice'"'"', '"'"'alice@example.com'"'"')"
|
| 129 |
+
}'
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### Query data
|
| 133 |
+
```bash
|
| 134 |
+
curl -X POST https://your-space.hf.space/api/v1/query \
|
| 135 |
+
-H "Content-Type: application/json" \
|
| 136 |
+
-d '{"sql": "SELECT * FROM users"}'
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
## Troubleshooting
|
| 140 |
+
|
| 141 |
+
**Health check fails:**
|
| 142 |
+
- Ensure SQLite server started before FastAPI wrapper
|
| 143 |
+
- Check `start.sh` sleep duration (currently 2 seconds)
|
| 144 |
+
|
| 145 |
+
**Port conflicts:**
|
| 146 |
+
- SQLite server: port 8000 (internal)
|
| 147 |
+
- FastAPI wrapper: port 7860 (exposed on HF Spaces)
|
| 148 |
+
|
| 149 |
+
**Data not persisting:**
|
| 150 |
+
- Verify `/data` directory exists
|
| 151 |
+
- Check HF Spaces has persistent storage enabled
|
| 152 |
+
- Database file should be at `/data/sqlite.db`
|
| 153 |
+
|
| 154 |
+
## Credits
|
| 155 |
+
|
| 156 |
+
Pattern based on ChromaDB HF Spaces deployment.
|
app.py
CHANGED
|
@@ -1,12 +1,19 @@
|
|
| 1 |
import os
|
| 2 |
import requests
|
| 3 |
-
from fastapi import FastAPI
|
| 4 |
-
from fastapi.responses import HTMLResponse
|
|
|
|
| 5 |
|
| 6 |
app = FastAPI(title="SQLite on HF Space")
|
| 7 |
|
| 8 |
SQLITE_URL = "http://localhost:8000"
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
@app.get("/", response_class=HTMLResponse)
|
| 11 |
def read_root():
|
| 12 |
return """
|
|
@@ -14,32 +21,81 @@ def read_root():
|
|
| 14 |
<head><title>SQLite HF Space</title></head>
|
| 15 |
<body>
|
| 16 |
<h1>✅ SQLite Server is Running!</h1>
|
| 17 |
-
<p>Persistent Storage: /data/sqlite.db</p>
|
| 18 |
-
<p>API
|
| 19 |
-
<
|
| 20 |
-
<
|
| 21 |
<ul>
|
| 22 |
-
<li>GET /api/v1/heartbeat - Health check</li>
|
| 23 |
-
<li>POST /api/v1/query - Execute SELECT queries</li>
|
| 24 |
-
<li>POST /api/v1/execute - Execute INSERT/UPDATE/DELETE</li>
|
| 25 |
-
<li>GET /api/v1/tables - List all tables</li>
|
| 26 |
-
<li>GET /api/v1/schema - Get database schema</li>
|
| 27 |
</ul>
|
| 28 |
</body>
|
| 29 |
</html>
|
| 30 |
-
"""
|
| 31 |
|
| 32 |
@app.get("/health")
|
| 33 |
def health_check():
|
| 34 |
try:
|
| 35 |
-
response = requests.get(f"{SQLITE_URL}/api/v1/heartbeat")
|
| 36 |
if response.status_code == 200:
|
| 37 |
return {"status": "healthy", "sqlite": "up"}
|
| 38 |
else:
|
| 39 |
-
return {"status": "unhealthy", "
|
| 40 |
except Exception as e:
|
| 41 |
return {"status": "error", "detail": str(e)}
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
if __name__ == "__main__":
|
| 44 |
import uvicorn
|
| 45 |
-
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import requests
|
| 3 |
+
from fastapi import FastAPI, HTTPException, Request
|
| 4 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
|
| 7 |
app = FastAPI(title="SQLite on HF Space")
|
| 8 |
|
| 9 |
SQLITE_URL = "http://localhost:8000"
|
| 10 |
|
| 11 |
+
class QueryRequest(BaseModel):
|
| 12 |
+
sql: str
|
| 13 |
+
|
| 14 |
+
class ExecuteRequest(BaseModel):
|
| 15 |
+
sql: str
|
| 16 |
+
|
| 17 |
@app.get("/", response_class=HTMLResponse)
|
| 18 |
def read_root():
|
| 19 |
return """
|
|
|
|
| 21 |
<head><title>SQLite HF Space</title></head>
|
| 22 |
<body>
|
| 23 |
<h1>✅ SQLite Server is Running!</h1>
|
| 24 |
+
<p><strong>Persistent Storage:</strong> /data/sqlite.db</p>
|
| 25 |
+
<p><strong>API Documentation:</strong> <a href="/docs">Swagger UI</a></p>
|
| 26 |
+
<hr>
|
| 27 |
+
<h3>Available Endpoints:</h3>
|
| 28 |
<ul>
|
| 29 |
+
<li><code>GET /api/v1/heartbeat</code> - Health check</li>
|
| 30 |
+
<li><code>POST /api/v1/query</code> - Execute SELECT queries</li>
|
| 31 |
+
<li><code>POST /api/v1/execute</code> - Execute INSERT/UPDATE/DELETE</li>
|
| 32 |
+
<li><code>GET /api/v1/tables</code> - List all tables</li>
|
| 33 |
+
<li><code>GET /api/v1/schema</code> - Get database schema</li>
|
| 34 |
</ul>
|
| 35 |
</body>
|
| 36 |
</html>
|
| 37 |
+
"""
|
| 38 |
|
| 39 |
@app.get("/health")
|
| 40 |
def health_check():
|
| 41 |
try:
|
| 42 |
+
response = requests.get(f"{SQLITE_URL}/api/v1/heartbeat", timeout=5)
|
| 43 |
if response.status_code == 200:
|
| 44 |
return {"status": "healthy", "sqlite": "up"}
|
| 45 |
else:
|
| 46 |
+
return {"status": "unhealthy", "detail": response.text}
|
| 47 |
except Exception as e:
|
| 48 |
return {"status": "error", "detail": str(e)}
|
| 49 |
|
| 50 |
+
@app.get("/api/v1/heartbeat")
|
| 51 |
+
def heartbeat():
|
| 52 |
+
try:
|
| 53 |
+
response = requests.get(f"{SQLITE_URL}/api/v1/heartbeat", timeout=5)
|
| 54 |
+
return response.json()
|
| 55 |
+
except Exception as e:
|
| 56 |
+
raise HTTPException(status_code=500, detail=f"SQLite server error: {str(e)}")
|
| 57 |
+
|
| 58 |
+
@app.post("/api/v1/query")
|
| 59 |
+
def query(request: QueryRequest):
|
| 60 |
+
try:
|
| 61 |
+
response = requests.post(f"{SQLITE_URL}/api/v1/query", json={"sql": request.sql}, timeout=10)
|
| 62 |
+
if response.status_code != 200:
|
| 63 |
+
raise HTTPException(status_code=response.status_code, detail=response.text)
|
| 64 |
+
return response.json()
|
| 65 |
+
except Exception as e:
|
| 66 |
+
raise HTTPException(status_code=500, detail=f"Query error: {str(e)}")
|
| 67 |
+
|
| 68 |
+
@app.post("/api/v1/execute")
|
| 69 |
+
def execute(request: ExecuteRequest):
|
| 70 |
+
try:
|
| 71 |
+
response = requests.post(f"{SQLITE_URL}/api/v1/execute", json={"sql": request.sql}, timeout=10)
|
| 72 |
+
if response.status_code != 200:
|
| 73 |
+
raise HTTPException(status_code=response.status_code, detail=response.text)
|
| 74 |
+
return response.json()
|
| 75 |
+
except Exception as e:
|
| 76 |
+
raise HTTPException(status_code=500, detail=f"Execute error: {str(e)}")
|
| 77 |
+
|
| 78 |
+
@app.get("/api/v1/tables")
|
| 79 |
+
def list_tables():
|
| 80 |
+
try:
|
| 81 |
+
response = requests.get(f"{SQLITE_URL}/api/v1/tables", timeout=5)
|
| 82 |
+
if response.status_code != 200:
|
| 83 |
+
raise HTTPException(status_code=response.status_code, detail=response.text)
|
| 84 |
+
return response.json()
|
| 85 |
+
except Exception as e:
|
| 86 |
+
raise HTTPException(status_code=500, detail=f"Tables error: {str(e)}")
|
| 87 |
+
|
| 88 |
+
@app.get("/api/v1/schema")
|
| 89 |
+
def get_schema():
|
| 90 |
+
try:
|
| 91 |
+
response = requests.get(f"{SQLITE_URL}/api/v1/schema", timeout=5)
|
| 92 |
+
if response.status_code != 200:
|
| 93 |
+
raise HTTPException(status_code=response.status_code, detail=response.text)
|
| 94 |
+
return response.json()
|
| 95 |
+
except Exception as e:
|
| 96 |
+
raise HTTPException(status_code=500, detail=f"Schema error: {str(e)}")
|
| 97 |
+
|
| 98 |
if __name__ == "__main__":
|
| 99 |
import uvicorn
|
| 100 |
+
port = int(os.getenv("PORT", 7860))
|
| 101 |
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
package-lock.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "sqlite-server-tester",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "sqlite-server-tester",
|
| 9 |
+
"version": "1.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"node-fetch": "^3.3.0"
|
| 12 |
+
}
|
| 13 |
+
},
|
| 14 |
+
"node_modules/data-uri-to-buffer": {
|
| 15 |
+
"version": "4.0.1",
|
| 16 |
+
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
| 17 |
+
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
| 18 |
+
"license": "MIT",
|
| 19 |
+
"engines": {
|
| 20 |
+
"node": ">= 12"
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
"node_modules/fetch-blob": {
|
| 24 |
+
"version": "3.2.0",
|
| 25 |
+
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
| 26 |
+
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
| 27 |
+
"funding": [
|
| 28 |
+
{
|
| 29 |
+
"type": "github",
|
| 30 |
+
"url": "https://github.com/sponsors/jimmywarting"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"type": "paypal",
|
| 34 |
+
"url": "https://paypal.me/jimmywarting"
|
| 35 |
+
}
|
| 36 |
+
],
|
| 37 |
+
"license": "MIT",
|
| 38 |
+
"dependencies": {
|
| 39 |
+
"node-domexception": "^1.0.0",
|
| 40 |
+
"web-streams-polyfill": "^3.0.3"
|
| 41 |
+
},
|
| 42 |
+
"engines": {
|
| 43 |
+
"node": "^12.20 || >= 14.13"
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
"node_modules/formdata-polyfill": {
|
| 47 |
+
"version": "4.0.10",
|
| 48 |
+
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
| 49 |
+
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
| 50 |
+
"license": "MIT",
|
| 51 |
+
"dependencies": {
|
| 52 |
+
"fetch-blob": "^3.1.2"
|
| 53 |
+
},
|
| 54 |
+
"engines": {
|
| 55 |
+
"node": ">=12.20.0"
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
"node_modules/node-domexception": {
|
| 59 |
+
"version": "1.0.0",
|
| 60 |
+
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
| 61 |
+
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
| 62 |
+
"deprecated": "Use your platform's native DOMException instead",
|
| 63 |
+
"funding": [
|
| 64 |
+
{
|
| 65 |
+
"type": "github",
|
| 66 |
+
"url": "https://github.com/sponsors/jimmywarting"
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"type": "github",
|
| 70 |
+
"url": "https://paypal.me/jimmywarting"
|
| 71 |
+
}
|
| 72 |
+
],
|
| 73 |
+
"license": "MIT",
|
| 74 |
+
"engines": {
|
| 75 |
+
"node": ">=10.5.0"
|
| 76 |
+
}
|
| 77 |
+
},
|
| 78 |
+
"node_modules/node-fetch": {
|
| 79 |
+
"version": "3.3.2",
|
| 80 |
+
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
| 81 |
+
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
| 82 |
+
"license": "MIT",
|
| 83 |
+
"dependencies": {
|
| 84 |
+
"data-uri-to-buffer": "^4.0.0",
|
| 85 |
+
"fetch-blob": "^3.1.4",
|
| 86 |
+
"formdata-polyfill": "^4.0.10"
|
| 87 |
+
},
|
| 88 |
+
"engines": {
|
| 89 |
+
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
| 90 |
+
},
|
| 91 |
+
"funding": {
|
| 92 |
+
"type": "opencollective",
|
| 93 |
+
"url": "https://opencollective.com/node-fetch"
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
"node_modules/web-streams-polyfill": {
|
| 97 |
+
"version": "3.3.3",
|
| 98 |
+
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
| 99 |
+
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
| 100 |
+
"license": "MIT",
|
| 101 |
+
"engines": {
|
| 102 |
+
"node": ">= 8"
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "sqlite-server-tester",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Test suite for SQLite Server",
|
| 5 |
+
"main": "test.js",
|
| 6 |
+
"type": "module",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"test": "node test.js",
|
| 9 |
+
"dev": "node test.js"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"node-fetch": "^3.3.0"
|
| 13 |
+
}
|
| 14 |
+
}
|
test.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fetch = (...args) =>
|
| 2 |
+
import("node-fetch").then(({ default: fetch }) => fetch(...args));
|
| 3 |
+
|
| 4 |
+
// ============ CONFIG ============
|
| 5 |
+
// Change BASE_URL to your hosted server
|
| 6 |
+
const BASE_URL = "https://maxxcarl-sqllite.hf.space";
|
| 7 |
+
// For local testing, use: "http://localhost:7860"
|
| 8 |
+
|
| 9 |
+
// ============ COLORS FOR CONSOLE ============
|
| 10 |
+
const colors = {
|
| 11 |
+
reset: "\x1b[0m",
|
| 12 |
+
bright: "\x1b[1m",
|
| 13 |
+
green: "\x1b[32m",
|
| 14 |
+
blue: "\x1b[34m",
|
| 15 |
+
yellow: "\x1b[33m",
|
| 16 |
+
red: "\x1b[31m",
|
| 17 |
+
cyan: "\x1b[36m",
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
function log(msg, color = "reset") {
|
| 21 |
+
console.log(`${colors[color]}${msg}${colors.reset}`);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
function header(msg) {
|
| 25 |
+
log(`\n${"=".repeat(60)}`, "cyan");
|
| 26 |
+
log(` ${msg}`, "bright");
|
| 27 |
+
log(`${"=".repeat(60)}\n`, "cyan");
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// ============ API FUNCTIONS ============
|
| 31 |
+
async function apiCall(endpoint, method = "GET", body = null) {
|
| 32 |
+
const url = `${BASE_URL}${endpoint}`;
|
| 33 |
+
const options = {
|
| 34 |
+
method,
|
| 35 |
+
headers: { "Content-Type": "application/json" },
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
if (body) options.body = JSON.stringify(body);
|
| 39 |
+
|
| 40 |
+
const response = await fetch(url, options);
|
| 41 |
+
const data = await response.json();
|
| 42 |
+
|
| 43 |
+
if (!response.ok) {
|
| 44 |
+
throw new Error(data.detail || `API Error: ${response.status}`);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
return data;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
async function executeQuery(sql) {
|
| 51 |
+
return apiCall("/api/v1/query", "POST", { sql });
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
async function executeUpdate(sql) {
|
| 55 |
+
return apiCall("/api/v1/execute", "POST", { sql });
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
async function getTables() {
|
| 59 |
+
return apiCall("/api/v1/tables");
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
async function getSchema() {
|
| 63 |
+
return apiCall("/api/v1/schema");
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
async function healthCheck() {
|
| 67 |
+
return apiCall("/api/v1/heartbeat");
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// ============ MAIN TEST FLOW ============
|
| 71 |
+
async function main() {
|
| 72 |
+
try {
|
| 73 |
+
log(`\n🚀 SQLite Server Tester`, "bright");
|
| 74 |
+
log(`Base URL: ${BASE_URL}`, "yellow");
|
| 75 |
+
log(`All endpoints accessed through: ${BASE_URL}\n`, "yellow");
|
| 76 |
+
|
| 77 |
+
// 1. Health Check
|
| 78 |
+
header("1️⃣ HEALTH CHECK");
|
| 79 |
+
try {
|
| 80 |
+
const health = await healthCheck();
|
| 81 |
+
log(`✅ Server is healthy!`, "green");
|
| 82 |
+
log(`Response: ${JSON.stringify(health, null, 2)}\n`, "cyan");
|
| 83 |
+
} catch (e) {
|
| 84 |
+
log(`❌ Server health check failed: ${e.message}`, "red");
|
| 85 |
+
return;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// 2. Create Users Table
|
| 89 |
+
header("2️⃣ CREATE USERS TABLE");
|
| 90 |
+
const createUsersSQL = `
|
| 91 |
+
CREATE TABLE IF NOT EXISTS users (
|
| 92 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 93 |
+
name TEXT NOT NULL,
|
| 94 |
+
email TEXT NOT NULL UNIQUE,
|
| 95 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 96 |
+
)
|
| 97 |
+
`;
|
| 98 |
+
try {
|
| 99 |
+
await executeUpdate(createUsersSQL);
|
| 100 |
+
log(`✅ Users table created!`, "green");
|
| 101 |
+
} catch (e) {
|
| 102 |
+
log(`❌ Error creating users table: ${e.message}`, "red");
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// 3. Create Products Table
|
| 106 |
+
header("3️⃣ CREATE PRODUCTS TABLE");
|
| 107 |
+
const createProductsSQL = `
|
| 108 |
+
CREATE TABLE IF NOT EXISTS products (
|
| 109 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 110 |
+
name TEXT NOT NULL,
|
| 111 |
+
price REAL NOT NULL,
|
| 112 |
+
stock INTEGER DEFAULT 0,
|
| 113 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 114 |
+
)
|
| 115 |
+
`;
|
| 116 |
+
try {
|
| 117 |
+
await executeUpdate(createProductsSQL);
|
| 118 |
+
log(`✅ Products table created!`, "green");
|
| 119 |
+
} catch (e) {
|
| 120 |
+
log(`❌ Error creating products table: ${e.message}`, "red");
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// 4. Insert Users
|
| 124 |
+
header("4️⃣ INSERT USERS");
|
| 125 |
+
const usersData = [
|
| 126 |
+
{ name: "Alice Johnson", email: "alice@example.com" },
|
| 127 |
+
{ name: "Bob Smith", email: "bob@example.com" },
|
| 128 |
+
{ name: "Charlie Brown", email: "charlie@example.com" },
|
| 129 |
+
];
|
| 130 |
+
|
| 131 |
+
for (const user of usersData) {
|
| 132 |
+
const insertSQL = `INSERT INTO users (name, email) VALUES ('${user.name}', '${user.email}')`;
|
| 133 |
+
try {
|
| 134 |
+
await executeUpdate(insertSQL);
|
| 135 |
+
log(` ✅ Added: ${user.name}`, "green");
|
| 136 |
+
} catch (e) {
|
| 137 |
+
log(` ⚠️ ${user.name}: ${e.message}`, "yellow");
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
// 5. Insert Products
|
| 142 |
+
header("5️⃣ INSERT PRODUCTS");
|
| 143 |
+
const productsData = [
|
| 144 |
+
{ name: "Laptop Pro", price: 1299.99, stock: 15 },
|
| 145 |
+
{ name: "Wireless Mouse", price: 29.99, stock: 100 },
|
| 146 |
+
{ name: "USB-C Cable", price: 9.99, stock: 250 },
|
| 147 |
+
{ name: "Monitor 4K", price: 499.99, stock: 8 },
|
| 148 |
+
];
|
| 149 |
+
|
| 150 |
+
for (const product of productsData) {
|
| 151 |
+
const insertSQL = `INSERT INTO products (name, price, stock) VALUES ('${product.name}', ${product.price}, ${product.stock})`;
|
| 152 |
+
try {
|
| 153 |
+
await executeUpdate(insertSQL);
|
| 154 |
+
log(` ✅ Added: ${product.name}`, "green");
|
| 155 |
+
} catch (e) {
|
| 156 |
+
log(` ⚠️ ${product.name}: ${e.message}`, "yellow");
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// 6. Get All Tables
|
| 161 |
+
header("6️⃣ LIST ALL TABLES");
|
| 162 |
+
try {
|
| 163 |
+
const tables = await getTables();
|
| 164 |
+
log(`Found tables: ${tables.tables.join(", ")}`, "cyan");
|
| 165 |
+
} catch (e) {
|
| 166 |
+
log(`❌ Error getting tables: ${e.message}`, "red");
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
// 7. Get Schema
|
| 170 |
+
header("7️⃣ DATABASE SCHEMA");
|
| 171 |
+
try {
|
| 172 |
+
const schema = await getSchema();
|
| 173 |
+
log(JSON.stringify(schema, null, 2), "cyan");
|
| 174 |
+
} catch (e) {
|
| 175 |
+
log(`❌ Error getting schema: ${e.message}`, "red");
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
// 8. Query Users
|
| 179 |
+
header("8️⃣ QUERY USERS");
|
| 180 |
+
try {
|
| 181 |
+
const result = await executeQuery("SELECT * FROM users");
|
| 182 |
+
log(JSON.stringify(result.data, null, 2), "cyan");
|
| 183 |
+
} catch (e) {
|
| 184 |
+
log(`❌ Error querying users: ${e.message}`, "red");
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
// 9. Query Products with Price > 50
|
| 188 |
+
header("9️⃣ QUERY EXPENSIVE PRODUCTS (> $50)");
|
| 189 |
+
try {
|
| 190 |
+
const result = await executeQuery(
|
| 191 |
+
"SELECT * FROM products WHERE price > 50 ORDER BY price DESC",
|
| 192 |
+
);
|
| 193 |
+
log(JSON.stringify(result.data, null, 2), "cyan");
|
| 194 |
+
} catch (e) {
|
| 195 |
+
log(`❌ Error querying products: ${e.message}`, "red");
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
// 10. Summary
|
| 199 |
+
header("✅ ALL TESTS COMPLETED");
|
| 200 |
+
log(
|
| 201 |
+
`
|
| 202 |
+
🎉 Database Setup Complete!
|
| 203 |
+
|
| 204 |
+
Tables Created:
|
| 205 |
+
- users (name, email, created_at)
|
| 206 |
+
- products (name, price, stock, created_at)
|
| 207 |
+
|
| 208 |
+
Sample Data:
|
| 209 |
+
- 3 users inserted
|
| 210 |
+
- 4 products inserted
|
| 211 |
+
|
| 212 |
+
Access your data:
|
| 213 |
+
- API: ${BASE_URL}
|
| 214 |
+
- Swagger UI: ${BASE_URL}/docs
|
| 215 |
+
- Health: ${BASE_URL}/health
|
| 216 |
+
`,
|
| 217 |
+
"green",
|
| 218 |
+
);
|
| 219 |
+
} catch (error) {
|
| 220 |
+
log(`\n❌ Fatal Error: ${error.message}`, "red");
|
| 221 |
+
process.exit(1);
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
// Run the tests
|
| 226 |
+
main().catch(console.error);
|