@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/common",
3
3
  "type": "module",
4
- "version": "0.3.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.3.0",
45
- "@rebasepro/utils": "0.3.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 definition.
4
+ * Default users collection.
5
5
  *
6
- * Shared between the admin UI (for navigation/display) and the backend
7
- * (for schema generation). Both consumers prepend this to the developer's
8
- * collections array and rely on generic slug-based deduplication
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
- password_hash: {
33
- name: "Password Hash",
37
+ displayName: {
38
+ name: "Name",
34
39
  type: "string",
35
- ui: { hideFromCollection: true }
36
- },
37
- display_name: {
38
- name: "Display Name",
39
- type: "string"
40
+ columnName: "display_name",
41
+ validation: { required: true }
40
42
  },
41
- photo_url: {
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
- email_verified: {
69
+ emailVerified: {
46
70
  name: "Email Verified",
47
71
  type: "boolean",
48
- defaultValue: false
72
+ columnName: "email_verified",
73
+ defaultValue: false,
74
+ ui: { hideFromCollection: true, disabled: { hidden: true } }
49
75
  },
50
- email_verification_token: {
76
+ emailVerificationToken: {
51
77
  name: "Email Verification Token",
52
78
  type: "string",
53
- ui: { hideFromCollection: true }
79
+ columnName: "email_verification_token",
80
+ ui: { hideFromCollection: true, disabled: { hidden: true } }
54
81
  },
55
- email_verification_sent_at: {
82
+ emailVerificationSentAt: {
56
83
  name: "Email Verification Sent At",
57
84
  type: "date",
58
- ui: { hideFromCollection: true }
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
- created_at: {
94
+ createdAt: {
67
95
  name: "Created At",
68
96
  type: "date",
97
+ columnName: "created_at",
69
98
  autoValue: "on_create",
70
- ui: { readOnly: true, hideFromCollection: true }
99
+ ui: { readOnly: true }
71
100
  },
72
- updated_at: {
101
+ updatedAt: {
73
102
  name: "Updated At",
74
103
  type: "date",
104
+ columnName: "updated_at",
75
105
  autoValue: "on_update",
76
- ui: { readOnly: true, hideFromCollection: true }
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: [operator, value]
73
- if (Array.isArray(rawValue) && rawValue.length === 2) {
74
- const [rawOp, val] = rawValue;
75
- const mappedOp = operatorMap[rawOp] ?? "==";
76
- filter[field] = [mappedOp, val];
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(column: keyof M & string, operator: WhereFilterOp, value: unknown) {
242
- return new QueryBuilder<M>(accessor).where(column, operator, value);
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
- * Maps standard operators to Rebase backend's string-based operators
5
- */
6
- function mapOperator(op: FilterOperator): string {
7
- switch (op) {
8
- case "==": return "eq";
9
- case "!=": return "neq";
10
- case ">": return "gt";
11
- case ">=": return "gte";
12
- case "<": return "lt";
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(column: keyof M & string, operator: FilterOperator, value: unknown): this {
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 mappedOp = mapOperator(operator);
37
- let formattedValue = value;
38
+ const column = columnOrCondition as string;
39
+ const condition: [FilterOperator, unknown] = [operator!, value];
40
+ const existing = this.params.where[column];
38
41
 
39
- // Handle arrays for in, nin, cs, csa
40
- if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
41
- formattedValue = `(${value.join(",")})`;
42
- } else if (value === null) {
43
- formattedValue = "null";
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