@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 +54 -0
- package/dist/index.d.mts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +298 -0
- package/dist/index.mjs +271 -0
- package/package.json +38 -0
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(*)
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|