openbase-js 0.1.1 → 0.1.3

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.
Files changed (3) hide show
  1. package/index.cjs +156 -0
  2. package/index.js +30 -24
  3. package/package.json +1 -1
package/index.cjs ADDED
@@ -0,0 +1,156 @@
1
+ // ─── Query Builder ────────────────────────────────────────────────────────────
2
+
3
+ class QueryBuilder {
4
+ constructor(baseUrl, apiKey, dbName, table) {
5
+ this._baseUrl = baseUrl;
6
+ this._apiKey = apiKey;
7
+ this._dbName = dbName;
8
+ this._table = table;
9
+ this._filters = {};
10
+ this._columns = '*';
11
+ this._limitVal = null;
12
+ this._single = false;
13
+ this._operation = 'select';
14
+ this._insertData = null;
15
+ this._updateData = null;
16
+ }
17
+
18
+ select(columns = '*') {
19
+ this._columns = columns;
20
+ this._operation = 'select';
21
+ return this;
22
+ }
23
+
24
+ eq(column, value) {
25
+ this._filters[column] = value;
26
+ return this;
27
+ }
28
+
29
+ limit(n) {
30
+ this._limitVal = n;
31
+ return this;
32
+ }
33
+
34
+ single() {
35
+ this._single = true;
36
+ this._limitVal = 1;
37
+ return this;
38
+ }
39
+
40
+ insert(data) {
41
+ this._operation = 'insert';
42
+ this._insertData = data;
43
+ return this;
44
+ }
45
+
46
+ update(data) {
47
+ this._operation = 'update';
48
+ this._updateData = data;
49
+ return this;
50
+ }
51
+
52
+ delete() {
53
+ this._operation = 'delete';
54
+ return this;
55
+ }
56
+
57
+ _buildSQL() {
58
+ let sql = '';
59
+
60
+ if (this._operation === 'select') {
61
+ sql = `SELECT ${this._columns} FROM "${this._table}"`;
62
+ const whereParts = Object.entries(this._filters).map(([col, val]) =>
63
+ typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
64
+ );
65
+ if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
66
+ if (this._limitVal) sql += ` LIMIT ${this._limitVal}`;
67
+
68
+ } else if (this._operation === 'insert') {
69
+ const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
70
+ const vals = Object.values(this._insertData).map(v =>
71
+ typeof v === 'string' ? `'${v.replace(/'/g, "''")}'` : v
72
+ ).join(', ');
73
+ sql = `INSERT INTO "${this._table}" (${cols}) VALUES (${vals}) RETURNING *`;
74
+
75
+ } else if (this._operation === 'update') {
76
+ const setClauses = Object.entries(this._updateData).map(([col, val]) =>
77
+ typeof val === 'string' ? `"${col}" = '${val.replace(/'/g, "''")}'` : `"${col}" = ${val}`
78
+ ).join(', ');
79
+ const whereParts = Object.entries(this._filters).map(([col, val]) =>
80
+ typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
81
+ );
82
+ sql = `UPDATE "${this._table}" SET ${setClauses}`;
83
+ if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
84
+ sql += ` RETURNING *`;
85
+
86
+ } else if (this._operation === 'delete') {
87
+ const whereParts = Object.entries(this._filters).map(([col, val]) =>
88
+ typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
89
+ );
90
+ sql = `DELETE FROM "${this._table}"`;
91
+ if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
92
+ sql += ` RETURNING *`;
93
+ }
94
+
95
+ return sql;
96
+ }
97
+
98
+ async _buildAndRun() {
99
+ const sql = this._buildSQL();
100
+ try {
101
+ const res = await fetch(`${this._baseUrl}/query`, {
102
+ method: 'POST',
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ 'Authorization': `Bearer ${this._apiKey}`,
106
+ },
107
+ body: JSON.stringify({ sql, db_name: this._dbName }),
108
+ });
109
+
110
+ const json = await res.json();
111
+ const { data, error } = json;
112
+ if (error) return { data: null, error };
113
+ if (this._single) return { data: data[0] || null, error: null };
114
+ return { data, error: null };
115
+ } catch (err) {
116
+ return { data: null, error: err.message };
117
+ }
118
+ }
119
+
120
+ then(resolve, reject) {
121
+ return this._buildAndRun().then(resolve, reject);
122
+ }
123
+ }
124
+
125
+ // ─── Client ───────────────────────────────────────────────────────────────────
126
+
127
+ class OpenbaseClient {
128
+ constructor(baseUrl, apiKey, dbName) {
129
+ this._baseUrl = baseUrl.replace(/\/$/, '');
130
+ this._apiKey = apiKey;
131
+ this._dbName = dbName;
132
+ }
133
+
134
+ from(table) {
135
+ return new QueryBuilder(this._baseUrl, this._apiKey, this._dbName, table);
136
+ }
137
+ }
138
+
139
+ // ─── Factory ──────────────────────────────────────────────────────────────────
140
+
141
+ function createClient(baseUrl, apiKey, dbName) {
142
+ if (!baseUrl) throw new Error('[openbase] Missing baseUrl. Pass your backend URL as the first argument.');
143
+ if (!apiKey) throw new Error('[openbase] Missing apiKey. Pass your anon or service key as the second argument.');
144
+ if (!dbName) throw new Error('[openbase] Missing dbName. Pass your database name as the third argument.');
145
+ return new OpenbaseClient(baseUrl, apiKey, dbName);
146
+ }
147
+
148
+ if (typeof module !== 'undefined') {
149
+ module.exports = { createClient };
150
+ }
151
+
152
+ if (typeof window !== 'undefined') {
153
+ window.openbase = { createClient };
154
+ }
155
+
156
+ module.exports = { createClient };
package/index.js CHANGED
@@ -1,5 +1,3 @@
1
- const axios = require('axios').default || require('axios');
2
-
3
1
  // ─── Query Builder ────────────────────────────────────────────────────────────
4
2
 
5
3
  class QueryBuilder {
@@ -17,8 +15,6 @@ class QueryBuilder {
17
15
  this._updateData = null;
18
16
  }
19
17
 
20
- // ─── Read ──────────────────────────────────────────────────────────────────
21
-
22
18
  select(columns = '*') {
23
19
  this._columns = columns;
24
20
  this._operation = 'select';
@@ -41,8 +37,6 @@ class QueryBuilder {
41
37
  return this;
42
38
  }
43
39
 
44
- // ─── Write ─────────────────────────────────────────────────────────────────
45
-
46
40
  insert(data) {
47
41
  this._operation = 'insert';
48
42
  this._insertData = data;
@@ -60,9 +54,7 @@ class QueryBuilder {
60
54
  return this;
61
55
  }
62
56
 
63
- // ─── Execute ───────────────────────────────────────────────────────────────
64
-
65
- async _buildAndRun() {
57
+ _buildSQL() {
66
58
  let sql = '';
67
59
 
68
60
  if (this._operation === 'select') {
@@ -76,13 +68,13 @@ class QueryBuilder {
76
68
  } else if (this._operation === 'insert') {
77
69
  const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
78
70
  const vals = Object.values(this._insertData).map(v =>
79
- typeof v === 'string' ? `'${v}'` : v
71
+ typeof v === 'string' ? `'${v.replace(/'/g, "''")}'` : v
80
72
  ).join(', ');
81
73
  sql = `INSERT INTO "${this._table}" (${cols}) VALUES (${vals}) RETURNING *`;
82
74
 
83
75
  } else if (this._operation === 'update') {
84
76
  const setClauses = Object.entries(this._updateData).map(([col, val]) =>
85
- typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
77
+ typeof val === 'string' ? `"${col}" = '${val.replace(/'/g, "''")}'` : `"${col}" = ${val}`
86
78
  ).join(', ');
87
79
  const whereParts = Object.entries(this._filters).map(([col, val]) =>
88
80
  typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
@@ -100,20 +92,25 @@ class QueryBuilder {
100
92
  sql += ` RETURNING *`;
101
93
  }
102
94
 
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
- );
95
+ return sql;
96
+ }
109
97
 
110
- const { data, error } = res.data;
98
+ async _buildAndRun() {
99
+ const sql = this._buildSQL();
100
+ try {
101
+ const res = await fetch(`${this._baseUrl}/query`, {
102
+ method: 'POST',
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ 'Authorization': `Bearer ${this._apiKey}`,
106
+ },
107
+ body: JSON.stringify({ sql, db_name: this._dbName }),
108
+ });
109
+
110
+ const json = await res.json();
111
+ const { data, error } = json;
111
112
  if (error) return { data: null, error };
112
-
113
- if (this._single) {
114
- return { data: data[0] || null, error: null };
115
- }
116
-
113
+ if (this._single) return { data: data[0] || null, error: null };
117
114
  return { data, error: null };
118
115
  } catch (err) {
119
116
  return { data: null, error: err.message };
@@ -142,7 +139,16 @@ class OpenbaseClient {
142
139
  // ─── Factory ──────────────────────────────────────────────────────────────────
143
140
 
144
141
  function createClient(baseUrl, apiKey, dbName) {
142
+ if (!baseUrl) throw new Error('[openbase] Missing baseUrl. Pass your backend URL as the first argument.');
143
+ if (!apiKey) throw new Error('[openbase] Missing apiKey. Pass your anon or service key as the second argument.');
144
+ if (!dbName) throw new Error('[openbase] Missing dbName. Pass your database name as the third argument.');
145
145
  return new OpenbaseClient(baseUrl, apiKey, dbName);
146
146
  }
147
147
 
148
- module.exports = { createClient };
148
+ if (typeof module !== 'undefined') {
149
+ module.exports = { createClient };
150
+ }
151
+
152
+ if (typeof window !== 'undefined') {
153
+ window.openbase = { createClient };
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbase-js",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "JavaScript client for Openbase — a self-hosted Supabase alternative",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",