openbase-js 0.1.3 → 0.1.4

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 (4) hide show
  1. package/index.cjs +110 -15
  2. package/index.d.ts +10 -0
  3. package/index.js +110 -14
  4. package/package.json +1 -1
package/index.cjs CHANGED
@@ -6,9 +6,12 @@ class QueryBuilder {
6
6
  this._apiKey = apiKey;
7
7
  this._dbName = dbName;
8
8
  this._table = table;
9
- this._filters = {};
9
+ this._filters = []; // changed: was {} now array of {col, op, val}
10
10
  this._columns = '*';
11
11
  this._limitVal = null;
12
+ this._offsetVal = null; // new
13
+ this._orderCol = null; // new
14
+ this._orderAsc = true; // new
12
15
  this._single = false;
13
16
  this._operation = 'select';
14
17
  this._insertData = null;
@@ -21,8 +24,59 @@ class QueryBuilder {
21
24
  return this;
22
25
  }
23
26
 
27
+ // ─── Filter operators ──────────────────────────────────────────────────────
28
+
24
29
  eq(column, value) {
25
- this._filters[column] = value;
30
+ this._filters.push({ col: column, op: 'eq', val: value });
31
+ return this;
32
+ }
33
+
34
+ gt(column, value) {
35
+ this._filters.push({ col: column, op: 'gt', val: value });
36
+ return this;
37
+ }
38
+
39
+ lt(column, value) {
40
+ this._filters.push({ col: column, op: 'lt', val: value });
41
+ return this;
42
+ }
43
+
44
+ gte(column, value) {
45
+ this._filters.push({ col: column, op: 'gte', val: value });
46
+ return this;
47
+ }
48
+
49
+ lte(column, value) {
50
+ this._filters.push({ col: column, op: 'lte', val: value });
51
+ return this;
52
+ }
53
+
54
+ like(column, pattern) {
55
+ this._filters.push({ col: column, op: 'like', val: pattern });
56
+ return this;
57
+ }
58
+
59
+ ilike(column, pattern) {
60
+ this._filters.push({ col: column, op: 'ilike', val: pattern });
61
+ return this;
62
+ }
63
+
64
+ in(column, values) {
65
+ this._filters.push({ col: column, op: 'in', val: values });
66
+ return this;
67
+ }
68
+
69
+ is(column, value) {
70
+ // value should be null, true, or false
71
+ this._filters.push({ col: column, op: 'is', val: value });
72
+ return this;
73
+ }
74
+
75
+ // ─── Modifiers ─────────────────────────────────────────────────────────────
76
+
77
+ order(column, { ascending = true } = {}) {
78
+ this._orderCol = column;
79
+ this._orderAsc = ascending;
26
80
  return this;
27
81
  }
28
82
 
@@ -31,12 +85,21 @@ class QueryBuilder {
31
85
  return this;
32
86
  }
33
87
 
88
+ range(from, to) {
89
+ // e.g. range(0, 9) => LIMIT 10 OFFSET 0
90
+ this._limitVal = to - from + 1;
91
+ this._offsetVal = from;
92
+ return this;
93
+ }
94
+
34
95
  single() {
35
96
  this._single = true;
36
97
  this._limitVal = 1;
37
98
  return this;
38
99
  }
39
100
 
101
+ // ─── Mutation operators ────────────────────────────────────────────────────
102
+
40
103
  insert(data) {
41
104
  this._operation = 'insert';
42
105
  this._insertData = data;
@@ -54,16 +117,47 @@ class QueryBuilder {
54
117
  return this;
55
118
  }
56
119
 
120
+ // ─── SQL Builder ───────────────────────────────────────────────────────────
121
+
122
+ _filterToSQL(f) {
123
+ const col = `"${f.col}"`;
124
+ switch (f.op) {
125
+ case 'eq': return typeof f.val === 'string' ? `${col} = '${f.val}'` : `${col} = ${f.val}`;
126
+ case 'gt': return `${col} > ${f.val}`;
127
+ case 'lt': return `${col} < ${f.val}`;
128
+ case 'gte': return `${col} >= ${f.val}`;
129
+ case 'lte': return `${col} <= ${f.val}`;
130
+ case 'like': return `${col} LIKE '${f.val}'`;
131
+ case 'ilike': return `${col} ILIKE '${f.val}'`;
132
+ case 'in': {
133
+ const list = f.val.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ');
134
+ return `${col} IN (${list})`;
135
+ }
136
+ case 'is': {
137
+ if (f.val === null) return `${col} IS NULL`;
138
+ if (f.val === true) return `${col} IS TRUE`;
139
+ if (f.val === false) return `${col} IS FALSE`;
140
+ return `${col} IS NULL`;
141
+ }
142
+ default: return `${col} = '${f.val}'`;
143
+ }
144
+ }
145
+
57
146
  _buildSQL() {
58
147
  let sql = '';
59
148
 
60
149
  if (this._operation === 'select') {
61
150
  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 ')}`;
151
+
152
+ if (this._filters.length) {
153
+ const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
154
+ sql += ` WHERE ${where}`;
155
+ }
156
+ if (this._orderCol) {
157
+ sql += ` ORDER BY "${this._orderCol}" ${this._orderAsc ? 'ASC' : 'DESC'}`;
158
+ }
66
159
  if (this._limitVal) sql += ` LIMIT ${this._limitVal}`;
160
+ if (this._offsetVal) sql += ` OFFSET ${this._offsetVal}`;
67
161
 
68
162
  } else if (this._operation === 'insert') {
69
163
  const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
@@ -76,25 +170,27 @@ class QueryBuilder {
76
170
  const setClauses = Object.entries(this._updateData).map(([col, val]) =>
77
171
  typeof val === 'string' ? `"${col}" = '${val.replace(/'/g, "''")}'` : `"${col}" = ${val}`
78
172
  ).join(', ');
79
- const whereParts = Object.entries(this._filters).map(([col, val]) =>
80
- typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
81
- );
82
173
  sql = `UPDATE "${this._table}" SET ${setClauses}`;
83
- if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
174
+ if (this._filters.length) {
175
+ const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
176
+ sql += ` WHERE ${where}`;
177
+ }
84
178
  sql += ` RETURNING *`;
85
179
 
86
180
  } 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
181
  sql = `DELETE FROM "${this._table}"`;
91
- if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
182
+ if (this._filters.length) {
183
+ const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
184
+ sql += ` WHERE ${where}`;
185
+ }
92
186
  sql += ` RETURNING *`;
93
187
  }
94
188
 
95
189
  return sql;
96
190
  }
97
191
 
192
+ // ─── Executor ──────────────────────────────────────────────────────────────
193
+
98
194
  async _buildAndRun() {
99
195
  const sql = this._buildSQL();
100
196
  try {
@@ -152,5 +248,4 @@ if (typeof module !== 'undefined') {
152
248
  if (typeof window !== 'undefined') {
153
249
  window.openbase = { createClient };
154
250
  }
155
-
156
251
  module.exports = { createClient };
package/index.d.ts CHANGED
@@ -6,6 +6,16 @@ export interface OpenbaseResponse<T> {
6
6
  export declare class QueryBuilder<T = Record<string, unknown>> {
7
7
  select(columns?: string): this;
8
8
  eq(column: string, value: unknown): this;
9
+ gt(column: string, value: number): this;
10
+ lt(column: string, value: number): this;
11
+ gte(column: string, value: number): this;
12
+ lte(column: string, value: number): this;
13
+ like(column: string, pattern: string): this;
14
+ ilike(column: string, pattern: string): this;
15
+ in(column: string, values: unknown[]): this;
16
+ is(column: string, value: null | boolean): this;
17
+ order(column: string, options?: { ascending?: boolean }): this;
18
+ range(from: number, to: number): this;
9
19
  limit(n: number): this;
10
20
  single(): this;
11
21
  insert(data: Partial<T>): this;
package/index.js CHANGED
@@ -6,9 +6,12 @@ class QueryBuilder {
6
6
  this._apiKey = apiKey;
7
7
  this._dbName = dbName;
8
8
  this._table = table;
9
- this._filters = {};
9
+ this._filters = []; // changed: was {} now array of {col, op, val}
10
10
  this._columns = '*';
11
11
  this._limitVal = null;
12
+ this._offsetVal = null; // new
13
+ this._orderCol = null; // new
14
+ this._orderAsc = true; // new
12
15
  this._single = false;
13
16
  this._operation = 'select';
14
17
  this._insertData = null;
@@ -21,8 +24,59 @@ class QueryBuilder {
21
24
  return this;
22
25
  }
23
26
 
27
+ // ─── Filter operators ──────────────────────────────────────────────────────
28
+
24
29
  eq(column, value) {
25
- this._filters[column] = value;
30
+ this._filters.push({ col: column, op: 'eq', val: value });
31
+ return this;
32
+ }
33
+
34
+ gt(column, value) {
35
+ this._filters.push({ col: column, op: 'gt', val: value });
36
+ return this;
37
+ }
38
+
39
+ lt(column, value) {
40
+ this._filters.push({ col: column, op: 'lt', val: value });
41
+ return this;
42
+ }
43
+
44
+ gte(column, value) {
45
+ this._filters.push({ col: column, op: 'gte', val: value });
46
+ return this;
47
+ }
48
+
49
+ lte(column, value) {
50
+ this._filters.push({ col: column, op: 'lte', val: value });
51
+ return this;
52
+ }
53
+
54
+ like(column, pattern) {
55
+ this._filters.push({ col: column, op: 'like', val: pattern });
56
+ return this;
57
+ }
58
+
59
+ ilike(column, pattern) {
60
+ this._filters.push({ col: column, op: 'ilike', val: pattern });
61
+ return this;
62
+ }
63
+
64
+ in(column, values) {
65
+ this._filters.push({ col: column, op: 'in', val: values });
66
+ return this;
67
+ }
68
+
69
+ is(column, value) {
70
+ // value should be null, true, or false
71
+ this._filters.push({ col: column, op: 'is', val: value });
72
+ return this;
73
+ }
74
+
75
+ // ─── Modifiers ─────────────────────────────────────────────────────────────
76
+
77
+ order(column, { ascending = true } = {}) {
78
+ this._orderCol = column;
79
+ this._orderAsc = ascending;
26
80
  return this;
27
81
  }
28
82
 
@@ -31,12 +85,21 @@ class QueryBuilder {
31
85
  return this;
32
86
  }
33
87
 
88
+ range(from, to) {
89
+ // e.g. range(0, 9) => LIMIT 10 OFFSET 0
90
+ this._limitVal = to - from + 1;
91
+ this._offsetVal = from;
92
+ return this;
93
+ }
94
+
34
95
  single() {
35
96
  this._single = true;
36
97
  this._limitVal = 1;
37
98
  return this;
38
99
  }
39
100
 
101
+ // ─── Mutation operators ────────────────────────────────────────────────────
102
+
40
103
  insert(data) {
41
104
  this._operation = 'insert';
42
105
  this._insertData = data;
@@ -54,16 +117,47 @@ class QueryBuilder {
54
117
  return this;
55
118
  }
56
119
 
120
+ // ─── SQL Builder ───────────────────────────────────────────────────────────
121
+
122
+ _filterToSQL(f) {
123
+ const col = `"${f.col}"`;
124
+ switch (f.op) {
125
+ case 'eq': return typeof f.val === 'string' ? `${col} = '${f.val}'` : `${col} = ${f.val}`;
126
+ case 'gt': return `${col} > ${f.val}`;
127
+ case 'lt': return `${col} < ${f.val}`;
128
+ case 'gte': return `${col} >= ${f.val}`;
129
+ case 'lte': return `${col} <= ${f.val}`;
130
+ case 'like': return `${col} LIKE '${f.val}'`;
131
+ case 'ilike': return `${col} ILIKE '${f.val}'`;
132
+ case 'in': {
133
+ const list = f.val.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ');
134
+ return `${col} IN (${list})`;
135
+ }
136
+ case 'is': {
137
+ if (f.val === null) return `${col} IS NULL`;
138
+ if (f.val === true) return `${col} IS TRUE`;
139
+ if (f.val === false) return `${col} IS FALSE`;
140
+ return `${col} IS NULL`;
141
+ }
142
+ default: return `${col} = '${f.val}'`;
143
+ }
144
+ }
145
+
57
146
  _buildSQL() {
58
147
  let sql = '';
59
148
 
60
149
  if (this._operation === 'select') {
61
150
  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 ')}`;
151
+
152
+ if (this._filters.length) {
153
+ const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
154
+ sql += ` WHERE ${where}`;
155
+ }
156
+ if (this._orderCol) {
157
+ sql += ` ORDER BY "${this._orderCol}" ${this._orderAsc ? 'ASC' : 'DESC'}`;
158
+ }
66
159
  if (this._limitVal) sql += ` LIMIT ${this._limitVal}`;
160
+ if (this._offsetVal) sql += ` OFFSET ${this._offsetVal}`;
67
161
 
68
162
  } else if (this._operation === 'insert') {
69
163
  const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
@@ -76,25 +170,27 @@ class QueryBuilder {
76
170
  const setClauses = Object.entries(this._updateData).map(([col, val]) =>
77
171
  typeof val === 'string' ? `"${col}" = '${val.replace(/'/g, "''")}'` : `"${col}" = ${val}`
78
172
  ).join(', ');
79
- const whereParts = Object.entries(this._filters).map(([col, val]) =>
80
- typeof val === 'string' ? `"${col}" = '${val}'` : `"${col}" = ${val}`
81
- );
82
173
  sql = `UPDATE "${this._table}" SET ${setClauses}`;
83
- if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
174
+ if (this._filters.length) {
175
+ const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
176
+ sql += ` WHERE ${where}`;
177
+ }
84
178
  sql += ` RETURNING *`;
85
179
 
86
180
  } 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
181
  sql = `DELETE FROM "${this._table}"`;
91
- if (whereParts.length) sql += ` WHERE ${whereParts.join(' AND ')}`;
182
+ if (this._filters.length) {
183
+ const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
184
+ sql += ` WHERE ${where}`;
185
+ }
92
186
  sql += ` RETURNING *`;
93
187
  }
94
188
 
95
189
  return sql;
96
190
  }
97
191
 
192
+ // ─── Executor ──────────────────────────────────────────────────────────────
193
+
98
194
  async _buildAndRun() {
99
195
  const sql = this._buildSQL();
100
196
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbase-js",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "JavaScript client for Openbase — a self-hosted Supabase alternative",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",