@rebasepro/client 0.2.1 → 0.2.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.
- package/dist/admin.d.ts +0 -3
- package/dist/auth.d.ts +3 -1
- package/dist/collection.d.ts +2 -2
- package/dist/index.d.ts +8 -5
- package/dist/index.es.js +16 -116
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +25 -124
- package/dist/index.umd.js.map +1 -1
- package/dist/query_builder.d.ts +1 -53
- package/package.json +4 -3
- package/src/admin.ts +2 -3
- package/src/auth.ts +3 -1
- package/src/collection.ts +2 -2
- package/src/index.ts +14 -5
- package/src/query_builder.ts +1 -125
- package/src/transport.ts +17 -9
package/dist/query_builder.d.ts
CHANGED
|
@@ -1,53 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { CollectionClient } from "./collection";
|
|
3
|
-
export type FilterOperator = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "cs" | "csa" | "==" | "!=" | ">" | ">=" | "<" | "<=" | "array-contains" | "array-contains-any" | "not-in";
|
|
4
|
-
export declare class QueryBuilder<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
5
|
-
private collection;
|
|
6
|
-
private params;
|
|
7
|
-
constructor(collection: CollectionClient<M>);
|
|
8
|
-
/**
|
|
9
|
-
* Add a filter condition to your query.
|
|
10
|
-
* @example
|
|
11
|
-
* client.collection('users').where('age', '>=', 18).find()
|
|
12
|
-
*/
|
|
13
|
-
where(column: keyof M & string, operator: FilterOperator, value: unknown): this;
|
|
14
|
-
/**
|
|
15
|
-
* Order the results by a specific column.
|
|
16
|
-
* @example
|
|
17
|
-
* client.collection('users').orderBy('createdAt', 'desc').find()
|
|
18
|
-
*/
|
|
19
|
-
orderBy(column: keyof M & string, ascending?: "asc" | "desc"): this;
|
|
20
|
-
/**
|
|
21
|
-
* Limit the number of results returned.
|
|
22
|
-
*/
|
|
23
|
-
limit(count: number): this;
|
|
24
|
-
/**
|
|
25
|
-
* Skip the first N results.
|
|
26
|
-
*/
|
|
27
|
-
offset(count: number): this;
|
|
28
|
-
/**
|
|
29
|
-
* Set a free-text search string if supported by the backend.
|
|
30
|
-
*/
|
|
31
|
-
search(searchString: string): this;
|
|
32
|
-
/**
|
|
33
|
-
* Include related entities in the response.
|
|
34
|
-
* Relations will be populated with full entity data instead of just IDs.
|
|
35
|
-
*
|
|
36
|
-
* @param relations - Relation names to include, or "*" for all.
|
|
37
|
-
* @example
|
|
38
|
-
* // Include specific relations
|
|
39
|
-
* client.data.posts.include("tags", "author").find()
|
|
40
|
-
*
|
|
41
|
-
* // Include all relations
|
|
42
|
-
* client.data.posts.include("*").find()
|
|
43
|
-
*/
|
|
44
|
-
include(...relations: string[]): this;
|
|
45
|
-
/**
|
|
46
|
-
* Execute the find query and return the results.
|
|
47
|
-
*/
|
|
48
|
-
find(): Promise<FindResponse<M>>;
|
|
49
|
-
/**
|
|
50
|
-
* Listen to realtime updates matching this query.
|
|
51
|
-
*/
|
|
52
|
-
listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;
|
|
53
|
-
}
|
|
1
|
+
export { QueryBuilder } from "@rebasepro/common";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"description": "HTTP SDK client for the Rebase custom backend",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
"./package.json": "./package.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@rebasepro/
|
|
34
|
-
"@rebasepro/
|
|
33
|
+
"@rebasepro/common": "0.2.4",
|
|
34
|
+
"@rebasepro/types": "0.2.4",
|
|
35
|
+
"@rebasepro/utils": "0.2.4"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@jest/globals": "^29.7.0",
|
package/src/admin.ts
CHANGED
|
@@ -16,7 +16,6 @@ export interface RebaseRole {
|
|
|
16
16
|
name: string;
|
|
17
17
|
isAdmin: boolean;
|
|
18
18
|
defaultPermissions: Record<string, unknown> | null;
|
|
19
|
-
config: Record<string, unknown> | null;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
export interface CreateAdminOptions {
|
|
@@ -76,14 +75,14 @@ export function createAdmin(transport: Transport, options?: CreateAdminOptions)
|
|
|
76
75
|
return transport.request<{ role: RebaseRole }>(adminPath + "/roles/" + encodeURIComponent(roleId), { method: "GET" });
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
async function createRole(data: { id: string, name: string, isAdmin?: boolean, defaultPermissions?: Record<string, unknown
|
|
78
|
+
async function createRole(data: { id: string, name: string, isAdmin?: boolean, defaultPermissions?: Record<string, unknown> }) {
|
|
80
79
|
return transport.request<{ role: RebaseRole }>(adminPath + "/roles", {
|
|
81
80
|
method: "POST",
|
|
82
81
|
body: JSON.stringify(data)
|
|
83
82
|
});
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
async function updateRole(roleId: string, data: { name?: string, isAdmin?: boolean, defaultPermissions?: Record<string, unknown
|
|
85
|
+
async function updateRole(roleId: string, data: { name?: string, isAdmin?: boolean, defaultPermissions?: Record<string, unknown> }) {
|
|
87
86
|
return transport.request<{ role: RebaseRole }>(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
88
87
|
method: "PUT",
|
|
89
88
|
body: JSON.stringify(data)
|
package/src/auth.ts
CHANGED
|
@@ -29,7 +29,9 @@ export type AuthChangeEvent = "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "
|
|
|
29
29
|
export interface AuthConfig {
|
|
30
30
|
needsSetup: boolean;
|
|
31
31
|
registrationEnabled: boolean;
|
|
32
|
-
emailServiceEnabled
|
|
32
|
+
emailServiceEnabled?: boolean;
|
|
33
|
+
passwordReset?: boolean;
|
|
34
|
+
emailVerification?: boolean;
|
|
33
35
|
enabledProviders: string[];
|
|
34
36
|
}
|
|
35
37
|
|
package/src/collection.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Transport, FindParams, buildQueryString } from "./transport";
|
|
2
2
|
import { RebaseWebSocketClient } from "./websocket";
|
|
3
|
-
import { Entity, FilterValues, WhereFilterOp, CollectionAccessor, WhereFieldValue, FindResponse } from "@rebasepro/types";
|
|
3
|
+
import { Entity, FilterValues, WhereFilterOp, CollectionAccessor, WhereFieldValue, FindResponse, FilterOperator } from "@rebasepro/types";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { QueryBuilder } from "./query_builder";
|
|
6
6
|
|
|
7
7
|
function parseWhereFilter(where?: Record<string, WhereFieldValue>): FilterValues<string> | undefined {
|
|
8
8
|
if (!where) return undefined;
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./auth";
|
|
|
11
11
|
export * from "./admin";
|
|
12
12
|
export * from "./cron";
|
|
13
13
|
export * from "./collection";
|
|
14
|
+
export * from "./query_builder";
|
|
14
15
|
export * from "./websocket";
|
|
15
16
|
export * from "./storage";
|
|
16
17
|
export * from "./reviver";
|
|
@@ -24,9 +25,15 @@ export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
|
24
25
|
|
|
25
26
|
import { RebaseWebSocketClient } from "./websocket";
|
|
26
27
|
import { RebaseClient as BaseRebaseClient, RebaseData, CollectionAccessor, StorageSource } from "@rebasepro/types";
|
|
28
|
+
export type { Entity, FindResponse } from "@rebasepro/types";
|
|
27
29
|
import { toSnakeCase } from "@rebasepro/utils";
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
type KebabToCamelCase<S extends string> =
|
|
32
|
+
S extends `${infer T}-${infer U}`
|
|
33
|
+
? `${T}${Capitalize<KebabToCamelCase<U>>}`
|
|
34
|
+
: S;
|
|
35
|
+
|
|
36
|
+
export type RebaseClient<DB = Record<string, unknown>> = Omit<BaseRebaseClient<DB>, "data"> & {
|
|
30
37
|
setToken: (token: string | null) => void;
|
|
31
38
|
setAuthTokenGetter: (getter: () => Promise<string | null>) => void;
|
|
32
39
|
setOnUnauthorized: (handler: () => Promise<boolean>) => void;
|
|
@@ -38,15 +45,17 @@ export type RebaseClient<DB = Record<string, unknown>> = BaseRebaseClient<DB> &
|
|
|
38
45
|
ws?: RebaseWebSocketClient;
|
|
39
46
|
storage?: StorageSource;
|
|
40
47
|
call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
|
|
41
|
-
data:
|
|
42
|
-
collection<
|
|
43
|
-
|
|
48
|
+
data: {
|
|
49
|
+
collection<S extends string>(slug: S): CollectionClient<
|
|
50
|
+
KebabToCamelCase<S> extends keyof DB
|
|
51
|
+
? (DB[KebabToCamelCase<S>] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>)
|
|
52
|
+
: Record<string, unknown>
|
|
44
53
|
>;
|
|
45
54
|
} & {
|
|
46
55
|
[K in keyof DB]: CollectionClient<
|
|
47
56
|
DB[K] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>
|
|
48
57
|
>;
|
|
49
|
-
};
|
|
58
|
+
} & RebaseData;
|
|
50
59
|
};
|
|
51
60
|
|
|
52
61
|
import { createStorage } from "./storage";
|
package/src/query_builder.ts
CHANGED
|
@@ -1,125 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { CollectionClient } from "./collection";
|
|
3
|
-
|
|
4
|
-
export type FilterOperator =
|
|
5
|
-
| "eq" | "neq" | "gt" | "gte" | "lt" | "lte"
|
|
6
|
-
| "in" | "nin" | "cs" | "csa"
|
|
7
|
-
| "==" | "!=" | ">" | ">=" | "<" | "<="
|
|
8
|
-
| "array-contains" | "array-contains-any"
|
|
9
|
-
| "not-in";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Maps standard operators to Rebase backend's string-based operators
|
|
13
|
-
*/
|
|
14
|
-
function mapOperator(op: FilterOperator): string {
|
|
15
|
-
switch (op) {
|
|
16
|
-
case "==": return "eq";
|
|
17
|
-
case "!=": return "neq";
|
|
18
|
-
case ">": return "gt";
|
|
19
|
-
case ">=": return "gte";
|
|
20
|
-
case "<": return "lt";
|
|
21
|
-
case "<=": return "lte";
|
|
22
|
-
case "array-contains": return "cs";
|
|
23
|
-
case "array-contains-any": return "csa";
|
|
24
|
-
case "not-in": return "nin";
|
|
25
|
-
default: return op;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class QueryBuilder<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
30
|
-
private params: FindParams = { where: {} };
|
|
31
|
-
|
|
32
|
-
constructor(private collection: CollectionClient<M>) {}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Add a filter condition to your query.
|
|
36
|
-
* @example
|
|
37
|
-
* client.collection('users').where('age', '>=', 18).find()
|
|
38
|
-
*/
|
|
39
|
-
where(column: keyof M & string, operator: FilterOperator, value: unknown): this {
|
|
40
|
-
if (!this.params.where) {
|
|
41
|
-
this.params.where = {};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const mappedOp = mapOperator(operator);
|
|
45
|
-
let formattedValue = value;
|
|
46
|
-
|
|
47
|
-
// Handle arrays for in, nin, cs, csa
|
|
48
|
-
if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
|
|
49
|
-
formattedValue = `(${value.join(",")})`;
|
|
50
|
-
} else if (value === null) {
|
|
51
|
-
formattedValue = "null";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
55
|
-
return this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Order the results by a specific column.
|
|
60
|
-
* @example
|
|
61
|
-
* client.collection('users').orderBy('createdAt', 'desc').find()
|
|
62
|
-
*/
|
|
63
|
-
orderBy(column: keyof M & string, ascending: "asc" | "desc" = "asc"): this {
|
|
64
|
-
this.params.orderBy = `${column}:${ascending}`;
|
|
65
|
-
return this;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Limit the number of results returned.
|
|
70
|
-
*/
|
|
71
|
-
limit(count: number): this {
|
|
72
|
-
this.params.limit = count;
|
|
73
|
-
return this;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Skip the first N results.
|
|
78
|
-
*/
|
|
79
|
-
offset(count: number): this {
|
|
80
|
-
this.params.offset = count;
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Set a free-text search string if supported by the backend.
|
|
86
|
-
*/
|
|
87
|
-
search(searchString: string): this {
|
|
88
|
-
this.params.searchString = searchString;
|
|
89
|
-
return this;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Include related entities in the response.
|
|
94
|
-
* Relations will be populated with full entity data instead of just IDs.
|
|
95
|
-
*
|
|
96
|
-
* @param relations - Relation names to include, or "*" for all.
|
|
97
|
-
* @example
|
|
98
|
-
* // Include specific relations
|
|
99
|
-
* client.data.posts.include("tags", "author").find()
|
|
100
|
-
*
|
|
101
|
-
* // Include all relations
|
|
102
|
-
* client.data.posts.include("*").find()
|
|
103
|
-
*/
|
|
104
|
-
include(...relations: string[]): this {
|
|
105
|
-
this.params.include = relations;
|
|
106
|
-
return this;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Execute the find query and return the results.
|
|
111
|
-
*/
|
|
112
|
-
async find(): Promise<FindResponse<M>> {
|
|
113
|
-
return this.collection.find(this.params) as Promise<FindResponse<M>>;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Listen to realtime updates matching this query.
|
|
118
|
-
*/
|
|
119
|
-
listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void {
|
|
120
|
-
if (!this.collection.listen) {
|
|
121
|
-
throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
122
|
-
}
|
|
123
|
-
return this.collection.listen(this.params, onUpdate, onError);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
1
|
+
export { QueryBuilder } from "@rebasepro/common";
|
package/src/transport.ts
CHANGED
|
@@ -169,15 +169,23 @@ headers });
|
|
|
169
169
|
if (res.status === 204) return undefined as T; // SAFETY: HTTP 204 No Content has no body
|
|
170
170
|
|
|
171
171
|
const text = await res.text().catch(() => "");
|
|
172
|
-
let body:
|
|
172
|
+
let body: Record<string, unknown> = {};
|
|
173
173
|
if (text) {
|
|
174
174
|
try {
|
|
175
|
-
body = JSON.parse(text, rebaseReviver)
|
|
175
|
+
body = JSON.parse(text, rebaseReviver) as Record<string, unknown>;
|
|
176
176
|
} catch (e) {
|
|
177
177
|
// If not valid JSON, fallback
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
const getErrorField = (obj: Record<string, unknown>, field: string): unknown => {
|
|
182
|
+
const err = obj?.error;
|
|
183
|
+
if (err && typeof err === "object" && err !== null && field in (err as Record<string, unknown>)) {
|
|
184
|
+
return (err as Record<string, unknown>)[field];
|
|
185
|
+
}
|
|
186
|
+
return obj?.[field];
|
|
187
|
+
};
|
|
188
|
+
|
|
181
189
|
if (res.status === 401 && onUnauthorizedHandler) {
|
|
182
190
|
const retried = await onUnauthorizedHandler();
|
|
183
191
|
if (retried) {
|
|
@@ -195,7 +203,7 @@ headers });
|
|
|
195
203
|
headers: retryHeaders });
|
|
196
204
|
if (retryRes.status === 204) return undefined as T; // SAFETY: HTTP 204 No Content has no body
|
|
197
205
|
const retryText = await retryRes.text().catch(() => "");
|
|
198
|
-
let retryBody:
|
|
206
|
+
let retryBody: Record<string, unknown> = {};
|
|
199
207
|
if (retryText) {
|
|
200
208
|
try {
|
|
201
209
|
retryBody = JSON.parse(retryText, rebaseReviver);
|
|
@@ -209,9 +217,9 @@ headers: retryHeaders });
|
|
|
209
217
|
}
|
|
210
218
|
throw new RebaseApiError(
|
|
211
219
|
retryRes.status,
|
|
212
|
-
retryBody
|
|
213
|
-
retryBody
|
|
214
|
-
retryBody
|
|
220
|
+
String(getErrorField(retryBody, "message") || fallbackMessage || `Request failed with status ${retryRes.status}`),
|
|
221
|
+
getErrorField(retryBody, "code") as string | undefined,
|
|
222
|
+
getErrorField(retryBody, "details")
|
|
215
223
|
);
|
|
216
224
|
}
|
|
217
225
|
return retryBody as T;
|
|
@@ -226,9 +234,9 @@ headers: retryHeaders });
|
|
|
226
234
|
}
|
|
227
235
|
throw new RebaseApiError(
|
|
228
236
|
res.status,
|
|
229
|
-
body
|
|
230
|
-
body
|
|
231
|
-
body
|
|
237
|
+
String(getErrorField(body, "message") || fallbackMessage || `Request failed with status ${res.status}`),
|
|
238
|
+
getErrorField(body, "code") as string | undefined,
|
|
239
|
+
getErrorField(body, "details")
|
|
232
240
|
);
|
|
233
241
|
}
|
|
234
242
|
|