openbase-js 0.1.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/index.d.ts +28 -0
- package/index.js +148 -0
- package/package.json +16 -0
- package/test.js +46 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface OpenbaseResponse<T> {
|
|
2
|
+
data: T | null;
|
|
3
|
+
error: string | null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export declare class QueryBuilder<T = Record<string, unknown>> {
|
|
7
|
+
select(columns?: string): this;
|
|
8
|
+
eq(column: string, value: unknown): this;
|
|
9
|
+
limit(n: number): this;
|
|
10
|
+
single(): this;
|
|
11
|
+
insert(data: Partial<T>): this;
|
|
12
|
+
update(data: Partial<T>): this;
|
|
13
|
+
delete(): this;
|
|
14
|
+
then<R>(
|
|
15
|
+
resolve: (value: OpenbaseResponse<T[]>) => R,
|
|
16
|
+
reject?: (reason: unknown) => R
|
|
17
|
+
): Promise<R>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export declare class OpenbaseClient {
|
|
21
|
+
from<T = Record<string, unknown>>(table: string): QueryBuilder<T>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare function createClient(
|
|
25
|
+
baseUrl: string,
|
|
26
|
+
apiKey: string,
|
|
27
|
+
dbName: string
|
|
28
|
+
): OpenbaseClient;
|
package/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
// ─── Query Builder ────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
class QueryBuilder {
|
|
6
|
+
constructor(baseUrl, apiKey, dbName, table) {
|
|
7
|
+
this._baseUrl = baseUrl;
|
|
8
|
+
this._apiKey = apiKey;
|
|
9
|
+
this._dbName = dbName;
|
|
10
|
+
this._table = table;
|
|
11
|
+
this._filters = {};
|
|
12
|
+
this._columns = '*';
|
|
13
|
+
this._limitVal = null;
|
|
14
|
+
this._single = false;
|
|
15
|
+
this._operation = 'select';
|
|
16
|
+
this._insertData = null;
|
|
17
|
+
this._updateData = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── Read ──────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
select(columns = '*') {
|
|
23
|
+
this._columns = columns;
|
|
24
|
+
this._operation = 'select';
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
eq(column, value) {
|
|
29
|
+
this._filters[column] = value;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
limit(n) {
|
|
34
|
+
this._limitVal = n;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
single() {
|
|
39
|
+
this._single = true;
|
|
40
|
+
this._limitVal = 1;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Write ─────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
insert(data) {
|
|
47
|
+
this._operation = 'insert';
|
|
48
|
+
this._insertData = data;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
update(data) {
|
|
53
|
+
this._operation = 'update';
|
|
54
|
+
this._updateData = data;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
delete() {
|
|
59
|
+
this._operation = 'delete';
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Execute ───────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
async _buildAndRun() {
|
|
66
|
+
let sql = '';
|
|
67
|
+
|
|
68
|
+
if (this._operation === 'select') {
|
|
69
|
+
sql = `SELECT ${this._columns} FROM "${this._table}"`;
|
|
70
|
+
const whereParts = Object.entries(this._filters).map(([col, val]) =>
|
|
71
|
+
typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
|
|
72
|
+
);
|
|
73
|
+
if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
|
|
74
|
+
if (this._limitVal) sql += ` LIMIT ${this._limitVal}`;
|
|
75
|
+
|
|
76
|
+
} else if (this._operation === 'insert') {
|
|
77
|
+
const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
|
|
78
|
+
const vals = Object.values(this._insertData).map(v =>
|
|
79
|
+
typeof v === 'string' ? `'${v}'` : v
|
|
80
|
+
).join(', ');
|
|
81
|
+
sql = `INSERT INTO "${this._table}" (${cols}) VALUES (${vals}) RETURNING *`;
|
|
82
|
+
|
|
83
|
+
} else if (this._operation === 'update') {
|
|
84
|
+
const setClauses = Object.entries(this._updateData).map(([col, val]) =>
|
|
85
|
+
typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
|
|
86
|
+
).join(', ');
|
|
87
|
+
const whereParts = Object.entries(this._filters).map(([col, val]) =>
|
|
88
|
+
typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
|
|
89
|
+
);
|
|
90
|
+
sql = `UPDATE "${this._table}" SET ${setClauses}`;
|
|
91
|
+
if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
|
|
92
|
+
sql += ` RETURNING *`;
|
|
93
|
+
|
|
94
|
+
} else if (this._operation === 'delete') {
|
|
95
|
+
const whereParts = Object.entries(this._filters).map(([col, val]) =>
|
|
96
|
+
typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
|
|
97
|
+
);
|
|
98
|
+
sql = `DELETE FROM "${this._table}"`;
|
|
99
|
+
if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
|
|
100
|
+
sql += ` RETURNING *`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const res = await axios.post(
|
|
105
|
+
`${this._baseUrl}/query`,
|
|
106
|
+
{ sql, db_name: this._dbName },
|
|
107
|
+
{ headers: { Authorization: `Bearer ${this._apiKey}` } }
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const { data, error } = res.data;
|
|
111
|
+
if (error) return { data: null, error };
|
|
112
|
+
|
|
113
|
+
if (this._single) {
|
|
114
|
+
return { data: data[0] || null, error: null };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { data, error: null };
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return { data: null, error: err.message };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
then(resolve, reject) {
|
|
124
|
+
return this._buildAndRun().then(resolve, reject);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Client ───────────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
class OpenbaseClient {
|
|
131
|
+
constructor(baseUrl, apiKey, dbName) {
|
|
132
|
+
this._baseUrl = baseUrl.replace(/\/$/, '');
|
|
133
|
+
this._apiKey = apiKey;
|
|
134
|
+
this._dbName = dbName;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
from(table) {
|
|
138
|
+
return new QueryBuilder(this._baseUrl, this._apiKey, this._dbName, table);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
function createClient(baseUrl, apiKey, dbName) {
|
|
145
|
+
return new OpenbaseClient(baseUrl, apiKey, dbName);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { createClient };
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openbase-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "JavaScript client for Openbase — a self-hosted Supabase alternative",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node test.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["openbase", "supabase", "postgres", "database"],
|
|
11
|
+
"author": "Omkar Patil",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"axios": "^1.0.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { createClient } = require('./index');
|
|
2
|
+
|
|
3
|
+
const client = createClient(
|
|
4
|
+
'http://localhost:3002',
|
|
5
|
+
'Yur7U6O5DHzC7Sjtyx_tI7EYM6lpMBO_I9vWWrN4YdQ', // replace with your actual anon key
|
|
6
|
+
'mydb'
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
async function test() {
|
|
10
|
+
console.log('\n── SELECT all ──────────────────────');
|
|
11
|
+
const { data, error } = await client.from('leads').select('*');
|
|
12
|
+
console.log('data:', data);
|
|
13
|
+
console.log('error:', error);
|
|
14
|
+
|
|
15
|
+
console.log('\n── SELECT with .eq ─────────────────');
|
|
16
|
+
const { data: single } = await client
|
|
17
|
+
.from('leads')
|
|
18
|
+
.select('company_name, slug')
|
|
19
|
+
.eq('slug', 'google')
|
|
20
|
+
.single();
|
|
21
|
+
console.log('single:', single);
|
|
22
|
+
|
|
23
|
+
console.log('\n── INSERT ──────────────────────────');
|
|
24
|
+
const { data: inserted } = await client.from('leads').insert({
|
|
25
|
+
company_name: 'Test Co',
|
|
26
|
+
slug: 'test-co',
|
|
27
|
+
custom_pitch: 'Hello from SDK',
|
|
28
|
+
});
|
|
29
|
+
console.log('inserted:', inserted);
|
|
30
|
+
|
|
31
|
+
console.log('\n── UPDATE ──────────────────────────');
|
|
32
|
+
const { data: updated } = await client
|
|
33
|
+
.from('leads')
|
|
34
|
+
.update({ custom_pitch: 'Updated via SDK' })
|
|
35
|
+
.eq('slug', 'test-co');
|
|
36
|
+
console.log('updated:', updated);
|
|
37
|
+
|
|
38
|
+
console.log('\n── DELETE ──────────────────────────');
|
|
39
|
+
const { data: deleted } = await client
|
|
40
|
+
.from('leads')
|
|
41
|
+
.delete()
|
|
42
|
+
.eq('slug', 'test-co');
|
|
43
|
+
console.log('deleted:', deleted);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test().catch(console.error);
|