@sfutureapps/db-sdk 0.3.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 ADDED
@@ -0,0 +1,54 @@
1
+ # @sfuture/db-js
2
+
3
+ Supabase-like JS SDK for ThinkPHP DB Gateway (MySQL)
4
+
5
+ ## Install (local)
6
+ - copy this folder
7
+ - run:
8
+ ```bash
9
+ npm i
10
+ npm run build
11
+ ```
12
+
13
+ ## Create client
14
+ ```ts
15
+ import { createClient } from "@sfuture/db-js";
16
+
17
+ const db = createClient("https://yourdomain.com", {
18
+ accessToken: () => localStorage.getItem("token"),
19
+ });
20
+ ```
21
+
22
+ ## Select + pagination
23
+ ```ts
24
+ const res = await db
25
+ .from("booking_paginate_view")
26
+ .select("*")
27
+ .like("property_name", "%Kampot%")
28
+ .order("createtime", { ascending: false })
29
+ .page(1, 20)
30
+ .withCount()
31
+ .execute();
32
+
33
+ console.log(res.data);
34
+ console.log(res.meta); // { page, limit, offset, total, last_page }
35
+ ```
36
+
37
+ ## Insert / Update / Delete
38
+ ```ts
39
+ await db.from("bookings").insert({ property_id: 1, user_id: 2, total_amount: 50 });
40
+
41
+ await db.from("bookings").eq("id", 123).update({ status: "paid" });
42
+
43
+ await db.from("bookings").eq("id", 123).delete();
44
+ ```
45
+
46
+ ## Aggregate
47
+ ```ts
48
+ const totalPaid = await db.from("bookings").eq("status", "paid").sum("total_amount");
49
+ console.log(totalPaid.data);
50
+ ```
51
+
52
+ ## Count notes
53
+ - `.withCount()` = pagination total rows (meta)
54
+ - `.count()` = aggregate COUNT(*)
@@ -0,0 +1,120 @@
1
+ type FilterOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "like" | "notLike" | "between" | "notBetween" | "in" | "notIn" | "isNull" | "notNull" | "regexp" | "notRegexp" | "time" | "findInSet";
2
+ type Filter = {
3
+ col: string;
4
+ op: FilterOp;
5
+ val: any;
6
+ };
7
+ type Order = {
8
+ col: string;
9
+ dir: "asc" | "desc";
10
+ };
11
+ type DbError = {
12
+ status?: number;
13
+ message: string;
14
+ details?: any;
15
+ };
16
+ type DbResponse<T> = {
17
+ data: T | null;
18
+ error: DbError | null;
19
+ count?: number | null;
20
+ meta?: any;
21
+ };
22
+ type ClientOptions = {
23
+ apiKey?: string;
24
+ accessToken?: () => string | null;
25
+ fetch?: typeof fetch;
26
+ headers?: Record<string, string>;
27
+ };
28
+
29
+ declare class HttpClient {
30
+ private baseUrl;
31
+ private apiKey?;
32
+ private accessToken?;
33
+ private fetcher;
34
+ private extraHeaders;
35
+ constructor(baseUrl: string, opts?: ClientOptions);
36
+ post<T>(path: string, body: any): Promise<DbResponse<T>>;
37
+ }
38
+
39
+ type OrderOpts = {
40
+ ascending?: boolean;
41
+ };
42
+ declare class QueryBuilder<Row = any> {
43
+ private tableName;
44
+ private client;
45
+ private _select;
46
+ private _filters;
47
+ private _order;
48
+ private _limit;
49
+ private _page;
50
+ private _offset;
51
+ private _withCount;
52
+ private _single;
53
+ private _distinct;
54
+ private _group;
55
+ private _having;
56
+ constructor(client: HttpClient, table: string);
57
+ select(columns?: string): this;
58
+ private addFilter;
59
+ eq(col: string, val: any): this;
60
+ neq(col: string, val: any): this;
61
+ gt(col: string, val: any): this;
62
+ gte(col: string, val: any): this;
63
+ lt(col: string, val: any): this;
64
+ lte(col: string, val: any): this;
65
+ like(col: string, val: string): this;
66
+ notLike(col: string, val: string): this;
67
+ between(col: string, range: [any, any]): this;
68
+ notBetween(col: string, range: [any, any]): this;
69
+ in(col: string, vals: any[]): this;
70
+ notIn(col: string, vals: any[]): this;
71
+ isNull(col: string): this;
72
+ notNull(col: string): this;
73
+ regexp(col: string, pattern: string): this;
74
+ notRegexp(col: string, pattern: string): this;
75
+ whereTime(col: string, op: ">" | ">=" | "<" | "<=", time: string): this;
76
+ betweenTime(col: string, range: [string, string]): this;
77
+ findInSet(col: string, val: string | number): this;
78
+ order(col: string, opts?: OrderOpts): this;
79
+ limit(n: number): this;
80
+ page(page: number, limit: number): this;
81
+ range(from: number, to: number): this;
82
+ /** Pagination total rows (not aggregate). */
83
+ withCount(): this;
84
+ single(): this;
85
+ distinct(): this;
86
+ group(cols: string): this;
87
+ havingEq(col: string, val: any): this;
88
+ havingGte(col: string, val: any): this;
89
+ havingLte(col: string, val: any): this;
90
+ havingGt(col: string, val: any): this;
91
+ havingLt(col: string, val: any): this;
92
+ havingNeq(col: string, val: any): this;
93
+ execute(): Promise<DbResponse<Row[] | Row>>;
94
+ private agg;
95
+ /** Aggregate count value (COUNT(*)) */
96
+ count(field?: string): Promise<DbResponse<number>>;
97
+ sum(field: string): Promise<DbResponse<number>>;
98
+ avg(field: string): Promise<DbResponse<number>>;
99
+ min(field: string): Promise<DbResponse<number>>;
100
+ max(field: string): Promise<DbResponse<number>>;
101
+ insert(values: Partial<Row> | Array<Partial<Row>>): Promise<DbResponse<unknown>>;
102
+ update(values: Partial<Row>): Promise<DbResponse<unknown>> | Promise<{
103
+ data: null;
104
+ error: {
105
+ message: string;
106
+ };
107
+ }>;
108
+ delete(): Promise<DbResponse<unknown>> | Promise<{
109
+ data: null;
110
+ error: {
111
+ message: string;
112
+ };
113
+ }>;
114
+ }
115
+
116
+ declare function createClient(baseUrl: string, opts?: ClientOptions): {
117
+ from<Row = any>(table: string): QueryBuilder<Row>;
118
+ };
119
+
120
+ export { type ClientOptions, type DbError, type DbResponse, type Filter, type Order, createClient };
@@ -0,0 +1,120 @@
1
+ type FilterOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "like" | "notLike" | "between" | "notBetween" | "in" | "notIn" | "isNull" | "notNull" | "regexp" | "notRegexp" | "time" | "findInSet";
2
+ type Filter = {
3
+ col: string;
4
+ op: FilterOp;
5
+ val: any;
6
+ };
7
+ type Order = {
8
+ col: string;
9
+ dir: "asc" | "desc";
10
+ };
11
+ type DbError = {
12
+ status?: number;
13
+ message: string;
14
+ details?: any;
15
+ };
16
+ type DbResponse<T> = {
17
+ data: T | null;
18
+ error: DbError | null;
19
+ count?: number | null;
20
+ meta?: any;
21
+ };
22
+ type ClientOptions = {
23
+ apiKey?: string;
24
+ accessToken?: () => string | null;
25
+ fetch?: typeof fetch;
26
+ headers?: Record<string, string>;
27
+ };
28
+
29
+ declare class HttpClient {
30
+ private baseUrl;
31
+ private apiKey?;
32
+ private accessToken?;
33
+ private fetcher;
34
+ private extraHeaders;
35
+ constructor(baseUrl: string, opts?: ClientOptions);
36
+ post<T>(path: string, body: any): Promise<DbResponse<T>>;
37
+ }
38
+
39
+ type OrderOpts = {
40
+ ascending?: boolean;
41
+ };
42
+ declare class QueryBuilder<Row = any> {
43
+ private tableName;
44
+ private client;
45
+ private _select;
46
+ private _filters;
47
+ private _order;
48
+ private _limit;
49
+ private _page;
50
+ private _offset;
51
+ private _withCount;
52
+ private _single;
53
+ private _distinct;
54
+ private _group;
55
+ private _having;
56
+ constructor(client: HttpClient, table: string);
57
+ select(columns?: string): this;
58
+ private addFilter;
59
+ eq(col: string, val: any): this;
60
+ neq(col: string, val: any): this;
61
+ gt(col: string, val: any): this;
62
+ gte(col: string, val: any): this;
63
+ lt(col: string, val: any): this;
64
+ lte(col: string, val: any): this;
65
+ like(col: string, val: string): this;
66
+ notLike(col: string, val: string): this;
67
+ between(col: string, range: [any, any]): this;
68
+ notBetween(col: string, range: [any, any]): this;
69
+ in(col: string, vals: any[]): this;
70
+ notIn(col: string, vals: any[]): this;
71
+ isNull(col: string): this;
72
+ notNull(col: string): this;
73
+ regexp(col: string, pattern: string): this;
74
+ notRegexp(col: string, pattern: string): this;
75
+ whereTime(col: string, op: ">" | ">=" | "<" | "<=", time: string): this;
76
+ betweenTime(col: string, range: [string, string]): this;
77
+ findInSet(col: string, val: string | number): this;
78
+ order(col: string, opts?: OrderOpts): this;
79
+ limit(n: number): this;
80
+ page(page: number, limit: number): this;
81
+ range(from: number, to: number): this;
82
+ /** Pagination total rows (not aggregate). */
83
+ withCount(): this;
84
+ single(): this;
85
+ distinct(): this;
86
+ group(cols: string): this;
87
+ havingEq(col: string, val: any): this;
88
+ havingGte(col: string, val: any): this;
89
+ havingLte(col: string, val: any): this;
90
+ havingGt(col: string, val: any): this;
91
+ havingLt(col: string, val: any): this;
92
+ havingNeq(col: string, val: any): this;
93
+ execute(): Promise<DbResponse<Row[] | Row>>;
94
+ private agg;
95
+ /** Aggregate count value (COUNT(*)) */
96
+ count(field?: string): Promise<DbResponse<number>>;
97
+ sum(field: string): Promise<DbResponse<number>>;
98
+ avg(field: string): Promise<DbResponse<number>>;
99
+ min(field: string): Promise<DbResponse<number>>;
100
+ max(field: string): Promise<DbResponse<number>>;
101
+ insert(values: Partial<Row> | Array<Partial<Row>>): Promise<DbResponse<unknown>>;
102
+ update(values: Partial<Row>): Promise<DbResponse<unknown>> | Promise<{
103
+ data: null;
104
+ error: {
105
+ message: string;
106
+ };
107
+ }>;
108
+ delete(): Promise<DbResponse<unknown>> | Promise<{
109
+ data: null;
110
+ error: {
111
+ message: string;
112
+ };
113
+ }>;
114
+ }
115
+
116
+ declare function createClient(baseUrl: string, opts?: ClientOptions): {
117
+ from<Row = any>(table: string): QueryBuilder<Row>;
118
+ };
119
+
120
+ export { type ClientOptions, type DbError, type DbResponse, type Filter, type Order, createClient };
package/dist/index.js ADDED
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createClient: () => createClient
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/client.ts
28
+ var HttpClient = class {
29
+ constructor(baseUrl, opts = {}) {
30
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
31
+ this.apiKey = opts.apiKey;
32
+ this.accessToken = opts.accessToken;
33
+ this.fetcher = opts.fetch ?? fetch;
34
+ this.extraHeaders = opts.headers ?? {};
35
+ }
36
+ async post(path, body) {
37
+ const headers = {
38
+ "Content-Type": "application/json",
39
+ ...this.extraHeaders
40
+ };
41
+ if (this.apiKey) headers["x-api-key"] = this.apiKey;
42
+ const token = this.accessToken?.();
43
+ if (token) headers["Authorization"] = `Bearer ${token}`;
44
+ const res = await this.fetcher(`${this.baseUrl}${path}`, {
45
+ method: "POST",
46
+ headers,
47
+ body: JSON.stringify(body)
48
+ });
49
+ const text = await res.text();
50
+ if (!res.ok) {
51
+ return { data: null, error: { status: res.status, message: text || res.statusText } };
52
+ }
53
+ try {
54
+ const json = JSON.parse(text || "{}");
55
+ return {
56
+ data: json.data ?? null,
57
+ count: json.count ?? null,
58
+ meta: json.meta ?? null,
59
+ error: json.error ?? null
60
+ };
61
+ } catch {
62
+ return { data: null, error: { status: res.status, message: "Invalid JSON response", details: text } };
63
+ }
64
+ }
65
+ };
66
+
67
+ // src/query-builder.ts
68
+ var QueryBuilder = class {
69
+ constructor(client, table) {
70
+ this._select = ["*"];
71
+ this._filters = [];
72
+ this._order = [];
73
+ this._limit = null;
74
+ this._page = null;
75
+ // ThinkPHP style
76
+ this._offset = null;
77
+ // offset style
78
+ this._withCount = false;
79
+ this._single = false;
80
+ this._distinct = false;
81
+ this._group = [];
82
+ this._having = [];
83
+ this.client = client;
84
+ this.tableName = table;
85
+ }
86
+ // ---- select ----
87
+ select(columns = "*") {
88
+ const cols = columns.trim();
89
+ this._select = cols === "*" || cols === "" ? ["*"] : cols.split(",").map((s) => s.trim()).filter(Boolean);
90
+ return this;
91
+ }
92
+ // ---- filters ----
93
+ addFilter(col, op, val) {
94
+ this._filters.push({ col, op, val });
95
+ return this;
96
+ }
97
+ eq(col, val) {
98
+ return this.addFilter(col, "eq", val);
99
+ }
100
+ neq(col, val) {
101
+ return this.addFilter(col, "neq", val);
102
+ }
103
+ gt(col, val) {
104
+ return this.addFilter(col, "gt", val);
105
+ }
106
+ gte(col, val) {
107
+ return this.addFilter(col, "gte", val);
108
+ }
109
+ lt(col, val) {
110
+ return this.addFilter(col, "lt", val);
111
+ }
112
+ lte(col, val) {
113
+ return this.addFilter(col, "lte", val);
114
+ }
115
+ like(col, val) {
116
+ return this.addFilter(col, "like", val);
117
+ }
118
+ notLike(col, val) {
119
+ return this.addFilter(col, "notLike", val);
120
+ }
121
+ between(col, range) {
122
+ return this.addFilter(col, "between", range);
123
+ }
124
+ notBetween(col, range) {
125
+ return this.addFilter(col, "notBetween", range);
126
+ }
127
+ in(col, vals) {
128
+ return this.addFilter(col, "in", vals);
129
+ }
130
+ notIn(col, vals) {
131
+ return this.addFilter(col, "notIn", vals);
132
+ }
133
+ isNull(col) {
134
+ return this.addFilter(col, "isNull", null);
135
+ }
136
+ notNull(col) {
137
+ return this.addFilter(col, "notNull", null);
138
+ }
139
+ regexp(col, pattern) {
140
+ return this.addFilter(col, "regexp", pattern);
141
+ }
142
+ notRegexp(col, pattern) {
143
+ return this.addFilter(col, "notRegexp", pattern);
144
+ }
145
+ whereTime(col, op, time) {
146
+ return this.addFilter(col, "time", { op, val: time });
147
+ }
148
+ betweenTime(col, range) {
149
+ return this.addFilter(col, "time", { op: "between", val: range });
150
+ }
151
+ findInSet(col, val) {
152
+ return this.addFilter(col, "findInSet", val);
153
+ }
154
+ // ---- order ----
155
+ order(col, opts = {}) {
156
+ this._order.push({ col, dir: opts.ascending === false ? "desc" : "asc" });
157
+ return this;
158
+ }
159
+ // ---- pagination ----
160
+ limit(n) {
161
+ this._limit = Math.max(1, n | 0);
162
+ return this;
163
+ }
164
+ page(page, limit) {
165
+ this._page = Math.max(1, page | 0);
166
+ this._limit = Math.max(1, limit | 0);
167
+ this._offset = null;
168
+ return this;
169
+ }
170
+ range(from, to) {
171
+ const f = Math.max(0, from | 0);
172
+ const t = Math.max(f, to | 0);
173
+ this._offset = f;
174
+ this._limit = t - f + 1;
175
+ this._page = null;
176
+ return this;
177
+ }
178
+ /** Pagination total rows (not aggregate). */
179
+ withCount() {
180
+ this._withCount = true;
181
+ return this;
182
+ }
183
+ single() {
184
+ this._single = true;
185
+ this.limit(1);
186
+ return this;
187
+ }
188
+ // ---- group/having/distinct ----
189
+ distinct() {
190
+ this._distinct = true;
191
+ return this;
192
+ }
193
+ group(cols) {
194
+ this._group = cols.split(",").map((s) => s.trim()).filter(Boolean);
195
+ return this;
196
+ }
197
+ havingEq(col, val) {
198
+ this._having.push({ col, op: "eq", val });
199
+ return this;
200
+ }
201
+ havingGte(col, val) {
202
+ this._having.push({ col, op: "gte", val });
203
+ return this;
204
+ }
205
+ havingLte(col, val) {
206
+ this._having.push({ col, op: "lte", val });
207
+ return this;
208
+ }
209
+ havingGt(col, val) {
210
+ this._having.push({ col, op: "gt", val });
211
+ return this;
212
+ }
213
+ havingLt(col, val) {
214
+ this._having.push({ col, op: "lt", val });
215
+ return this;
216
+ }
217
+ havingNeq(col, val) {
218
+ this._having.push({ col, op: "neq", val });
219
+ return this;
220
+ }
221
+ // ---- execute ----
222
+ async execute() {
223
+ const payload = {
224
+ table: this.tableName,
225
+ select: this._select,
226
+ filters: this._filters,
227
+ order: this._order,
228
+ limit: this._limit ?? 20,
229
+ count: this._withCount,
230
+ distinct: this._distinct,
231
+ group: this._group,
232
+ having: this._having
233
+ };
234
+ if (this._page !== null) payload.page = this._page;
235
+ if (this._offset !== null) payload.offset = this._offset;
236
+ const res = await this.client.post("/db/v1/query", payload);
237
+ if (res.error) return res;
238
+ if (this._single) {
239
+ const first = Array.isArray(res.data) ? res.data[0] ?? null : null;
240
+ return { data: first, error: null, count: res.count ?? null, meta: res.meta ?? null };
241
+ }
242
+ return res;
243
+ }
244
+ // ---- aggregates ----
245
+ agg(fn, field = "*") {
246
+ return this.client.post("/db/v1/query", {
247
+ table: this.tableName,
248
+ aggregate: { fn, field },
249
+ filters: this._filters
250
+ });
251
+ }
252
+ /** Aggregate count value (COUNT(*)) */
253
+ count(field = "*") {
254
+ return this.agg("count", field);
255
+ }
256
+ sum(field) {
257
+ return this.agg("sum", field);
258
+ }
259
+ avg(field) {
260
+ return this.agg("avg", field);
261
+ }
262
+ min(field) {
263
+ return this.agg("min", field);
264
+ }
265
+ max(field) {
266
+ return this.agg("max", field);
267
+ }
268
+ // ---- write ----
269
+ insert(values) {
270
+ return this.client.post("/db/v1/insert", { table: this.tableName, values });
271
+ }
272
+ update(values) {
273
+ if (this._filters.length === 0) {
274
+ return Promise.resolve({ data: null, error: { message: "Update requires filters (e.g. .eq('id', 1))" } });
275
+ }
276
+ return this.client.post("/db/v1/update", { table: this.tableName, values, filters: this._filters });
277
+ }
278
+ delete() {
279
+ if (this._filters.length === 0) {
280
+ return Promise.resolve({ data: null, error: { message: "Delete requires filters (e.g. .eq('id', 1))" } });
281
+ }
282
+ return this.client.post("/db/v1/delete", { table: this.tableName, filters: this._filters });
283
+ }
284
+ };
285
+
286
+ // src/main.ts
287
+ function createClient(baseUrl, opts = {}) {
288
+ const http = new HttpClient(baseUrl, opts);
289
+ return {
290
+ from(table) {
291
+ return new QueryBuilder(http, table);
292
+ }
293
+ };
294
+ }
295
+ // Annotate the CommonJS export names for ESM import in node:
296
+ 0 && (module.exports = {
297
+ createClient
298
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,271 @@
1
+ // src/client.ts
2
+ var HttpClient = class {
3
+ constructor(baseUrl, opts = {}) {
4
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
5
+ this.apiKey = opts.apiKey;
6
+ this.accessToken = opts.accessToken;
7
+ this.fetcher = opts.fetch ?? fetch;
8
+ this.extraHeaders = opts.headers ?? {};
9
+ }
10
+ async post(path, body) {
11
+ const headers = {
12
+ "Content-Type": "application/json",
13
+ ...this.extraHeaders
14
+ };
15
+ if (this.apiKey) headers["x-api-key"] = this.apiKey;
16
+ const token = this.accessToken?.();
17
+ if (token) headers["Authorization"] = `Bearer ${token}`;
18
+ const res = await this.fetcher(`${this.baseUrl}${path}`, {
19
+ method: "POST",
20
+ headers,
21
+ body: JSON.stringify(body)
22
+ });
23
+ const text = await res.text();
24
+ if (!res.ok) {
25
+ return { data: null, error: { status: res.status, message: text || res.statusText } };
26
+ }
27
+ try {
28
+ const json = JSON.parse(text || "{}");
29
+ return {
30
+ data: json.data ?? null,
31
+ count: json.count ?? null,
32
+ meta: json.meta ?? null,
33
+ error: json.error ?? null
34
+ };
35
+ } catch {
36
+ return { data: null, error: { status: res.status, message: "Invalid JSON response", details: text } };
37
+ }
38
+ }
39
+ };
40
+
41
+ // src/query-builder.ts
42
+ var QueryBuilder = class {
43
+ constructor(client, table) {
44
+ this._select = ["*"];
45
+ this._filters = [];
46
+ this._order = [];
47
+ this._limit = null;
48
+ this._page = null;
49
+ // ThinkPHP style
50
+ this._offset = null;
51
+ // offset style
52
+ this._withCount = false;
53
+ this._single = false;
54
+ this._distinct = false;
55
+ this._group = [];
56
+ this._having = [];
57
+ this.client = client;
58
+ this.tableName = table;
59
+ }
60
+ // ---- select ----
61
+ select(columns = "*") {
62
+ const cols = columns.trim();
63
+ this._select = cols === "*" || cols === "" ? ["*"] : cols.split(",").map((s) => s.trim()).filter(Boolean);
64
+ return this;
65
+ }
66
+ // ---- filters ----
67
+ addFilter(col, op, val) {
68
+ this._filters.push({ col, op, val });
69
+ return this;
70
+ }
71
+ eq(col, val) {
72
+ return this.addFilter(col, "eq", val);
73
+ }
74
+ neq(col, val) {
75
+ return this.addFilter(col, "neq", val);
76
+ }
77
+ gt(col, val) {
78
+ return this.addFilter(col, "gt", val);
79
+ }
80
+ gte(col, val) {
81
+ return this.addFilter(col, "gte", val);
82
+ }
83
+ lt(col, val) {
84
+ return this.addFilter(col, "lt", val);
85
+ }
86
+ lte(col, val) {
87
+ return this.addFilter(col, "lte", val);
88
+ }
89
+ like(col, val) {
90
+ return this.addFilter(col, "like", val);
91
+ }
92
+ notLike(col, val) {
93
+ return this.addFilter(col, "notLike", val);
94
+ }
95
+ between(col, range) {
96
+ return this.addFilter(col, "between", range);
97
+ }
98
+ notBetween(col, range) {
99
+ return this.addFilter(col, "notBetween", range);
100
+ }
101
+ in(col, vals) {
102
+ return this.addFilter(col, "in", vals);
103
+ }
104
+ notIn(col, vals) {
105
+ return this.addFilter(col, "notIn", vals);
106
+ }
107
+ isNull(col) {
108
+ return this.addFilter(col, "isNull", null);
109
+ }
110
+ notNull(col) {
111
+ return this.addFilter(col, "notNull", null);
112
+ }
113
+ regexp(col, pattern) {
114
+ return this.addFilter(col, "regexp", pattern);
115
+ }
116
+ notRegexp(col, pattern) {
117
+ return this.addFilter(col, "notRegexp", pattern);
118
+ }
119
+ whereTime(col, op, time) {
120
+ return this.addFilter(col, "time", { op, val: time });
121
+ }
122
+ betweenTime(col, range) {
123
+ return this.addFilter(col, "time", { op: "between", val: range });
124
+ }
125
+ findInSet(col, val) {
126
+ return this.addFilter(col, "findInSet", val);
127
+ }
128
+ // ---- order ----
129
+ order(col, opts = {}) {
130
+ this._order.push({ col, dir: opts.ascending === false ? "desc" : "asc" });
131
+ return this;
132
+ }
133
+ // ---- pagination ----
134
+ limit(n) {
135
+ this._limit = Math.max(1, n | 0);
136
+ return this;
137
+ }
138
+ page(page, limit) {
139
+ this._page = Math.max(1, page | 0);
140
+ this._limit = Math.max(1, limit | 0);
141
+ this._offset = null;
142
+ return this;
143
+ }
144
+ range(from, to) {
145
+ const f = Math.max(0, from | 0);
146
+ const t = Math.max(f, to | 0);
147
+ this._offset = f;
148
+ this._limit = t - f + 1;
149
+ this._page = null;
150
+ return this;
151
+ }
152
+ /** Pagination total rows (not aggregate). */
153
+ withCount() {
154
+ this._withCount = true;
155
+ return this;
156
+ }
157
+ single() {
158
+ this._single = true;
159
+ this.limit(1);
160
+ return this;
161
+ }
162
+ // ---- group/having/distinct ----
163
+ distinct() {
164
+ this._distinct = true;
165
+ return this;
166
+ }
167
+ group(cols) {
168
+ this._group = cols.split(",").map((s) => s.trim()).filter(Boolean);
169
+ return this;
170
+ }
171
+ havingEq(col, val) {
172
+ this._having.push({ col, op: "eq", val });
173
+ return this;
174
+ }
175
+ havingGte(col, val) {
176
+ this._having.push({ col, op: "gte", val });
177
+ return this;
178
+ }
179
+ havingLte(col, val) {
180
+ this._having.push({ col, op: "lte", val });
181
+ return this;
182
+ }
183
+ havingGt(col, val) {
184
+ this._having.push({ col, op: "gt", val });
185
+ return this;
186
+ }
187
+ havingLt(col, val) {
188
+ this._having.push({ col, op: "lt", val });
189
+ return this;
190
+ }
191
+ havingNeq(col, val) {
192
+ this._having.push({ col, op: "neq", val });
193
+ return this;
194
+ }
195
+ // ---- execute ----
196
+ async execute() {
197
+ const payload = {
198
+ table: this.tableName,
199
+ select: this._select,
200
+ filters: this._filters,
201
+ order: this._order,
202
+ limit: this._limit ?? 20,
203
+ count: this._withCount,
204
+ distinct: this._distinct,
205
+ group: this._group,
206
+ having: this._having
207
+ };
208
+ if (this._page !== null) payload.page = this._page;
209
+ if (this._offset !== null) payload.offset = this._offset;
210
+ const res = await this.client.post("/db/v1/query", payload);
211
+ if (res.error) return res;
212
+ if (this._single) {
213
+ const first = Array.isArray(res.data) ? res.data[0] ?? null : null;
214
+ return { data: first, error: null, count: res.count ?? null, meta: res.meta ?? null };
215
+ }
216
+ return res;
217
+ }
218
+ // ---- aggregates ----
219
+ agg(fn, field = "*") {
220
+ return this.client.post("/db/v1/query", {
221
+ table: this.tableName,
222
+ aggregate: { fn, field },
223
+ filters: this._filters
224
+ });
225
+ }
226
+ /** Aggregate count value (COUNT(*)) */
227
+ count(field = "*") {
228
+ return this.agg("count", field);
229
+ }
230
+ sum(field) {
231
+ return this.agg("sum", field);
232
+ }
233
+ avg(field) {
234
+ return this.agg("avg", field);
235
+ }
236
+ min(field) {
237
+ return this.agg("min", field);
238
+ }
239
+ max(field) {
240
+ return this.agg("max", field);
241
+ }
242
+ // ---- write ----
243
+ insert(values) {
244
+ return this.client.post("/db/v1/insert", { table: this.tableName, values });
245
+ }
246
+ update(values) {
247
+ if (this._filters.length === 0) {
248
+ return Promise.resolve({ data: null, error: { message: "Update requires filters (e.g. .eq('id', 1))" } });
249
+ }
250
+ return this.client.post("/db/v1/update", { table: this.tableName, values, filters: this._filters });
251
+ }
252
+ delete() {
253
+ if (this._filters.length === 0) {
254
+ return Promise.resolve({ data: null, error: { message: "Delete requires filters (e.g. .eq('id', 1))" } });
255
+ }
256
+ return this.client.post("/db/v1/delete", { table: this.tableName, filters: this._filters });
257
+ }
258
+ };
259
+
260
+ // src/main.ts
261
+ function createClient(baseUrl, opts = {}) {
262
+ const http = new HttpClient(baseUrl, opts);
263
+ return {
264
+ from(table) {
265
+ return new QueryBuilder(http, table);
266
+ }
267
+ };
268
+ }
269
+ export {
270
+ createClient
271
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@sfutureapps/db-sdk",
3
+ "version": "0.3.0",
4
+ "description": "SfutureApps JS SDK for ThinkPHP DB Gateway (MySQL)",
5
+ "license": "MIT",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm --dts",
21
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
22
+ },
23
+ "devDependencies": {
24
+ "tsup": "^8.0.0",
25
+ "typescript": "^5.4.0"
26
+ },
27
+ "keywords": [
28
+ "thinkphp",
29
+ "mysql",
30
+ "sdk",
31
+ "database",
32
+ "rest",
33
+ "orm",
34
+ "js",
35
+ "typescript",
36
+ "sfutureApps"
37
+ ]
38
+ }