gcharanteja commited on
Commit
c3f4433
Β·
1 Parent(s): ec417fa

feat: Refactor SQLite server to support multi-project databases and update Express.js server

Browse files

- Renamed project from sqlite-server-tester to sqlite-express-server
- Updated package.json to include express, axios, and dotenv dependencies
- Created server.js for Express.js server handling SQLite database interactions
- Implemented API endpoints for managing products and orders
- Enhanced SQLite server (sqlite_server.py) to handle multiple project databases
- Added project management endpoints (create, list, delete projects)
- Updated test.js to reflect new multi-project structure and test various functionalities

Files changed (7) hide show
  1. .env +9 -0
  2. app.py +81 -20
  3. package-lock.json +971 -51
  4. package.json +9 -7
  5. server.js +206 -0
  6. sqlite_server.py +104 -26
  7. test.js +157 -113
.env ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # SQLite Server Connection
2
+ # Format: http://localhost:8000 (local) or https://your-space.hf.space (remote)
3
+ DB_URL=https://maxxcarl-sqllite.hf.space
4
+
5
+ # Database/Project name
6
+ DATABASE=ecommerce
7
+
8
+ # Express server port
9
+ PORT=3000
app.py CHANGED
@@ -1,37 +1,68 @@
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 """
20
  <html>
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
  """
@@ -55,10 +86,40 @@ def heartbeat():
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()
@@ -68,7 +129,7 @@ def query(request: QueryRequest):
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()
@@ -76,9 +137,9 @@ def execute(request: ExecuteRequest):
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()
@@ -86,9 +147,9 @@ def list_tables():
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()
@@ -98,4 +159,4 @@ def get_schema():
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)
 
1
  import os
2
  import requests
3
+ from fastapi import FastAPI, HTTPException, Query
4
+ from fastapi.responses import HTMLResponse
5
  from pydantic import BaseModel
6
 
7
+ app = FastAPI(title="SQLite Multi-Project on HF Space")
8
 
9
  SQLITE_URL = "http://localhost:8000"
10
 
11
  class QueryRequest(BaseModel):
12
  sql: str
13
+ project: str = "default"
14
 
15
  class ExecuteRequest(BaseModel):
16
  sql: str
17
+ project: str = "default"
18
+
19
+ class ProjectRequest(BaseModel):
20
+ name: str
21
+ description: str = ""
22
 
23
  @app.get("/", response_class=HTMLResponse)
24
  def read_root():
25
  return """
26
  <html>
27
+ <head>
28
+ <title>SQLite Multi-Project Server</title>
29
+ <style>
30
+ body { font-family: Arial; margin: 40px; background: #f5f5f5; }
31
+ h1 { color: #333; }
32
+ .info { background: white; padding: 20px; border-radius: 8px; margin: 10px 0; }
33
+ code { background: #e0e0e0; padding: 2px 6px; border-radius: 3px; }
34
+ ul { line-height: 1.8; }
35
+ </style>
36
+ </head>
37
  <body>
38
+ <h1>βœ… SQLite Multi-Project Server</h1>
39
+ <div class="info">
40
+ <p><strong>Persistent Storage:</strong> /data/ (organized by project)</p>
41
+ <p><strong>API Docs:</strong> <a href="/docs">Swagger UI</a></p>
42
+ </div>
43
+
44
+ <h2>Project Management</h2>
45
+ <ul>
46
+ <li><code>GET /api/v1/projects</code> - List all projects</li>
47
+ <li><code>POST /api/v1/projects</code> - Create new project</li>
48
+ <li><code>DELETE /api/v1/projects/{name}</code> - Delete project</li>
49
+ </ul>
50
+
51
+ <h2>Database Operations</h2>
52
  <ul>
53
+ <li><code>GET /api/v1/tables?project=myproject</code> - List tables</li>
54
+ <li><code>GET /api/v1/schema?project=myproject</code> - Get schema</li>
55
+ <li><code>POST /api/v1/query</code> - Execute SELECT</li>
56
  <li><code>POST /api/v1/execute</code> - Execute INSERT/UPDATE/DELETE</li>
 
 
57
  </ul>
58
+
59
+ <h2>Example: Multiple Projects</h2>
60
+ <pre>
61
+ πŸ“ /data/
62
+ β”œβ”€β”€ project1.db (users, orders tables)
63
+ β”œβ”€β”€ project2.db (products, inventory tables)
64
+ └── project3.db (logs, analytics tables)
65
+ </pre>
66
  </body>
67
  </html>
68
  """
 
86
  except Exception as e:
87
  raise HTTPException(status_code=500, detail=f"SQLite server error: {str(e)}")
88
 
89
+ @app.get("/api/v1/projects")
90
+ def list_projects():
91
+ try:
92
+ response = requests.get(f"{SQLITE_URL}/api/v1/projects", timeout=5)
93
+ if response.status_code != 200:
94
+ raise HTTPException(status_code=response.status_code, detail=response.text)
95
+ return response.json()
96
+ except Exception as e:
97
+ raise HTTPException(status_code=500, detail=f"Projects error: {str(e)}")
98
+
99
+ @app.post("/api/v1/projects")
100
+ def create_project(request: ProjectRequest):
101
+ try:
102
+ response = requests.post(f"{SQLITE_URL}/api/v1/projects", json={"name": request.name, "description": request.description}, timeout=5)
103
+ if response.status_code != 200:
104
+ raise HTTPException(status_code=response.status_code, detail=response.text)
105
+ return response.json()
106
+ except Exception as e:
107
+ raise HTTPException(status_code=500, detail=f"Create project error: {str(e)}")
108
+
109
+ @app.delete("/api/v1/projects/{project_name}")
110
+ def delete_project(project_name: str):
111
+ try:
112
+ response = requests.delete(f"{SQLITE_URL}/api/v1/projects/{project_name}", timeout=5)
113
+ if response.status_code != 200:
114
+ raise HTTPException(status_code=response.status_code, detail=response.text)
115
+ return response.json()
116
+ except Exception as e:
117
+ raise HTTPException(status_code=500, detail=f"Delete project error: {str(e)}")
118
+
119
  @app.post("/api/v1/query")
120
  def query(request: QueryRequest):
121
  try:
122
+ response = requests.post(f"{SQLITE_URL}/api/v1/query", json={"sql": request.sql, "project": request.project}, timeout=10)
123
  if response.status_code != 200:
124
  raise HTTPException(status_code=response.status_code, detail=response.text)
125
  return response.json()
 
129
  @app.post("/api/v1/execute")
130
  def execute(request: ExecuteRequest):
131
  try:
132
+ response = requests.post(f"{SQLITE_URL}/api/v1/execute", json={"sql": request.sql, "project": request.project}, timeout=10)
133
  if response.status_code != 200:
134
  raise HTTPException(status_code=response.status_code, detail=response.text)
135
  return response.json()
 
137
  raise HTTPException(status_code=500, detail=f"Execute error: {str(e)}")
138
 
139
  @app.get("/api/v1/tables")
140
+ def list_tables(project: str = Query("default")):
141
  try:
142
+ response = requests.get(f"{SQLITE_URL}/api/v1/tables?project={project}", timeout=5)
143
  if response.status_code != 200:
144
  raise HTTPException(status_code=response.status_code, detail=response.text)
145
  return response.json()
 
147
  raise HTTPException(status_code=500, detail=f"Tables error: {str(e)}")
148
 
149
  @app.get("/api/v1/schema")
150
+ def get_schema(project: str = Query("default")):
151
  try:
152
+ response = requests.get(f"{SQLITE_URL}/api/v1/schema?project={project}", timeout=5)
153
  if response.status_code != 200:
154
  raise HTTPException(status_code=response.status_code, detail=response.text)
155
  return response.json()
 
159
  if __name__ == "__main__":
160
  import uvicorn
161
  port = int(os.getenv("PORT", 7860))
162
+ uvicorn.run(app, host="0.0.0.0", port=port)
package-lock.json CHANGED
@@ -1,105 +1,1025 @@
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
  }
 
1
  {
2
+ "name": "sqlite-express-server",
3
  "version": "1.0.0",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
+ "name": "sqlite-express-server",
9
  "version": "1.0.0",
10
  "dependencies": {
11
+ "axios": "^1.17.0",
12
+ "dotenv": "^16.0.3",
13
+ "express": "^4.18.2"
14
  }
15
  },
16
+ "node_modules/accepts": {
17
+ "version": "1.3.8",
18
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
19
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
20
  "license": "MIT",
21
+ "dependencies": {
22
+ "mime-types": "~2.1.34",
23
+ "negotiator": "0.6.3"
24
+ },
25
+ "engines": {
26
+ "node": ">= 0.6"
27
+ }
28
+ },
29
+ "node_modules/agent-base": {
30
+ "version": "6.0.2",
31
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
32
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "debug": "4"
36
+ },
37
+ "engines": {
38
+ "node": ">= 6.0.0"
39
+ }
40
+ },
41
+ "node_modules/agent-base/node_modules/debug": {
42
+ "version": "4.4.3",
43
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
44
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
45
+ "license": "MIT",
46
+ "dependencies": {
47
+ "ms": "^2.1.3"
48
+ },
49
+ "engines": {
50
+ "node": ">=6.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "supports-color": {
54
+ "optional": true
55
+ }
56
+ }
57
+ },
58
+ "node_modules/agent-base/node_modules/ms": {
59
+ "version": "2.1.3",
60
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
61
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
62
+ "license": "MIT"
63
+ },
64
+ "node_modules/array-flatten": {
65
+ "version": "1.1.1",
66
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
67
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
68
+ "license": "MIT"
69
+ },
70
+ "node_modules/asynckit": {
71
+ "version": "0.4.0",
72
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
73
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
74
+ "license": "MIT"
75
+ },
76
+ "node_modules/axios": {
77
+ "version": "1.17.0",
78
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz",
79
+ "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==",
80
+ "license": "MIT",
81
+ "dependencies": {
82
+ "follow-redirects": "^1.16.0",
83
+ "form-data": "^4.0.5",
84
+ "https-proxy-agent": "^5.0.1",
85
+ "proxy-from-env": "^2.1.0"
86
+ }
87
+ },
88
+ "node_modules/body-parser": {
89
+ "version": "1.20.5",
90
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
91
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
92
+ "license": "MIT",
93
+ "dependencies": {
94
+ "bytes": "~3.1.2",
95
+ "content-type": "~1.0.5",
96
+ "debug": "2.6.9",
97
+ "depd": "2.0.0",
98
+ "destroy": "~1.2.0",
99
+ "http-errors": "~2.0.1",
100
+ "iconv-lite": "~0.4.24",
101
+ "on-finished": "~2.4.1",
102
+ "qs": "~6.15.1",
103
+ "raw-body": "~2.5.3",
104
+ "type-is": "~1.6.18",
105
+ "unpipe": "~1.0.0"
106
+ },
107
+ "engines": {
108
+ "node": ">= 0.8",
109
+ "npm": "1.2.8000 || >= 1.4.16"
110
+ }
111
+ },
112
+ "node_modules/bytes": {
113
+ "version": "3.1.2",
114
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
115
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
116
+ "license": "MIT",
117
+ "engines": {
118
+ "node": ">= 0.8"
119
+ }
120
+ },
121
+ "node_modules/call-bind-apply-helpers": {
122
+ "version": "1.0.2",
123
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
124
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
125
+ "license": "MIT",
126
+ "dependencies": {
127
+ "es-errors": "^1.3.0",
128
+ "function-bind": "^1.1.2"
129
+ },
130
+ "engines": {
131
+ "node": ">= 0.4"
132
+ }
133
+ },
134
+ "node_modules/call-bound": {
135
+ "version": "1.0.4",
136
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
137
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
138
+ "license": "MIT",
139
+ "dependencies": {
140
+ "call-bind-apply-helpers": "^1.0.2",
141
+ "get-intrinsic": "^1.3.0"
142
+ },
143
+ "engines": {
144
+ "node": ">= 0.4"
145
+ },
146
+ "funding": {
147
+ "url": "https://github.com/sponsors/ljharb"
148
+ }
149
+ },
150
+ "node_modules/combined-stream": {
151
+ "version": "1.0.8",
152
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
153
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
154
+ "license": "MIT",
155
+ "dependencies": {
156
+ "delayed-stream": "~1.0.0"
157
+ },
158
+ "engines": {
159
+ "node": ">= 0.8"
160
+ }
161
+ },
162
+ "node_modules/content-disposition": {
163
+ "version": "0.5.4",
164
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
165
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
166
+ "license": "MIT",
167
+ "dependencies": {
168
+ "safe-buffer": "5.2.1"
169
+ },
170
+ "engines": {
171
+ "node": ">= 0.6"
172
+ }
173
+ },
174
+ "node_modules/content-type": {
175
+ "version": "1.0.5",
176
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
177
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
178
+ "license": "MIT",
179
+ "engines": {
180
+ "node": ">= 0.6"
181
+ }
182
+ },
183
+ "node_modules/cookie": {
184
+ "version": "0.7.2",
185
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
186
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
187
+ "license": "MIT",
188
+ "engines": {
189
+ "node": ">= 0.6"
190
+ }
191
+ },
192
+ "node_modules/cookie-signature": {
193
+ "version": "1.0.7",
194
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
195
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
196
+ "license": "MIT"
197
+ },
198
+ "node_modules/debug": {
199
+ "version": "2.6.9",
200
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
201
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
202
+ "license": "MIT",
203
+ "dependencies": {
204
+ "ms": "2.0.0"
205
+ }
206
+ },
207
+ "node_modules/delayed-stream": {
208
+ "version": "1.0.0",
209
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
210
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
211
+ "license": "MIT",
212
+ "engines": {
213
+ "node": ">=0.4.0"
214
+ }
215
+ },
216
+ "node_modules/depd": {
217
+ "version": "2.0.0",
218
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
219
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
220
+ "license": "MIT",
221
+ "engines": {
222
+ "node": ">= 0.8"
223
+ }
224
+ },
225
+ "node_modules/destroy": {
226
+ "version": "1.2.0",
227
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
228
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
229
+ "license": "MIT",
230
+ "engines": {
231
+ "node": ">= 0.8",
232
+ "npm": "1.2.8000 || >= 1.4.16"
233
+ }
234
+ },
235
+ "node_modules/dotenv": {
236
+ "version": "16.6.1",
237
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
238
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
239
+ "license": "BSD-2-Clause",
240
+ "engines": {
241
+ "node": ">=12"
242
+ },
243
+ "funding": {
244
+ "url": "https://dotenvx.com"
245
+ }
246
+ },
247
+ "node_modules/dunder-proto": {
248
+ "version": "1.0.1",
249
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
250
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
251
+ "license": "MIT",
252
+ "dependencies": {
253
+ "call-bind-apply-helpers": "^1.0.1",
254
+ "es-errors": "^1.3.0",
255
+ "gopd": "^1.2.0"
256
+ },
257
+ "engines": {
258
+ "node": ">= 0.4"
259
+ }
260
+ },
261
+ "node_modules/ee-first": {
262
+ "version": "1.1.1",
263
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
264
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
265
+ "license": "MIT"
266
+ },
267
+ "node_modules/encodeurl": {
268
+ "version": "2.0.0",
269
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
270
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
271
+ "license": "MIT",
272
+ "engines": {
273
+ "node": ">= 0.8"
274
+ }
275
+ },
276
+ "node_modules/es-define-property": {
277
+ "version": "1.0.1",
278
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
279
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
280
+ "license": "MIT",
281
+ "engines": {
282
+ "node": ">= 0.4"
283
+ }
284
+ },
285
+ "node_modules/es-errors": {
286
+ "version": "1.3.0",
287
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
288
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
289
+ "license": "MIT",
290
+ "engines": {
291
+ "node": ">= 0.4"
292
+ }
293
+ },
294
+ "node_modules/es-object-atoms": {
295
+ "version": "1.1.2",
296
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
297
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
298
+ "license": "MIT",
299
+ "dependencies": {
300
+ "es-errors": "^1.3.0"
301
+ },
302
+ "engines": {
303
+ "node": ">= 0.4"
304
+ }
305
+ },
306
+ "node_modules/es-set-tostringtag": {
307
+ "version": "2.1.0",
308
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
309
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
310
+ "license": "MIT",
311
+ "dependencies": {
312
+ "es-errors": "^1.3.0",
313
+ "get-intrinsic": "^1.2.6",
314
+ "has-tostringtag": "^1.0.2",
315
+ "hasown": "^2.0.2"
316
+ },
317
+ "engines": {
318
+ "node": ">= 0.4"
319
+ }
320
+ },
321
+ "node_modules/escape-html": {
322
+ "version": "1.0.3",
323
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
324
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
325
+ "license": "MIT"
326
+ },
327
+ "node_modules/etag": {
328
+ "version": "1.8.1",
329
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
330
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
331
+ "license": "MIT",
332
+ "engines": {
333
+ "node": ">= 0.6"
334
+ }
335
+ },
336
+ "node_modules/express": {
337
+ "version": "4.22.2",
338
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
339
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
340
+ "license": "MIT",
341
+ "dependencies": {
342
+ "accepts": "~1.3.8",
343
+ "array-flatten": "1.1.1",
344
+ "body-parser": "~1.20.5",
345
+ "content-disposition": "~0.5.4",
346
+ "content-type": "~1.0.4",
347
+ "cookie": "~0.7.1",
348
+ "cookie-signature": "~1.0.6",
349
+ "debug": "2.6.9",
350
+ "depd": "2.0.0",
351
+ "encodeurl": "~2.0.0",
352
+ "escape-html": "~1.0.3",
353
+ "etag": "~1.8.1",
354
+ "finalhandler": "~1.3.1",
355
+ "fresh": "~0.5.2",
356
+ "http-errors": "~2.0.0",
357
+ "merge-descriptors": "1.0.3",
358
+ "methods": "~1.1.2",
359
+ "on-finished": "~2.4.1",
360
+ "parseurl": "~1.3.3",
361
+ "path-to-regexp": "~0.1.12",
362
+ "proxy-addr": "~2.0.7",
363
+ "qs": "~6.15.1",
364
+ "range-parser": "~1.2.1",
365
+ "safe-buffer": "5.2.1",
366
+ "send": "~0.19.0",
367
+ "serve-static": "~1.16.2",
368
+ "setprototypeof": "1.2.0",
369
+ "statuses": "~2.0.1",
370
+ "type-is": "~1.6.18",
371
+ "utils-merge": "1.0.1",
372
+ "vary": "~1.1.2"
373
+ },
374
+ "engines": {
375
+ "node": ">= 0.10.0"
376
+ },
377
+ "funding": {
378
+ "type": "opencollective",
379
+ "url": "https://opencollective.com/express"
380
+ }
381
+ },
382
+ "node_modules/finalhandler": {
383
+ "version": "1.3.2",
384
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
385
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
386
+ "license": "MIT",
387
+ "dependencies": {
388
+ "debug": "2.6.9",
389
+ "encodeurl": "~2.0.0",
390
+ "escape-html": "~1.0.3",
391
+ "on-finished": "~2.4.1",
392
+ "parseurl": "~1.3.3",
393
+ "statuses": "~2.0.2",
394
+ "unpipe": "~1.0.0"
395
+ },
396
  "engines": {
397
+ "node": ">= 0.8"
398
  }
399
  },
400
+ "node_modules/follow-redirects": {
401
+ "version": "1.16.0",
402
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
403
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
404
  "funding": [
405
  {
406
+ "type": "individual",
407
+ "url": "https://github.com/sponsors/RubenVerborgh"
 
 
 
 
408
  }
409
  ],
410
  "license": "MIT",
411
+ "engines": {
412
+ "node": ">=4.0"
413
+ },
414
+ "peerDependenciesMeta": {
415
+ "debug": {
416
+ "optional": true
417
+ }
418
+ }
419
+ },
420
+ "node_modules/form-data": {
421
+ "version": "4.0.5",
422
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
423
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
424
+ "license": "MIT",
425
  "dependencies": {
426
+ "asynckit": "^0.4.0",
427
+ "combined-stream": "^1.0.8",
428
+ "es-set-tostringtag": "^2.1.0",
429
+ "hasown": "^2.0.2",
430
+ "mime-types": "^2.1.12"
431
  },
432
  "engines": {
433
+ "node": ">= 6"
434
+ }
435
+ },
436
+ "node_modules/forwarded": {
437
+ "version": "0.2.0",
438
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
439
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
440
+ "license": "MIT",
441
+ "engines": {
442
+ "node": ">= 0.6"
443
+ }
444
+ },
445
+ "node_modules/fresh": {
446
+ "version": "0.5.2",
447
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
448
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
449
+ "license": "MIT",
450
+ "engines": {
451
+ "node": ">= 0.6"
452
+ }
453
+ },
454
+ "node_modules/function-bind": {
455
+ "version": "1.1.2",
456
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
457
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
458
+ "license": "MIT",
459
+ "funding": {
460
+ "url": "https://github.com/sponsors/ljharb"
461
  }
462
  },
463
+ "node_modules/get-intrinsic": {
464
+ "version": "1.3.0",
465
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
466
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
467
  "license": "MIT",
468
  "dependencies": {
469
+ "call-bind-apply-helpers": "^1.0.2",
470
+ "es-define-property": "^1.0.1",
471
+ "es-errors": "^1.3.0",
472
+ "es-object-atoms": "^1.1.1",
473
+ "function-bind": "^1.1.2",
474
+ "get-proto": "^1.0.1",
475
+ "gopd": "^1.2.0",
476
+ "has-symbols": "^1.1.0",
477
+ "hasown": "^2.0.2",
478
+ "math-intrinsics": "^1.1.0"
479
  },
480
  "engines": {
481
+ "node": ">= 0.4"
482
+ },
483
+ "funding": {
484
+ "url": "https://github.com/sponsors/ljharb"
485
  }
486
  },
487
+ "node_modules/get-proto": {
488
+ "version": "1.0.1",
489
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
490
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
491
+ "license": "MIT",
492
+ "dependencies": {
493
+ "dunder-proto": "^1.0.1",
494
+ "es-object-atoms": "^1.0.0"
495
+ },
496
+ "engines": {
497
+ "node": ">= 0.4"
498
+ }
499
+ },
500
+ "node_modules/gopd": {
501
+ "version": "1.2.0",
502
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
503
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
504
+ "license": "MIT",
505
+ "engines": {
506
+ "node": ">= 0.4"
507
+ },
508
+ "funding": {
509
+ "url": "https://github.com/sponsors/ljharb"
510
+ }
511
+ },
512
+ "node_modules/has-symbols": {
513
+ "version": "1.1.0",
514
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
515
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
516
+ "license": "MIT",
517
+ "engines": {
518
+ "node": ">= 0.4"
519
+ },
520
+ "funding": {
521
+ "url": "https://github.com/sponsors/ljharb"
522
+ }
523
+ },
524
+ "node_modules/has-tostringtag": {
525
+ "version": "1.0.2",
526
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
527
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
528
+ "license": "MIT",
529
+ "dependencies": {
530
+ "has-symbols": "^1.0.3"
531
+ },
532
+ "engines": {
533
+ "node": ">= 0.4"
534
+ },
535
+ "funding": {
536
+ "url": "https://github.com/sponsors/ljharb"
537
+ }
538
+ },
539
+ "node_modules/hasown": {
540
+ "version": "2.0.4",
541
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
542
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
543
+ "license": "MIT",
544
+ "dependencies": {
545
+ "function-bind": "^1.1.2"
546
+ },
547
+ "engines": {
548
+ "node": ">= 0.4"
549
+ }
550
+ },
551
+ "node_modules/http-errors": {
552
+ "version": "2.0.1",
553
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
554
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
555
+ "license": "MIT",
556
+ "dependencies": {
557
+ "depd": "~2.0.0",
558
+ "inherits": "~2.0.4",
559
+ "setprototypeof": "~1.2.0",
560
+ "statuses": "~2.0.2",
561
+ "toidentifier": "~1.0.1"
562
+ },
563
+ "engines": {
564
+ "node": ">= 0.8"
565
+ },
566
+ "funding": {
567
+ "type": "opencollective",
568
+ "url": "https://opencollective.com/express"
569
+ }
570
+ },
571
+ "node_modules/https-proxy-agent": {
572
+ "version": "5.0.1",
573
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
574
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
575
+ "license": "MIT",
576
+ "dependencies": {
577
+ "agent-base": "6",
578
+ "debug": "4"
579
+ },
580
+ "engines": {
581
+ "node": ">= 6"
582
+ }
583
+ },
584
+ "node_modules/https-proxy-agent/node_modules/debug": {
585
+ "version": "4.4.3",
586
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
587
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
588
+ "license": "MIT",
589
+ "dependencies": {
590
+ "ms": "^2.1.3"
591
+ },
592
+ "engines": {
593
+ "node": ">=6.0"
594
+ },
595
+ "peerDependenciesMeta": {
596
+ "supports-color": {
597
+ "optional": true
598
+ }
599
+ }
600
+ },
601
+ "node_modules/https-proxy-agent/node_modules/ms": {
602
+ "version": "2.1.3",
603
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
604
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
605
+ "license": "MIT"
606
+ },
607
+ "node_modules/iconv-lite": {
608
+ "version": "0.4.24",
609
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
610
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
611
+ "license": "MIT",
612
+ "dependencies": {
613
+ "safer-buffer": ">= 2.1.2 < 3"
614
+ },
615
+ "engines": {
616
+ "node": ">=0.10.0"
617
+ }
618
+ },
619
+ "node_modules/inherits": {
620
+ "version": "2.0.4",
621
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
622
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
623
+ "license": "ISC"
624
+ },
625
+ "node_modules/ipaddr.js": {
626
+ "version": "1.9.1",
627
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
628
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
629
+ "license": "MIT",
630
+ "engines": {
631
+ "node": ">= 0.10"
632
+ }
633
+ },
634
+ "node_modules/math-intrinsics": {
635
+ "version": "1.1.0",
636
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
637
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
638
+ "license": "MIT",
639
+ "engines": {
640
+ "node": ">= 0.4"
641
+ }
642
+ },
643
+ "node_modules/media-typer": {
644
+ "version": "0.3.0",
645
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
646
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
647
+ "license": "MIT",
648
+ "engines": {
649
+ "node": ">= 0.6"
650
+ }
651
+ },
652
+ "node_modules/merge-descriptors": {
653
+ "version": "1.0.3",
654
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
655
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
656
+ "license": "MIT",
657
+ "funding": {
658
+ "url": "https://github.com/sponsors/sindresorhus"
659
+ }
660
+ },
661
+ "node_modules/methods": {
662
+ "version": "1.1.2",
663
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
664
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
665
+ "license": "MIT",
666
+ "engines": {
667
+ "node": ">= 0.6"
668
+ }
669
+ },
670
+ "node_modules/mime": {
671
+ "version": "1.6.0",
672
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
673
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
674
+ "license": "MIT",
675
+ "bin": {
676
+ "mime": "cli.js"
677
+ },
678
+ "engines": {
679
+ "node": ">=4"
680
+ }
681
+ },
682
+ "node_modules/mime-db": {
683
+ "version": "1.52.0",
684
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
685
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
686
+ "license": "MIT",
687
+ "engines": {
688
+ "node": ">= 0.6"
689
+ }
690
+ },
691
+ "node_modules/mime-types": {
692
+ "version": "2.1.35",
693
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
694
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
695
+ "license": "MIT",
696
+ "dependencies": {
697
+ "mime-db": "1.52.0"
698
+ },
699
+ "engines": {
700
+ "node": ">= 0.6"
701
+ }
702
+ },
703
+ "node_modules/ms": {
704
+ "version": "2.0.0",
705
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
706
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
707
+ "license": "MIT"
708
+ },
709
+ "node_modules/negotiator": {
710
+ "version": "0.6.3",
711
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
712
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
713
+ "license": "MIT",
714
+ "engines": {
715
+ "node": ">= 0.6"
716
+ }
717
+ },
718
+ "node_modules/object-inspect": {
719
+ "version": "1.13.4",
720
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
721
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
722
+ "license": "MIT",
723
+ "engines": {
724
+ "node": ">= 0.4"
725
+ },
726
+ "funding": {
727
+ "url": "https://github.com/sponsors/ljharb"
728
+ }
729
+ },
730
+ "node_modules/on-finished": {
731
+ "version": "2.4.1",
732
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
733
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
734
+ "license": "MIT",
735
+ "dependencies": {
736
+ "ee-first": "1.1.1"
737
+ },
738
+ "engines": {
739
+ "node": ">= 0.8"
740
+ }
741
+ },
742
+ "node_modules/parseurl": {
743
+ "version": "1.3.3",
744
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
745
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
746
+ "license": "MIT",
747
+ "engines": {
748
+ "node": ">= 0.8"
749
+ }
750
+ },
751
+ "node_modules/path-to-regexp": {
752
+ "version": "0.1.13",
753
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
754
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
755
+ "license": "MIT"
756
+ },
757
+ "node_modules/proxy-addr": {
758
+ "version": "2.0.7",
759
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
760
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
761
+ "license": "MIT",
762
+ "dependencies": {
763
+ "forwarded": "0.2.0",
764
+ "ipaddr.js": "1.9.1"
765
+ },
766
+ "engines": {
767
+ "node": ">= 0.10"
768
+ }
769
+ },
770
+ "node_modules/proxy-from-env": {
771
+ "version": "2.1.0",
772
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
773
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
774
+ "license": "MIT",
775
+ "engines": {
776
+ "node": ">=10"
777
+ }
778
+ },
779
+ "node_modules/qs": {
780
+ "version": "6.15.2",
781
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
782
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
783
+ "license": "BSD-3-Clause",
784
+ "dependencies": {
785
+ "side-channel": "^1.1.0"
786
+ },
787
+ "engines": {
788
+ "node": ">=0.6"
789
+ },
790
+ "funding": {
791
+ "url": "https://github.com/sponsors/ljharb"
792
+ }
793
+ },
794
+ "node_modules/range-parser": {
795
+ "version": "1.2.1",
796
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
797
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
798
+ "license": "MIT",
799
+ "engines": {
800
+ "node": ">= 0.6"
801
+ }
802
+ },
803
+ "node_modules/raw-body": {
804
+ "version": "2.5.3",
805
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
806
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
807
+ "license": "MIT",
808
+ "dependencies": {
809
+ "bytes": "~3.1.2",
810
+ "http-errors": "~2.0.1",
811
+ "iconv-lite": "~0.4.24",
812
+ "unpipe": "~1.0.0"
813
+ },
814
+ "engines": {
815
+ "node": ">= 0.8"
816
+ }
817
+ },
818
+ "node_modules/safe-buffer": {
819
+ "version": "5.2.1",
820
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
821
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
822
  "funding": [
823
  {
824
  "type": "github",
825
+ "url": "https://github.com/sponsors/feross"
826
  },
827
  {
828
+ "type": "patreon",
829
+ "url": "https://www.patreon.com/feross"
830
+ },
831
+ {
832
+ "type": "consulting",
833
+ "url": "https://feross.org/support"
834
  }
835
  ],
836
+ "license": "MIT"
837
+ },
838
+ "node_modules/safer-buffer": {
839
+ "version": "2.1.2",
840
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
841
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
842
+ "license": "MIT"
843
+ },
844
+ "node_modules/send": {
845
+ "version": "0.19.2",
846
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
847
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
848
  "license": "MIT",
849
+ "dependencies": {
850
+ "debug": "2.6.9",
851
+ "depd": "2.0.0",
852
+ "destroy": "1.2.0",
853
+ "encodeurl": "~2.0.0",
854
+ "escape-html": "~1.0.3",
855
+ "etag": "~1.8.1",
856
+ "fresh": "~0.5.2",
857
+ "http-errors": "~2.0.1",
858
+ "mime": "1.6.0",
859
+ "ms": "2.1.3",
860
+ "on-finished": "~2.4.1",
861
+ "range-parser": "~1.2.1",
862
+ "statuses": "~2.0.2"
863
+ },
864
  "engines": {
865
+ "node": ">= 0.8.0"
866
  }
867
  },
868
+ "node_modules/send/node_modules/ms": {
869
+ "version": "2.1.3",
870
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
871
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
872
+ "license": "MIT"
873
+ },
874
+ "node_modules/serve-static": {
875
+ "version": "1.16.3",
876
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
877
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
878
  "license": "MIT",
879
  "dependencies": {
880
+ "encodeurl": "~2.0.0",
881
+ "escape-html": "~1.0.3",
882
+ "parseurl": "~1.3.3",
883
+ "send": "~0.19.1"
884
  },
885
  "engines": {
886
+ "node": ">= 0.8.0"
887
+ }
888
+ },
889
+ "node_modules/setprototypeof": {
890
+ "version": "1.2.0",
891
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
892
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
893
+ "license": "ISC"
894
+ },
895
+ "node_modules/side-channel": {
896
+ "version": "1.1.0",
897
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
898
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
899
+ "license": "MIT",
900
+ "dependencies": {
901
+ "es-errors": "^1.3.0",
902
+ "object-inspect": "^1.13.3",
903
+ "side-channel-list": "^1.0.0",
904
+ "side-channel-map": "^1.0.1",
905
+ "side-channel-weakmap": "^1.0.2"
906
+ },
907
+ "engines": {
908
+ "node": ">= 0.4"
909
  },
910
  "funding": {
911
+ "url": "https://github.com/sponsors/ljharb"
912
+ }
913
+ },
914
+ "node_modules/side-channel-list": {
915
+ "version": "1.0.1",
916
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
917
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
918
+ "license": "MIT",
919
+ "dependencies": {
920
+ "es-errors": "^1.3.0",
921
+ "object-inspect": "^1.13.4"
922
+ },
923
+ "engines": {
924
+ "node": ">= 0.4"
925
+ },
926
+ "funding": {
927
+ "url": "https://github.com/sponsors/ljharb"
928
+ }
929
+ },
930
+ "node_modules/side-channel-map": {
931
+ "version": "1.0.1",
932
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
933
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
934
+ "license": "MIT",
935
+ "dependencies": {
936
+ "call-bound": "^1.0.2",
937
+ "es-errors": "^1.3.0",
938
+ "get-intrinsic": "^1.2.5",
939
+ "object-inspect": "^1.13.3"
940
+ },
941
+ "engines": {
942
+ "node": ">= 0.4"
943
+ },
944
+ "funding": {
945
+ "url": "https://github.com/sponsors/ljharb"
946
+ }
947
+ },
948
+ "node_modules/side-channel-weakmap": {
949
+ "version": "1.0.2",
950
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
951
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
952
+ "license": "MIT",
953
+ "dependencies": {
954
+ "call-bound": "^1.0.2",
955
+ "es-errors": "^1.3.0",
956
+ "get-intrinsic": "^1.2.5",
957
+ "object-inspect": "^1.13.3",
958
+ "side-channel-map": "^1.0.1"
959
+ },
960
+ "engines": {
961
+ "node": ">= 0.4"
962
+ },
963
+ "funding": {
964
+ "url": "https://github.com/sponsors/ljharb"
965
+ }
966
+ },
967
+ "node_modules/statuses": {
968
+ "version": "2.0.2",
969
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
970
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
971
+ "license": "MIT",
972
+ "engines": {
973
+ "node": ">= 0.8"
974
+ }
975
+ },
976
+ "node_modules/toidentifier": {
977
+ "version": "1.0.1",
978
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
979
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
980
+ "license": "MIT",
981
+ "engines": {
982
+ "node": ">=0.6"
983
+ }
984
+ },
985
+ "node_modules/type-is": {
986
+ "version": "1.6.18",
987
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
988
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
989
+ "license": "MIT",
990
+ "dependencies": {
991
+ "media-typer": "0.3.0",
992
+ "mime-types": "~2.1.24"
993
+ },
994
+ "engines": {
995
+ "node": ">= 0.6"
996
+ }
997
+ },
998
+ "node_modules/unpipe": {
999
+ "version": "1.0.0",
1000
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1001
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1002
+ "license": "MIT",
1003
+ "engines": {
1004
+ "node": ">= 0.8"
1005
+ }
1006
+ },
1007
+ "node_modules/utils-merge": {
1008
+ "version": "1.0.1",
1009
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1010
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1011
+ "license": "MIT",
1012
+ "engines": {
1013
+ "node": ">= 0.4.0"
1014
  }
1015
  },
1016
+ "node_modules/vary": {
1017
+ "version": "1.1.2",
1018
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1019
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1020
  "license": "MIT",
1021
  "engines": {
1022
+ "node": ">= 0.8"
1023
  }
1024
  }
1025
  }
package.json CHANGED
@@ -1,14 +1,16 @@
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
  }
 
1
  {
2
+ "name": "sqlite-express-server",
3
  "version": "1.0.0",
4
+ "description": "Express.js server connected to remote SQLite database",
5
+ "main": "server.js",
 
6
  "scripts": {
7
+ "start": "node server.js",
8
+ "dev": "npm run start",
9
+ "test": "node test.js"
10
  },
11
  "dependencies": {
12
+ "axios": "^1.17.0",
13
+ "dotenv": "^16.0.3",
14
+ "express": "^4.18.2"
15
  }
16
  }
server.js ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require("express");
2
+ const axios = require("axios");
3
+ require("dotenv").config();
4
+
5
+ const app = express();
6
+ app.use(express.json());
7
+
8
+ // Environment variables
9
+ const SQLITE_SERVER = process.env.DB_URL || "http://localhost:8000";
10
+ const DATABASE = process.env.DATABASE || "default";
11
+ const PORT = process.env.PORT || 3000;
12
+
13
+ console.log(`πŸ”— Connected to SQLite Server: ${SQLITE_SERVER}`);
14
+ console.log(`πŸ“¦ Using Database: ${DATABASE}`);
15
+
16
+ // Helper function for SQLite API calls
17
+ async function querySQLite(sql, method = "POST", endpoint = "/api/v1/query") {
18
+ try {
19
+ const payload = { sql, project: DATABASE };
20
+ const response = await axios({
21
+ method,
22
+ url: `${SQLITE_SERVER}${endpoint}`,
23
+ data: payload,
24
+ headers: { "Content-Type": "application/json" },
25
+ });
26
+
27
+ return response.data;
28
+ } catch (err) {
29
+ console.error("SQLite Query Error:", err.message);
30
+ throw err;
31
+ }
32
+ }
33
+
34
+ // Health check
35
+ app.get("/health", async (req, res) => {
36
+ try {
37
+ const response = await axios.get(`${SQLITE_SERVER}/api/v1/heartbeat`);
38
+ if (response.status === 200) {
39
+ res.status(200).json({ status: "healthy", database: DATABASE });
40
+ } else {
41
+ res.status(500).json({ status: "unhealthy" });
42
+ }
43
+ } catch (err) {
44
+ res.status(500).json({ status: "error", message: err.message });
45
+ }
46
+ });
47
+
48
+ // Get all products
49
+ app.get("/api/products", async (req, res) => {
50
+ try {
51
+ const result = await querySQLite(
52
+ "SELECT * FROM products",
53
+ "POST",
54
+ "/api/v1/query",
55
+ );
56
+ res.status(200).json(result.data || []);
57
+ } catch (err) {
58
+ res.status(500).json({ error: "Failed to fetch products" });
59
+ }
60
+ });
61
+
62
+ // Get product by ID
63
+ app.get("/api/products/:id", async (req, res) => {
64
+ try {
65
+ const result = await querySQLite(
66
+ `SELECT * FROM products WHERE id = ${req.params.id}`,
67
+ "POST",
68
+ "/api/v1/query",
69
+ );
70
+ if (result.data && result.data.length > 0) {
71
+ res.status(200).json(result.data[0]);
72
+ } else {
73
+ res.status(404).json({ error: "Product not found" });
74
+ }
75
+ } catch (err) {
76
+ res.status(500).json({ error: "Database query failed" });
77
+ }
78
+ });
79
+
80
+ // Create product
81
+ app.post("/api/products", async (req, res) => {
82
+ try {
83
+ const { name, price, stock } = req.body;
84
+
85
+ if (!name || price === undefined) {
86
+ return res
87
+ .status(400)
88
+ .json({ error: "Missing required fields: name, price" });
89
+ }
90
+
91
+ const sql = `INSERT INTO products (name, price, stock) VALUES ('${name}', ${price}, ${stock || 0})`;
92
+ const result = await querySQLite(sql, "POST", "/api/v1/execute");
93
+
94
+ res.status(201).json({
95
+ status: "created",
96
+ message: "Product created successfully",
97
+ rows_affected: result.rows_affected,
98
+ });
99
+ } catch (err) {
100
+ res.status(500).json({ error: "Failed to create product" });
101
+ }
102
+ });
103
+
104
+ // Update product
105
+ app.put("/api/products/:id", async (req, res) => {
106
+ try {
107
+ const { name, price, stock } = req.body;
108
+ const updates = [];
109
+
110
+ if (name) updates.push(`name = '${name}'`);
111
+ if (price !== undefined) updates.push(`price = ${price}`);
112
+ if (stock !== undefined) updates.push(`stock = ${stock}`);
113
+
114
+ if (updates.length === 0) {
115
+ return res.status(400).json({ error: "No fields to update" });
116
+ }
117
+
118
+ const sql = `UPDATE products SET ${updates.join(", ")} WHERE id = ${req.params.id}`;
119
+ const result = await querySQLite(sql, "POST", "/api/v1/execute");
120
+
121
+ res.status(200).json({
122
+ status: "updated",
123
+ message: "Product updated successfully",
124
+ rows_affected: result.rows_affected,
125
+ });
126
+ } catch (err) {
127
+ res.status(500).json({ error: "Failed to update product" });
128
+ }
129
+ });
130
+
131
+ // Delete product
132
+ app.delete("/api/products/:id", async (req, res) => {
133
+ try {
134
+ const sql = `DELETE FROM products WHERE id = ${req.params.id}`;
135
+ const result = await querySQLite(sql, "POST", "/api/v1/execute");
136
+
137
+ res.status(200).json({
138
+ status: "deleted",
139
+ message: "Product deleted successfully",
140
+ rows_affected: result.rows_affected,
141
+ });
142
+ } catch (err) {
143
+ res.status(500).json({ error: "Failed to delete product" });
144
+ }
145
+ });
146
+
147
+ // Get all orders
148
+ app.get("/api/orders", async (req, res) => {
149
+ try {
150
+ const result = await querySQLite(
151
+ "SELECT * FROM orders",
152
+ "POST",
153
+ "/api/v1/query",
154
+ );
155
+ res.status(200).json(result.data || []);
156
+ } catch (err) {
157
+ res.status(500).json({ error: "Failed to fetch orders" });
158
+ }
159
+ });
160
+
161
+ // Create order
162
+ app.post("/api/orders", async (req, res) => {
163
+ try {
164
+ const { customer_name, total, status } = req.body;
165
+
166
+ if (!customer_name) {
167
+ return res
168
+ .status(400)
169
+ .json({ error: "Missing required field: customer_name" });
170
+ }
171
+
172
+ const sql = `INSERT INTO orders (customer_name, total, status) VALUES ('${customer_name}', ${total || 0}, '${status || "pending"}')`;
173
+ const result = await querySQLite(sql, "POST", "/api/v1/execute");
174
+
175
+ res.status(201).json({
176
+ status: "created",
177
+ message: "Order created successfully",
178
+ rows_affected: result.rows_affected,
179
+ });
180
+ } catch (err) {
181
+ res.status(500).json({ error: "Failed to create order" });
182
+ }
183
+ });
184
+
185
+ // Get database info
186
+ app.get("/api/info", async (req, res) => {
187
+ try {
188
+ const response = await axios.get(
189
+ `${SQLITE_SERVER}/api/v1/schema?project=${DATABASE}`,
190
+ );
191
+ res.status(200).json({
192
+ server: SQLITE_SERVER,
193
+ database: DATABASE,
194
+ schema: response.data.schema || {},
195
+ });
196
+ } catch (err) {
197
+ res.status(500).json({ error: "Failed to fetch database info" });
198
+ }
199
+ });
200
+
201
+ app.listen(PORT, () => {
202
+ console.log(`\nβœ… Express Server running on port ${PORT}`);
203
+ console.log(`πŸ“ API: http://localhost:${PORT}`);
204
+ console.log(`πŸ”— SQLite Server: ${SQLITE_SERVER}`);
205
+ console.log(`πŸ“¦ Database: ${DATABASE}\n`);
206
+ });
sqlite_server.py CHANGED
@@ -2,83 +2,161 @@ import os
2
  import sqlite3
3
  import json
4
  from pathlib import Path
5
- from fastapi import FastAPI, HTTPException
6
  from pydantic import BaseModel
7
  from typing import List, Dict, Any
8
  import uvicorn
9
 
10
- app = FastAPI(title="SQLite Server")
11
 
12
- DB_PATH = "/data/sqlite.db"
13
 
14
- def ensure_db_exists():
15
- Path("/data").mkdir(parents=True, exist_ok=True)
16
- if not os.path.exists(DB_PATH):
17
- conn = sqlite3.connect(DB_PATH)
 
 
 
 
 
 
 
18
  conn.close()
19
 
20
- def get_connection():
21
- ensure_db_exists()
22
- conn = sqlite3.connect(DB_PATH)
 
23
  conn.row_factory = sqlite3.Row
24
  return conn
25
 
26
  class QueryRequest(BaseModel):
27
  sql: str
 
28
 
29
  class ExecuteRequest(BaseModel):
30
  sql: str
 
 
 
 
 
31
 
32
  @app.get("/api/v1/heartbeat")
33
  def heartbeat():
34
  try:
35
- conn = get_connection()
36
- conn.execute("SELECT 1")
37
- conn.close()
38
- return {"status": "ok"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  except Exception as e:
40
  raise HTTPException(status_code=500, detail=str(e))
41
 
42
  @app.post("/api/v1/query")
43
  def query(request: QueryRequest):
 
44
  try:
45
- conn = get_connection()
 
46
  cursor = conn.execute(request.sql)
47
  rows = cursor.fetchall()
48
  conn.close()
49
- return {"data": [dict(row) for row in rows]}
 
 
 
 
50
  except Exception as e:
51
  raise HTTPException(status_code=400, detail=str(e))
52
 
53
  @app.post("/api/v1/execute")
54
  def execute(request: ExecuteRequest):
 
55
  try:
56
- conn = get_connection()
 
57
  cursor = conn.execute(request.sql)
58
  conn.commit()
59
- changes = conn.total_changes
60
  conn.close()
61
- return {"rows_affected": cursor.rowcount, "total_changes": changes}
 
 
 
 
62
  except Exception as e:
63
  raise HTTPException(status_code=400, detail=str(e))
64
 
65
  @app.get("/api/v1/tables")
66
- def list_tables():
 
67
  try:
68
- conn = get_connection()
 
69
  cursor = conn.execute(
70
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
71
  )
72
  tables = [row[0] for row in cursor.fetchall()]
73
  conn.close()
74
- return {"tables": tables}
75
  except Exception as e:
76
  raise HTTPException(status_code=500, detail=str(e))
77
 
78
  @app.get("/api/v1/schema")
79
- def get_schema():
 
80
  try:
81
- conn = get_connection()
 
82
  cursor = conn.execute(
83
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
84
  )
@@ -92,10 +170,10 @@ def get_schema():
92
  for col in cursor.fetchall()
93
  ]
94
  conn.close()
95
- return schema
96
  except Exception as e:
97
  raise HTTPException(status_code=500, detail=str(e))
98
 
99
  if __name__ == "__main__":
100
- ensure_db_exists()
101
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
2
  import sqlite3
3
  import json
4
  from pathlib import Path
5
+ from fastapi import FastAPI, HTTPException, Query
6
  from pydantic import BaseModel
7
  from typing import List, Dict, Any
8
  import uvicorn
9
 
10
+ app = FastAPI(title="SQLite Multi-Project Server")
11
 
12
+ DB_DIR = "/data"
13
 
14
+ def ensure_db_dir():
15
+ Path(DB_DIR).mkdir(parents=True, exist_ok=True)
16
+
17
+ def get_db_path(project_name: str):
18
+ # Sanitize project name
19
+ project_name = project_name.replace("/", "_").replace("\\", "_")
20
+ return os.path.join(DB_DIR, f"{project_name}.db")
21
+
22
+ def create_db_if_not_exists(db_path: str):
23
+ if not os.path.exists(db_path):
24
+ conn = sqlite3.connect(db_path)
25
  conn.close()
26
 
27
+ def get_connection(db_path: str):
28
+ ensure_db_dir()
29
+ create_db_if_not_exists(db_path)
30
+ conn = sqlite3.connect(db_path)
31
  conn.row_factory = sqlite3.Row
32
  return conn
33
 
34
  class QueryRequest(BaseModel):
35
  sql: str
36
+ project: str = "default"
37
 
38
  class ExecuteRequest(BaseModel):
39
  sql: str
40
+ project: str = "default"
41
+
42
+ class ProjectRequest(BaseModel):
43
+ name: str
44
+ description: str = ""
45
 
46
  @app.get("/api/v1/heartbeat")
47
  def heartbeat():
48
  try:
49
+ ensure_db_dir()
50
+ return {"status": "ok", "message": "SQLite Multi-Project Server running"}
51
+ except Exception as e:
52
+ raise HTTPException(status_code=500, detail=str(e))
53
+
54
+ @app.get("/api/v1/projects")
55
+ def list_projects():
56
+ """List all projects (databases)"""
57
+ try:
58
+ ensure_db_dir()
59
+ files = [f[:-3] for f in os.listdir(DB_DIR) if f.endswith('.db')]
60
+ projects = []
61
+ for name in files:
62
+ db_path = get_db_path(name)
63
+ size = os.path.getsize(db_path)
64
+ projects.append({"name": name, "size_bytes": size})
65
+ return {"projects": projects, "total": len(projects)}
66
+ except Exception as e:
67
+ raise HTTPException(status_code=500, detail=str(e))
68
+
69
+ @app.post("/api/v1/projects")
70
+ def create_project(request: ProjectRequest):
71
+ """Create a new project database"""
72
+ try:
73
+ db_path = get_db_path(request.name)
74
+ if os.path.exists(db_path):
75
+ raise HTTPException(status_code=400, detail=f"Project '{request.name}' already exists")
76
+
77
+ create_db_if_not_exists(db_path)
78
+ return {
79
+ "status": "created",
80
+ "project": request.name,
81
+ "path": db_path,
82
+ "description": request.description
83
+ }
84
+ except HTTPException:
85
+ raise
86
+ except Exception as e:
87
+ raise HTTPException(status_code=500, detail=str(e))
88
+
89
+ @app.delete("/api/v1/projects/{project_name}")
90
+ def delete_project(project_name: str):
91
+ """Delete a project database"""
92
+ try:
93
+ db_path = get_db_path(project_name)
94
+ if not os.path.exists(db_path):
95
+ raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found")
96
+
97
+ os.remove(db_path)
98
+ return {"status": "deleted", "project": project_name}
99
+ except HTTPException:
100
+ raise
101
  except Exception as e:
102
  raise HTTPException(status_code=500, detail=str(e))
103
 
104
  @app.post("/api/v1/query")
105
  def query(request: QueryRequest):
106
+ """Execute SELECT query on a project"""
107
  try:
108
+ db_path = get_db_path(request.project)
109
+ conn = get_connection(db_path)
110
  cursor = conn.execute(request.sql)
111
  rows = cursor.fetchall()
112
  conn.close()
113
+ return {
114
+ "project": request.project,
115
+ "data": [dict(row) for row in rows],
116
+ "rows": len(rows)
117
+ }
118
  except Exception as e:
119
  raise HTTPException(status_code=400, detail=str(e))
120
 
121
  @app.post("/api/v1/execute")
122
  def execute(request: ExecuteRequest):
123
+ """Execute INSERT/UPDATE/DELETE on a project"""
124
  try:
125
+ db_path = get_db_path(request.project)
126
+ conn = get_connection(db_path)
127
  cursor = conn.execute(request.sql)
128
  conn.commit()
129
+ changes = cursor.rowcount
130
  conn.close()
131
+ return {
132
+ "project": request.project,
133
+ "rows_affected": changes,
134
+ "status": "success"
135
+ }
136
  except Exception as e:
137
  raise HTTPException(status_code=400, detail=str(e))
138
 
139
  @app.get("/api/v1/tables")
140
+ def list_tables(project: str = Query("default")):
141
+ """List all tables in a project"""
142
  try:
143
+ db_path = get_db_path(project)
144
+ conn = get_connection(db_path)
145
  cursor = conn.execute(
146
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
147
  )
148
  tables = [row[0] for row in cursor.fetchall()]
149
  conn.close()
150
+ return {"project": project, "tables": tables, "count": len(tables)}
151
  except Exception as e:
152
  raise HTTPException(status_code=500, detail=str(e))
153
 
154
  @app.get("/api/v1/schema")
155
+ def get_schema(project: str = Query("default")):
156
+ """Get database schema for a project"""
157
  try:
158
+ db_path = get_db_path(project)
159
+ conn = get_connection(db_path)
160
  cursor = conn.execute(
161
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
162
  )
 
170
  for col in cursor.fetchall()
171
  ]
172
  conn.close()
173
+ return {"project": project, "schema": schema}
174
  except Exception as e:
175
  raise HTTPException(status_code=500, detail=str(e))
176
 
177
  if __name__ == "__main__":
178
+ ensure_db_dir()
179
  uvicorn.run(app, host="0.0.0.0", port=8000)
test.js CHANGED
@@ -2,11 +2,10 @@ 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",
@@ -47,37 +46,16 @@ async function apiCall(endpoint, method = "GET", body = null) {
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) {
@@ -85,136 +63,202 @@ async function main() {
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");
 
2
  import("node-fetch").then(({ default: fetch }) => fetch(...args));
3
 
4
  // ============ CONFIG ============
 
5
  const BASE_URL = "https://maxxcarl-sqllite.hf.space";
6
  // For local testing, use: "http://localhost:7860"
7
 
8
+ // ============ COLORS ============
9
  const colors = {
10
  reset: "\x1b[0m",
11
  bright: "\x1b[1m",
 
46
  return data;
47
  }
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  // ============ MAIN TEST FLOW ============
50
  async function main() {
51
  try {
52
+ log(`\nπŸš€ SQLite Multi-Project Tester`, "bright");
53
+ log(`Base URL: ${BASE_URL}\n`, "yellow");
 
54
 
55
  // 1. Health Check
56
  header("1️⃣ HEALTH CHECK");
57
  try {
58
+ const health = await apiCall("/api/v1/heartbeat");
59
  log(`βœ… Server is healthy!`, "green");
60
  log(`Response: ${JSON.stringify(health, null, 2)}\n`, "cyan");
61
  } catch (e) {
 
63
  return;
64
  }
65
 
66
+ // 2. Create Projects
67
+ header("2️⃣ CREATE PROJECTS");
68
+ const projects = ["ecommerce", "blog", "analytics"];
69
+
70
+ for (const projectName of projects) {
71
+ try {
72
+ await apiCall("/api/v1/projects", "POST", {
73
+ name: projectName,
74
+ description: `${projectName} project database`,
75
+ });
76
+ log(`βœ… Created project: ${projectName}`, "green");
77
+ } catch (e) {
78
+ log(`⚠️ Project ${projectName}: ${e.message}`, "yellow");
79
+ }
 
80
  }
81
 
82
+ // 3. Setup E-Commerce Project
83
+ header("3️⃣ ECOMMERCE PROJECT - CREATE TABLES");
84
+ const ecommerceSQL = [
85
+ `CREATE TABLE IF NOT EXISTS products (
86
+ id INTEGER PRIMARY KEY,
87
  name TEXT NOT NULL,
88
  price REAL NOT NULL,
89
+ stock INTEGER
90
+ )`,
91
+ `CREATE TABLE IF NOT EXISTS orders (
92
+ id INTEGER PRIMARY KEY,
93
+ customer_name TEXT NOT NULL,
94
+ total REAL,
95
+ status TEXT DEFAULT 'pending'
96
+ )`,
97
+ ];
98
+
99
+ for (const sql of ecommerceSQL) {
100
+ try {
101
+ await apiCall("/api/v1/execute", "POST", { sql, project: "ecommerce" });
102
+ log(` βœ… Table created`, "green");
103
+ } catch (e) {
104
+ log(` ⚠️ ${e.message}`, "yellow");
105
+ }
106
  }
107
 
108
+ // 4. Insert E-Commerce Data
109
+ header("4️⃣ ECOMMERCE PROJECT - INSERT DATA");
110
+ const ecommerceInserts = [
111
+ `INSERT INTO products (name, price, stock) VALUES ('iPhone 15', 999.99, 50)`,
112
+ `INSERT INTO products (name, price, stock) VALUES ('MacBook Pro', 2499.99, 20)`,
113
+ `INSERT INTO orders (customer_name, total, status) VALUES ('Alice', 999.99, 'shipped')`,
114
+ `INSERT INTO orders (customer_name, total, status) VALUES ('Bob', 3499.98, 'pending')`,
115
  ];
116
 
117
+ for (const sql of ecommerceInserts) {
 
118
  try {
119
+ await apiCall("/api/v1/execute", "POST", { sql, project: "ecommerce" });
120
+ log(` βœ… Data inserted`, "green");
121
  } catch (e) {
122
+ log(` ⚠️ ${e.message}`, "yellow");
123
  }
124
  }
125
 
126
+ // 5. Setup Blog Project
127
+ header("5️⃣ BLOG PROJECT - CREATE TABLES");
128
+ const blogSQL = [
129
+ `CREATE TABLE IF NOT EXISTS posts (
130
+ id INTEGER PRIMARY KEY,
131
+ title TEXT NOT NULL,
132
+ content TEXT,
133
+ author TEXT,
134
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
135
+ )`,
136
+ `CREATE TABLE IF NOT EXISTS comments (
137
+ id INTEGER PRIMARY KEY,
138
+ post_id INTEGER,
139
+ text TEXT,
140
+ author TEXT
141
+ )`,
142
  ];
143
 
144
+ for (const sql of blogSQL) {
 
145
  try {
146
+ await apiCall("/api/v1/execute", "POST", { sql, project: "blog" });
147
+ log(` βœ… Table created`, "green");
148
  } catch (e) {
149
+ log(` ⚠️ ${e.message}`, "yellow");
150
  }
151
  }
152
 
153
+ // 6. Insert Blog Data
154
+ header("6️⃣ BLOG PROJECT - INSERT DATA");
155
+ const blogInserts = [
156
+ `INSERT INTO posts (title, author, content) VALUES ('Getting Started with SQLite', 'Tech Writer', 'SQLite is awesome...')`,
157
+ `INSERT INTO comments (post_id, author, text) VALUES (1, 'Reader1', 'Great article!')`,
158
+ ];
159
+
160
+ for (const sql of blogInserts) {
161
+ try {
162
+ await apiCall("/api/v1/execute", "POST", { sql, project: "blog" });
163
+ log(` βœ… Data inserted`, "green");
164
+ } catch (e) {
165
+ log(` ⚠️ ${e.message}`, "yellow");
166
+ }
167
+ }
168
+
169
+ // 7. Setup Analytics Project
170
+ header("7️⃣ ANALYTICS PROJECT - CREATE TABLE");
171
+ const analyticsSQL = `CREATE TABLE IF NOT EXISTS events (
172
+ id INTEGER PRIMARY KEY,
173
+ event_type TEXT,
174
+ user_id INTEGER,
175
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
176
+ )`;
177
+
178
  try {
179
+ await apiCall("/api/v1/execute", "POST", {
180
+ sql: analyticsSQL,
181
+ project: "analytics",
182
+ });
183
+ log(`βœ… Analytics table created`, "green");
184
  } catch (e) {
185
+ log(`❌ ${e.message}`, "red");
186
  }
187
 
188
+ // 8. List All Projects
189
+ header("8️⃣ LIST ALL PROJECTS");
190
  try {
191
+ const projects = await apiCall("/api/v1/projects");
192
+ log(JSON.stringify(projects, null, 2), "cyan");
193
  } catch (e) {
194
+ log(`❌ Error: ${e.message}`, "red");
195
  }
196
 
197
+ // 9. Query E-Commerce Data
198
+ header("9️⃣ QUERY ECOMMERCE - PRODUCTS");
199
  try {
200
+ const result = await apiCall("/api/v1/query", "POST", {
201
+ sql: "SELECT * FROM products WHERE price > 1000",
202
+ project: "ecommerce",
203
+ });
204
  log(JSON.stringify(result.data, null, 2), "cyan");
205
  } catch (e) {
206
+ log(`❌ Error: ${e.message}`, "red");
207
  }
208
 
209
+ // 10. Query Blog Data
210
+ header("πŸ”Ÿ QUERY BLOG - POSTS WITH COMMENTS");
211
  try {
212
+ const result = await apiCall("/api/v1/query", "POST", {
213
+ sql: `SELECT p.title, p.author, c.text as comment
214
+ FROM posts p LEFT JOIN comments c ON p.id = c.post_id`,
215
+ project: "blog",
216
+ });
217
  log(JSON.stringify(result.data, null, 2), "cyan");
218
  } catch (e) {
219
+ log(`❌ Error: ${e.message}`, "red");
220
+ }
221
+
222
+ // 11. Schema Overview
223
+ header("1️⃣1️⃣ DATABASE ORGANIZATION");
224
+ try {
225
+ log(
226
+ `
227
+ πŸ“ /data/ (Persistent HF Spaces Storage)
228
+ β”œβ”€β”€ ecommerce.db
229
+ β”‚ β”œβ”€β”€ products (id, name, price, stock)
230
+ β”‚ └── orders (id, customer_name, total, status)
231
+ β”œβ”€β”€ blog.db
232
+ β”‚ β”œβ”€β”€ posts (id, title, content, author, created_at)
233
+ β”‚ └── comments (id, post_id, text, author)
234
+ └── analytics.db
235
+ └── events (id, event_type, user_id, timestamp)
236
+
237
+ βœ… Each project is completely isolated
238
+ βœ… Fast queries - data organized by concern
239
+ βœ… Easy backups - one db file per project
240
+ `,
241
+ "cyan"
242
+ );
243
+ } catch (e) {
244
+ log(`❌ Error: ${e.message}`, "red");
245
  }
246
 
247
+ // 12. Summary
248
+ header("βœ… MULTI-PROJECT SETUP COMPLETE");
249
  log(
250
  `
251
+ πŸŽ‰ Three Isolated Projects Created!
 
 
 
 
252
 
253
+ πŸ›’ ECOMMERCE: products, orders
254
+ ✍️ BLOG: posts, comments
255
+ πŸ“Š ANALYTICS: events
256
 
257
+ πŸš€ Fast & Organized Queries
258
+ πŸ’Ύ Persistent Storage: /data/
259
+ πŸ”„ Easy to Scale & Manage
 
260
  `,
261
+ "green"
262
  );
263
  } catch (error) {
264
  log(`\n❌ Fatal Error: ${error.message}`, "red");