@cloud-cli/d0 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/client.mjs +18 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +68 -0
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +2 -0
- package/package.json +30 -0
- package/src/index.ts +90 -0
- package/src/serve.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# d0
|
|
2
|
+
|
|
3
|
+
SQLite server over HTTP
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
**POST /query**
|
|
8
|
+
|
|
9
|
+
Run a prepared SQLite statement.
|
|
10
|
+
Send a JSON with two properties: `s` for statement and `d` for data (optional)
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
// using fetch
|
|
14
|
+
fetch('https://db.example.com/query', {
|
|
15
|
+
body: JSON.stringify({
|
|
16
|
+
s: 'SELECT * FROM user WHERE id = ?',
|
|
17
|
+
d: [123]
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// using the server-provided library
|
|
22
|
+
import db from 'https://db.example.com/index.mjs';
|
|
23
|
+
|
|
24
|
+
const user = await db.query('SELECT * FROM user WHERE id = ?', [123]);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Environment variables
|
|
28
|
+
|
|
29
|
+
| Variable | Description |
|
|
30
|
+
|-|-|
|
|
31
|
+
| PORT | HTTP port |
|
|
32
|
+
| SQLITE_DB_PATH | Path to file where the database files are stored |
|
package/client.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const baseURL = 'https://__API_URL__';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
async query(statement, data) {
|
|
5
|
+
const req = await fetch(new URL('/query', baseURL), {
|
|
6
|
+
body: JSON.stringify({
|
|
7
|
+
s: statement,
|
|
8
|
+
d: data || null,
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (req.ok) {
|
|
13
|
+
return await req.json();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
throw new Error(await req.text());
|
|
17
|
+
},
|
|
18
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import { Database } from 'better-sqlite3';
|
|
4
|
+
export declare function getDatabase(): Database;
|
|
5
|
+
export declare function serve(): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import SQLite from 'better-sqlite3';
|
|
4
|
+
export function getDatabase() {
|
|
5
|
+
const db = new SQLite(process.env.SQLITE_DB_PATH || './db.sqlite3');
|
|
6
|
+
db.pragma('journal_mode = WAL');
|
|
7
|
+
return db;
|
|
8
|
+
}
|
|
9
|
+
export function serve() {
|
|
10
|
+
const db = getDatabase();
|
|
11
|
+
const server = createServer((request, response) => {
|
|
12
|
+
const url = new URL(request.url, 'http://localhost');
|
|
13
|
+
const route = `${request.method} ${url.pathname}`.trim();
|
|
14
|
+
const args = Object.fromEntries(url.searchParams.entries());
|
|
15
|
+
const q = { db, request, response, args };
|
|
16
|
+
switch (route) {
|
|
17
|
+
case 'GET /index.mjs':
|
|
18
|
+
return onEsModule(q);
|
|
19
|
+
case 'POST /query':
|
|
20
|
+
return onQuery(q);
|
|
21
|
+
default:
|
|
22
|
+
response.writeHead(404).end();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
server.listen(process.env.PORT);
|
|
26
|
+
return server;
|
|
27
|
+
}
|
|
28
|
+
async function onQuery({ db, request, response }) {
|
|
29
|
+
const query = await readStream(request);
|
|
30
|
+
if (!query.length) {
|
|
31
|
+
response.writeHead(400).end();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const { s, d } = JSON.parse(query);
|
|
36
|
+
const result = db.prepare(s).run(d);
|
|
37
|
+
response.end(JSON.stringify(result));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
process.env.DEBUG && console.error(error);
|
|
41
|
+
response.writeHead(400).end(String(error));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let file;
|
|
45
|
+
async function getFile() {
|
|
46
|
+
if (!file) {
|
|
47
|
+
file = await readFile('./client.mjs', 'utf8');
|
|
48
|
+
}
|
|
49
|
+
return file;
|
|
50
|
+
}
|
|
51
|
+
async function onEsModule({ request, response }) {
|
|
52
|
+
const hostname = request.headers['x-forwarded-for'];
|
|
53
|
+
const code = await getFile();
|
|
54
|
+
response
|
|
55
|
+
.writeHead(200, {
|
|
56
|
+
'Content-Type': 'text/javascript',
|
|
57
|
+
'Access-Control-Allow-Origin': '*',
|
|
58
|
+
})
|
|
59
|
+
.end(code.replace('__API_URL__', hostname));
|
|
60
|
+
}
|
|
61
|
+
function readStream(stream) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const all = [];
|
|
64
|
+
stream.on('data', (c) => all.push(c));
|
|
65
|
+
stream.on('end', () => resolve(Buffer.concat(all).toString('utf8')));
|
|
66
|
+
stream.on('error', reject);
|
|
67
|
+
});
|
|
68
|
+
}
|
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/serve.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cloud-cli/d0",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "echo true",
|
|
10
|
+
"start": "node dist/serve.js"
|
|
11
|
+
},
|
|
12
|
+
"prettier": "@cloud-cli/prettier-config",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/cloud-cli/d0.git"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"better-sqlite3": "^8.2.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@cloud-cli/prettier-config": "^1.0.0",
|
|
25
|
+
"@cloud-cli/typescript-config": "^1.0.0",
|
|
26
|
+
"@types/better-sqlite3": "^7.6.3",
|
|
27
|
+
"@types/node": "^20.3.3",
|
|
28
|
+
"typescript": "^5.4.3"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createServer, IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import SQLite, { Database } from 'better-sqlite3';
|
|
4
|
+
|
|
5
|
+
type Q = {
|
|
6
|
+
db: Database;
|
|
7
|
+
request: IncomingMessage;
|
|
8
|
+
response: ServerResponse;
|
|
9
|
+
args: Record<string, string>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function getDatabase(): Database {
|
|
13
|
+
const db = new SQLite(process.env.SQLITE_DB_PATH || './db.sqlite3');
|
|
14
|
+
db.pragma('journal_mode = WAL');
|
|
15
|
+
return db;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function serve() {
|
|
19
|
+
const db = getDatabase();
|
|
20
|
+
|
|
21
|
+
const server = createServer((request, response) => {
|
|
22
|
+
const url = new URL(request.url, 'http://localhost');
|
|
23
|
+
const route = `${request.method} ${url.pathname}`.trim();
|
|
24
|
+
const args = Object.fromEntries(url.searchParams.entries()) as Q['args'];
|
|
25
|
+
const q: Q = { db, request, response, args };
|
|
26
|
+
|
|
27
|
+
switch (route) {
|
|
28
|
+
case 'GET /index.mjs':
|
|
29
|
+
return onEsModule(q);
|
|
30
|
+
|
|
31
|
+
case 'POST /query':
|
|
32
|
+
return onQuery(q);
|
|
33
|
+
|
|
34
|
+
default:
|
|
35
|
+
response.writeHead(404).end();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
server.listen(process.env.PORT);
|
|
40
|
+
|
|
41
|
+
return server;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function onQuery({ db, request, response }: Q) {
|
|
45
|
+
const query = await readStream(request);
|
|
46
|
+
|
|
47
|
+
if (!query.length) {
|
|
48
|
+
response.writeHead(400).end();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const { s, d } = JSON.parse(query);
|
|
54
|
+
const result = db.prepare(s).run(d);
|
|
55
|
+
response.end(JSON.stringify(result));
|
|
56
|
+
} catch (error) {
|
|
57
|
+
process.env.DEBUG && console.error(error);
|
|
58
|
+
response.writeHead(400).end(String(error));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let file;
|
|
63
|
+
async function getFile() {
|
|
64
|
+
if (!file) {
|
|
65
|
+
file = await readFile('./client.mjs', 'utf8');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return file;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function onEsModule({ request, response }: Q) {
|
|
72
|
+
const hostname = request.headers['x-forwarded-for'];
|
|
73
|
+
const code = await getFile();
|
|
74
|
+
|
|
75
|
+
response
|
|
76
|
+
.writeHead(200, {
|
|
77
|
+
'Content-Type': 'text/javascript',
|
|
78
|
+
'Access-Control-Allow-Origin': '*',
|
|
79
|
+
})
|
|
80
|
+
.end(code.replace('__API_URL__', hostname));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readStream(stream): Promise<string> {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const all = [];
|
|
86
|
+
stream.on('data', (c) => all.push(c));
|
|
87
|
+
stream.on('end', () => resolve(Buffer.concat(all).toString('utf8')));
|
|
88
|
+
stream.on('error', reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
package/src/serve.ts
ADDED