@rebasepro/common 0.3.0 → 0.4.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/dist/collections/default-collections.d.ts +5 -8
- package/dist/data/query_builder.d.ts +6 -2
- package/dist/index.es.js +142 -67
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +142 -67
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -3
- package/src/collections/default-collections.ts +62 -30
- package/src/data/buildRebaseData.ts +28 -8
- package/src/data/query_builder.ts +37 -27
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/common",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"fast-equals": "6.0.0",
|
|
43
43
|
"json-logic-js": "^2.0.5",
|
|
44
|
-
"@rebasepro/types": "0.
|
|
45
|
-
"@rebasepro/utils": "0.
|
|
44
|
+
"@rebasepro/types": "0.4.0",
|
|
45
|
+
"@rebasepro/utils": "0.4.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@jest/globals": "^29.7.0",
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { PostgresCollection } from "@rebasepro/types";
|
|
1
|
+
import type { PostgresCollection } from "@rebasepro/types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Default users collection
|
|
4
|
+
* Default users collection.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* (Map keyed by slug, last-write-wins) so that developer-defined
|
|
10
|
-
* collections with the same slug override this default — no hardcoded
|
|
11
|
-
* string checks required.
|
|
6
|
+
* Prepended to the developer's collections array by the admin and server.
|
|
7
|
+
* Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
|
|
8
|
+
* override by defining their own collection with `slug: "users"`.
|
|
12
9
|
*/
|
|
13
10
|
export const defaultUsersCollection: PostgresCollection = {
|
|
14
11
|
name: "Users",
|
|
@@ -18,62 +15,97 @@ export const defaultUsersCollection: PostgresCollection = {
|
|
|
18
15
|
schema: "rebase",
|
|
19
16
|
icon: "Users",
|
|
20
17
|
group: "Settings",
|
|
18
|
+
openEntityMode: "dialog",
|
|
19
|
+
disableDefaultActions: ["copy"],
|
|
20
|
+
securityRules: [
|
|
21
|
+
{ operation: "select", roles: ["admin"] },
|
|
22
|
+
{ operations: ["insert", "update", "delete"], roles: ["admin"] }
|
|
23
|
+
],
|
|
24
|
+
sort: ["createdAt", "desc"],
|
|
21
25
|
properties: {
|
|
22
26
|
id: {
|
|
23
27
|
name: "ID",
|
|
24
28
|
type: "string",
|
|
25
|
-
isId: "uuid"
|
|
29
|
+
isId: "uuid",
|
|
30
|
+
ui: { readOnly: true }
|
|
26
31
|
},
|
|
27
32
|
email: {
|
|
28
33
|
name: "Email",
|
|
29
34
|
type: "string",
|
|
30
35
|
validation: { required: true, unique: true }
|
|
31
36
|
},
|
|
32
|
-
|
|
33
|
-
name: "
|
|
37
|
+
displayName: {
|
|
38
|
+
name: "Name",
|
|
34
39
|
type: "string",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
display_name: {
|
|
38
|
-
name: "Display Name",
|
|
39
|
-
type: "string"
|
|
40
|
+
columnName: "display_name",
|
|
41
|
+
validation: { required: true }
|
|
40
42
|
},
|
|
41
|
-
|
|
43
|
+
photoURL: {
|
|
42
44
|
name: "Photo URL",
|
|
43
|
-
type: "string"
|
|
45
|
+
type: "string",
|
|
46
|
+
columnName: "photo_url",
|
|
47
|
+
url: "image"
|
|
48
|
+
},
|
|
49
|
+
roles: {
|
|
50
|
+
name: "Roles",
|
|
51
|
+
type: "array",
|
|
52
|
+
columnType: "text[]",
|
|
53
|
+
of: {
|
|
54
|
+
name: "Role",
|
|
55
|
+
type: "string",
|
|
56
|
+
enum: {
|
|
57
|
+
admin: "Admin",
|
|
58
|
+
editor: "Editor",
|
|
59
|
+
viewer: "Viewer"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
passwordHash: {
|
|
64
|
+
name: "Password Hash",
|
|
65
|
+
type: "string",
|
|
66
|
+
columnName: "password_hash",
|
|
67
|
+
ui: { hideFromCollection: true, disabled: { hidden: true } }
|
|
44
68
|
},
|
|
45
|
-
|
|
69
|
+
emailVerified: {
|
|
46
70
|
name: "Email Verified",
|
|
47
71
|
type: "boolean",
|
|
48
|
-
|
|
72
|
+
columnName: "email_verified",
|
|
73
|
+
defaultValue: false,
|
|
74
|
+
ui: { hideFromCollection: true, disabled: { hidden: true } }
|
|
49
75
|
},
|
|
50
|
-
|
|
76
|
+
emailVerificationToken: {
|
|
51
77
|
name: "Email Verification Token",
|
|
52
78
|
type: "string",
|
|
53
|
-
|
|
79
|
+
columnName: "email_verification_token",
|
|
80
|
+
ui: { hideFromCollection: true, disabled: { hidden: true } }
|
|
54
81
|
},
|
|
55
|
-
|
|
82
|
+
emailVerificationSentAt: {
|
|
56
83
|
name: "Email Verification Sent At",
|
|
57
84
|
type: "date",
|
|
58
|
-
|
|
85
|
+
columnName: "email_verification_sent_at",
|
|
86
|
+
ui: { hideFromCollection: true, disabled: { hidden: true } }
|
|
59
87
|
},
|
|
60
88
|
metadata: {
|
|
61
89
|
name: "Metadata",
|
|
62
90
|
type: "map",
|
|
63
91
|
defaultValue: {},
|
|
64
|
-
ui: { hideFromCollection: true }
|
|
92
|
+
ui: { hideFromCollection: true, disabled: { hidden: true } }
|
|
65
93
|
},
|
|
66
|
-
|
|
94
|
+
createdAt: {
|
|
67
95
|
name: "Created At",
|
|
68
96
|
type: "date",
|
|
97
|
+
columnName: "created_at",
|
|
69
98
|
autoValue: "on_create",
|
|
70
|
-
ui: { readOnly: true
|
|
99
|
+
ui: { readOnly: true }
|
|
71
100
|
},
|
|
72
|
-
|
|
101
|
+
updatedAt: {
|
|
73
102
|
name: "Updated At",
|
|
74
103
|
type: "date",
|
|
104
|
+
columnName: "updated_at",
|
|
75
105
|
autoValue: "on_update",
|
|
76
|
-
ui: {
|
|
106
|
+
ui: { hideFromCollection: true, disabled: { hidden: true } }
|
|
77
107
|
}
|
|
78
|
-
}
|
|
108
|
+
},
|
|
109
|
+
listProperties: ["displayName", "email", "roles", "createdAt"],
|
|
110
|
+
propertiesOrder: ["id", "email", "displayName", "roles", "createdAt"]
|
|
79
111
|
};
|
|
@@ -8,7 +8,10 @@ import {
|
|
|
8
8
|
EntityValues,
|
|
9
9
|
FilterValues,
|
|
10
10
|
WhereFilterOp,
|
|
11
|
-
WhereFieldValue
|
|
11
|
+
WhereFieldValue,
|
|
12
|
+
WhereFilterOpShort,
|
|
13
|
+
LogicalCondition,
|
|
14
|
+
WhereValue
|
|
12
15
|
} from "@rebasepro/types";
|
|
13
16
|
import { toSnakeCase } from "@rebasepro/utils";
|
|
14
17
|
import { QueryBuilder } from "./query_builder";
|
|
@@ -69,11 +72,18 @@ function convertWhereToFilter(where?: Record<string, WhereFieldValue>): FilterVa
|
|
|
69
72
|
continue;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
// Handle tuple
|
|
73
|
-
if (Array.isArray(rawValue)
|
|
74
|
-
const [
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// Handle tuple or array of tuples
|
|
76
|
+
if (Array.isArray(rawValue)) {
|
|
77
|
+
const conditions: [WhereFilterOpShort, unknown][] = Array.isArray(rawValue[0])
|
|
78
|
+
? (rawValue as [WhereFilterOpShort, unknown][])
|
|
79
|
+
: [rawValue as [WhereFilterOpShort, unknown]];
|
|
80
|
+
|
|
81
|
+
const mappedConditions: [WhereFilterOp, unknown][] = conditions.map(([rawOp, val]) => {
|
|
82
|
+
const mappedOp = operatorMap[rawOp] ?? "==";
|
|
83
|
+
return [mappedOp, val];
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
filter[field] = Array.isArray(rawValue[0]) ? mappedConditions : mappedConditions[0];
|
|
77
87
|
continue;
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -190,6 +200,12 @@ values: {} as Record<string, unknown> }
|
|
|
190
200
|
});
|
|
191
201
|
},
|
|
192
202
|
|
|
203
|
+
deleteAll: driver.deleteAll
|
|
204
|
+
? async (): Promise<void> => {
|
|
205
|
+
return driver.deleteAll!(slug);
|
|
206
|
+
}
|
|
207
|
+
: undefined,
|
|
208
|
+
|
|
193
209
|
count: driver.countEntities
|
|
194
210
|
? async (params?: FindParams): Promise<number> => {
|
|
195
211
|
return driver.countEntities!({
|
|
@@ -238,8 +254,12 @@ values: {} as Record<string, unknown> }
|
|
|
238
254
|
} : undefined,
|
|
239
255
|
|
|
240
256
|
// Fluent Query Builder
|
|
241
|
-
where(
|
|
242
|
-
|
|
257
|
+
where(columnOrCondition: string | LogicalCondition, operator?: WhereFilterOpShort, value?: unknown) {
|
|
258
|
+
const builder = new QueryBuilder<M>(accessor);
|
|
259
|
+
if (typeof columnOrCondition === "object") {
|
|
260
|
+
return builder.where(columnOrCondition);
|
|
261
|
+
}
|
|
262
|
+
return builder.where(columnOrCondition as keyof M & string, operator!, value as WhereValue<M[keyof M & string]>);
|
|
243
263
|
},
|
|
244
264
|
orderBy(column: keyof M & string, ascending?: "asc" | "desc") {
|
|
245
265
|
return new QueryBuilder<M>(accessor).orderBy(column, ascending);
|
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
import { FindParams, Entity, FindResponse, CollectionAccessor, QueryBuilderInterface, FilterOperator } from "@rebasepro/types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
case "<=": return "lte";
|
|
14
|
-
case "array-contains": return "cs";
|
|
15
|
-
case "array-contains-any": return "csa";
|
|
16
|
-
case "not-in": return "nin";
|
|
17
|
-
default: return op;
|
|
18
|
-
}
|
|
1
|
+
import { FindParams, Entity, FindResponse, CollectionAccessor, QueryBuilderInterface, FilterOperator, LogicalCondition, WhereValue, FilterCondition } from "@rebasepro/types";
|
|
2
|
+
|
|
3
|
+
export function or(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition {
|
|
4
|
+
return { type: "or", conditions };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function and(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition {
|
|
8
|
+
return { type: "and", conditions };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function cond(column: string, operator: FilterOperator, value: unknown): FilterCondition {
|
|
12
|
+
return { column, operator, value };
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
export class QueryBuilder<M extends Record<string, unknown> = Record<string, unknown>> implements QueryBuilderInterface<M> {
|
|
@@ -28,22 +22,38 @@ export class QueryBuilder<M extends Record<string, unknown> = Record<string, unk
|
|
|
28
22
|
* @example
|
|
29
23
|
* client.collection('users').where('age', '>=', 18).find()
|
|
30
24
|
*/
|
|
31
|
-
where
|
|
25
|
+
where<K extends keyof M & string>(column: K, operator: FilterOperator, value: WhereValue<M[K]>): this;
|
|
26
|
+
where(logicalCondition: LogicalCondition): this;
|
|
27
|
+
where(columnOrCondition: string | LogicalCondition, operator?: FilterOperator, value?: unknown): this {
|
|
28
|
+
// Handle LogicalCondition signature
|
|
29
|
+
if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
|
|
30
|
+
this.params.logical = columnOrCondition as LogicalCondition;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
32
34
|
if (!this.params.where) {
|
|
33
35
|
this.params.where = {};
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
+
const column = columnOrCondition as string;
|
|
39
|
+
const condition: [FilterOperator, unknown] = [operator!, value];
|
|
40
|
+
const existing = this.params.where[column];
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
if (existing === undefined) {
|
|
43
|
+
this.params.where[column] = condition;
|
|
44
|
+
} else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
|
|
45
|
+
(this.params.where[column] as [FilterOperator, unknown][]).push(condition);
|
|
46
|
+
} else {
|
|
47
|
+
// Convert existing single tuple/value into array of tuples
|
|
48
|
+
let firstCondition: [FilterOperator, unknown];
|
|
49
|
+
if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
|
|
50
|
+
firstCondition = existing as [FilterOperator, unknown];
|
|
51
|
+
} else {
|
|
52
|
+
firstCondition = ["==", existing];
|
|
53
|
+
}
|
|
54
|
+
this.params.where[column] = [firstCondition, condition];
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
47
57
|
return this;
|
|
48
58
|
}
|
|
49
59
|
|