@rebasepro/common 0.10.0 → 0.10.1-canary.0a881d4
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 +6 -90
- package/dist/index.es.js +19 -488
- package/dist/index.es.js.map +1 -1
- package/dist/util/collections.d.ts +1 -9
- package/dist/util/conditions.d.ts +2 -3
- package/dist/util/entities.d.ts +0 -2
- package/dist/util/index.d.ts +0 -5
- package/dist/util/permissions.d.ts +7 -4
- package/dist/util/resolutions.d.ts +5 -5
- package/package.json +4 -6
- package/src/collections/default-collections.ts +16 -32
- package/src/util/collections.ts +0 -28
- package/src/util/conditions.ts +2 -190
- package/src/util/entities.ts +0 -17
- package/src/util/index.ts +0 -5
- package/src/util/permissions.test.ts +9 -14
- package/src/util/permissions.ts +7 -4
- package/src/util/resolutions.ts +15 -7
- package/dist/util/filter-operator-resolution.d.ts +0 -32
- package/dist/util/navigation_from_path.d.ts +0 -34
- package/dist/util/navigation_utils.d.ts +0 -20
- package/dist/util/parent_references_from_path.d.ts +0 -6
- package/dist/util/references.d.ts +0 -2
- package/src/util/filter-operator-resolution.ts +0 -81
- package/src/util/navigation_from_path.ts +0 -122
- package/src/util/navigation_utils.ts +0 -170
- package/src/util/parent_references_from_path.ts +0 -58
- package/src/util/references.ts +0 -48
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CollectionConfig, Properties } from "@rebasepro/types";
|
|
2
2
|
export declare function sortProperties<M extends Record<string, unknown>>(properties: Properties, propertiesOrder?: string[]): Properties;
|
|
3
|
-
export declare function resolveDefaultSelectedView(defaultSelectedView: string | DefaultSelectedViewBuilder | undefined, params: DefaultSelectedViewParams): string | undefined;
|
|
4
|
-
export declare function getLocalChangesBackup(collection: CollectionConfig): "manual_apply" | "auto_apply";
|
|
5
|
-
/**
|
|
6
|
-
* Returns the primary keys for a entity collection by inspecting the properties
|
|
7
|
-
* and finding any properties with `isId`.
|
|
8
|
-
* Fallbacks to `["id"]` if no properties are marked as `isId: true`.
|
|
9
|
-
* @param collection
|
|
10
|
-
*/
|
|
11
3
|
export declare function getPrimaryKeys<M extends Record<string, unknown>>(collection: CollectionConfig<M>): Extract<keyof M, string>[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AuthState, ConditionContext, JsonLogicRule } from "@rebasepro/types";
|
|
2
2
|
/**
|
|
3
3
|
* Register custom JSON Logic operations for Rebase.
|
|
4
4
|
* Call this once at app initialization.
|
|
@@ -18,9 +18,8 @@ export declare function buildConditionContext(params: {
|
|
|
18
18
|
path: string;
|
|
19
19
|
entityId?: string;
|
|
20
20
|
index?: number;
|
|
21
|
-
authController:
|
|
21
|
+
authController: AuthState;
|
|
22
22
|
}): ConditionContext;
|
|
23
23
|
/**
|
|
24
24
|
* Apply PropertyConditions to a resolved property, evaluating all JSON Logic rules.
|
|
25
25
|
*/
|
|
26
|
-
export declare function applyPropertyConditions(property: Property, context: ConditionContext): Property;
|
package/dist/util/entities.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { DataType, Entity, EntityReference, EntityRelation, EntityStatus, EntityValues, Properties, Property } from "@rebasepro/types";
|
|
2
|
-
export declare function isReadOnly(property: Property): boolean;
|
|
3
|
-
export declare function isHidden(property: Property): boolean;
|
|
4
2
|
export declare function isPropertyBuilder(property?: Property): boolean;
|
|
5
3
|
export declare function getDefaultValuesFor<M extends Record<string, unknown>>(properties: Properties): Partial<EntityValues<M>>;
|
|
6
4
|
export declare function getDefaultValueFor(property?: Property): unknown;
|
package/dist/util/index.d.ts
CHANGED
|
@@ -7,9 +7,6 @@ export * from "./paths";
|
|
|
7
7
|
export * from "./resolutions";
|
|
8
8
|
export * from "./policy";
|
|
9
9
|
export * from "./permissions";
|
|
10
|
-
export * from "./references";
|
|
11
|
-
export * from "./navigation_from_path";
|
|
12
|
-
export * from "./parent_references_from_path";
|
|
13
10
|
export * from "./builders";
|
|
14
11
|
export * from "./storage";
|
|
15
12
|
export * from "./callbacks";
|
|
@@ -17,5 +14,3 @@ export * from "./relations";
|
|
|
17
14
|
export * from "./auth-default-policies";
|
|
18
15
|
export * from "./junction-policies";
|
|
19
16
|
export * from "./conditions";
|
|
20
|
-
export * from "./navigation_utils";
|
|
21
|
-
export * from "./filter-operator-resolution";
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { Entity, CollectionConfig, SecurityOperation, User } from "@rebasepro/types";
|
|
1
|
+
import { AuthState, Entity, CollectionConfig, SecurityOperation, User } from "@rebasepro/types";
|
|
2
2
|
/**
|
|
3
3
|
* Minimal auth context for permission checking.
|
|
4
4
|
* Only requires the user object — avoids forcing callers to construct
|
|
5
5
|
* a full AuthController just to check permissions.
|
|
6
|
+
*
|
|
7
|
+
* An alias, not a second definition: {@link AuthState} in `@rebasepro/types` is
|
|
8
|
+
* the same shape and is what `dynamicProps` and the JSON-Logic condition context
|
|
9
|
+
* now take, so declaring it twice would be the `WhereFilterOp` mistake again —
|
|
10
|
+
* two copies that agree only by luck.
|
|
6
11
|
*/
|
|
7
|
-
export
|
|
8
|
-
user: USER | null;
|
|
9
|
-
}
|
|
12
|
+
export type AuthContext<USER extends User = User> = AuthState<USER>;
|
|
10
13
|
/**
|
|
11
14
|
* How to resolve a policy result that cannot be decided client-side (a raw-SQL
|
|
12
15
|
* escape-hatch rule, or a row-column reference with no row in hand).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArrayProperty,
|
|
1
|
+
import { ArrayProperty, AuthState, CollectionConfig, EnumValueConfig, EnumValues, NumberProperty, Properties, Property, Relation, RelationProperty, StringProperty } from "@rebasepro/types";
|
|
2
2
|
type PropertyConfig = {
|
|
3
3
|
property: unknown;
|
|
4
4
|
[key: string]: unknown;
|
|
@@ -16,7 +16,7 @@ export type ResolvePropertyProps<M extends Record<string, unknown> = Record<stri
|
|
|
16
16
|
index?: number;
|
|
17
17
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
18
18
|
ignoreMissingFields?: boolean;
|
|
19
|
-
authController:
|
|
19
|
+
authController: AuthState;
|
|
20
20
|
};
|
|
21
21
|
export declare function resolveProperty<M extends Record<string, unknown> = Record<string, unknown>>(props: ResolvePropertyProps<M>): Property | null;
|
|
22
22
|
export declare function resolveRelationProperty(property: RelationProperty, relations: Relation[], propertyKey?: string): RelationProperty;
|
|
@@ -40,7 +40,7 @@ export declare function resolveProperties<M extends Record<string, unknown>>({ p
|
|
|
40
40
|
index?: number;
|
|
41
41
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
42
42
|
ignoreMissingFields?: boolean;
|
|
43
|
-
authController:
|
|
43
|
+
authController: AuthState;
|
|
44
44
|
}): Properties;
|
|
45
45
|
export declare function resolveArrayProperties<M>({ propertyKey, property, ignoreMissingFields, ...props }: {
|
|
46
46
|
propertyKey?: string;
|
|
@@ -52,7 +52,7 @@ export declare function resolveArrayProperties<M>({ propertyKey, property, ignor
|
|
|
52
52
|
index?: number;
|
|
53
53
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
54
54
|
ignoreMissingFields?: boolean;
|
|
55
|
-
authController:
|
|
55
|
+
authController: AuthState;
|
|
56
56
|
}): Property[];
|
|
57
57
|
export declare function getArrayResolvedProperties({ propertyKey, propertyValue, property, ...props }: {
|
|
58
58
|
propertyValue: unknown;
|
|
@@ -65,7 +65,7 @@ export declare function getArrayResolvedProperties({ propertyKey, propertyValue,
|
|
|
65
65
|
entityId?: string | number;
|
|
66
66
|
index?: number;
|
|
67
67
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
68
|
-
authController:
|
|
68
|
+
authController: AuthState;
|
|
69
69
|
}): Property[];
|
|
70
70
|
export declare function resolveEnumValues(input: EnumValues): EnumValueConfig[] | undefined;
|
|
71
71
|
export declare function getSubcollections<M extends Record<string, unknown> = Record<string, unknown>>(collection: CollectionConfig<M>): CollectionConfig<Record<string, unknown>>[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/common",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.1-canary.0a881d4",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"fast-equals": "6.0.0",
|
|
42
42
|
"json-logic-js": "^2.0.5",
|
|
43
|
-
"@rebasepro/types": "0.10.
|
|
44
|
-
"@rebasepro/utils": "0.10.
|
|
43
|
+
"@rebasepro/types": "0.10.1-canary.0a881d4",
|
|
44
|
+
"@rebasepro/utils": "0.10.1-canary.0a881d4"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@jest/globals": "^30.4.1",
|
|
@@ -50,10 +50,7 @@
|
|
|
50
50
|
"@types/jest": "^30.0.0",
|
|
51
51
|
"@types/node": "^25.9.3",
|
|
52
52
|
"@types/object-hash": "^3.0.6",
|
|
53
|
-
"@types/react": "^19.2.17",
|
|
54
|
-
"@types/react-dom": "^19.2.3",
|
|
55
53
|
"@types/react-measure": "^2.0.12",
|
|
56
|
-
"@vitejs/plugin-react": "^6.0.2",
|
|
57
54
|
"babel-plugin-react-compiler": "beta",
|
|
58
55
|
"cross-env": "^10.1.0",
|
|
59
56
|
"jest": "^30.4.2",
|
|
@@ -92,6 +89,7 @@
|
|
|
92
89
|
"testEnvironment": "node",
|
|
93
90
|
"moduleNameMapper": {
|
|
94
91
|
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js",
|
|
92
|
+
"^@rebasepro/admin-types$": "<rootDir>/../admin-types/src/index.ts",
|
|
95
93
|
"^@rebasepro/client$": "<rootDir>/../client/src/index.ts",
|
|
96
94
|
"^@rebasepro/types$": "<rootDir>/../types/src/index.ts",
|
|
97
95
|
"^@rebasepro/utils$": "<rootDir>/../utils/src/index.ts"
|
|
@@ -6,6 +6,12 @@ import { defineCollection } from "../util/builders";
|
|
|
6
6
|
* Prepended to the developer's collections array by the admin and server.
|
|
7
7
|
* Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
|
|
8
8
|
* override by defining their own collection with `slug: "users"`.
|
|
9
|
+
*
|
|
10
|
+
* Schema only — no `admin` block. This package is on the backend's dependency path,
|
|
11
|
+
* where that field does not exist: `@rebasepro/admin-types` adds it by declaration
|
|
12
|
+
* merging, and a BaaS install never installs that. The scaffolded
|
|
13
|
+
* `config/collections/users.ts` carries the presentation for projects that want this
|
|
14
|
+
* collection in their panel, which is also where it is editable.
|
|
9
15
|
*/
|
|
10
16
|
export const defaultUsersCollection = defineCollection({
|
|
11
17
|
name: "Users",
|
|
@@ -14,23 +20,17 @@ export const defaultUsersCollection = defineCollection({
|
|
|
14
20
|
auth: true,
|
|
15
21
|
table: "users",
|
|
16
22
|
schema: "rebase",
|
|
17
|
-
icon: "Users",
|
|
18
|
-
group: "Settings",
|
|
19
|
-
openEntityMode: "dialog",
|
|
20
|
-
disableDefaultActions: ["copy"],
|
|
21
23
|
securityRules: [
|
|
22
24
|
{ operation: "select",
|
|
23
25
|
roles: ["admin"] },
|
|
24
26
|
{ operations: ["insert", "update", "delete"],
|
|
25
27
|
roles: ["admin"] }
|
|
26
28
|
],
|
|
27
|
-
sort: ["createdAt", "desc"],
|
|
28
29
|
properties: {
|
|
29
30
|
id: {
|
|
30
31
|
name: "ID",
|
|
31
32
|
type: "string",
|
|
32
|
-
isId: "uuid"
|
|
33
|
-
ui: { readOnly: true }
|
|
33
|
+
isId: "uuid"
|
|
34
34
|
},
|
|
35
35
|
email: {
|
|
36
36
|
name: "Email",
|
|
@@ -47,8 +47,7 @@ unique: true }
|
|
|
47
47
|
photoURL: {
|
|
48
48
|
name: "Photo URL",
|
|
49
49
|
type: "string",
|
|
50
|
-
columnName: "photo_url"
|
|
51
|
-
ui: { url: "image" }
|
|
50
|
+
columnName: "photo_url"
|
|
52
51
|
},
|
|
53
52
|
roles: {
|
|
54
53
|
name: "Roles",
|
|
@@ -68,58 +67,43 @@ unique: true }
|
|
|
68
67
|
name: "Password Hash",
|
|
69
68
|
type: "string",
|
|
70
69
|
columnName: "password_hash",
|
|
71
|
-
excludeFromApi: true
|
|
72
|
-
ui: { hideFromCollection: true,
|
|
73
|
-
disabled: { hidden: true } }
|
|
70
|
+
excludeFromApi: true
|
|
74
71
|
},
|
|
75
72
|
emailVerified: {
|
|
76
73
|
name: "Email Verified",
|
|
77
74
|
type: "boolean",
|
|
78
75
|
columnName: "email_verified",
|
|
79
|
-
defaultValue: false
|
|
80
|
-
ui: { hideFromCollection: true,
|
|
81
|
-
disabled: { hidden: true } }
|
|
76
|
+
defaultValue: false
|
|
82
77
|
},
|
|
83
78
|
emailVerificationToken: {
|
|
84
79
|
name: "Email Verification Token",
|
|
85
80
|
type: "string",
|
|
86
81
|
columnName: "email_verification_token",
|
|
87
|
-
excludeFromApi: true
|
|
88
|
-
ui: { hideFromCollection: true,
|
|
89
|
-
disabled: { hidden: true } }
|
|
82
|
+
excludeFromApi: true
|
|
90
83
|
},
|
|
91
84
|
emailVerificationSentAt: {
|
|
92
85
|
name: "Email Verification Sent At",
|
|
93
86
|
type: "date",
|
|
94
|
-
columnName: "email_verification_sent_at"
|
|
95
|
-
ui: { hideFromCollection: true,
|
|
96
|
-
disabled: { hidden: true } }
|
|
87
|
+
columnName: "email_verification_sent_at"
|
|
97
88
|
},
|
|
98
89
|
metadata: {
|
|
99
90
|
name: "Metadata",
|
|
100
91
|
type: "map",
|
|
101
92
|
keyValue: true,
|
|
102
93
|
properties: {},
|
|
103
|
-
defaultValue: {}
|
|
104
|
-
ui: { hideFromCollection: true,
|
|
105
|
-
disabled: { hidden: true } }
|
|
94
|
+
defaultValue: {}
|
|
106
95
|
},
|
|
107
96
|
createdAt: {
|
|
108
97
|
name: "Created At",
|
|
109
98
|
type: "date",
|
|
110
99
|
columnName: "created_at",
|
|
111
|
-
autoValue: "on_create"
|
|
112
|
-
ui: { readOnly: true }
|
|
100
|
+
autoValue: "on_create"
|
|
113
101
|
},
|
|
114
102
|
updatedAt: {
|
|
115
103
|
name: "Updated At",
|
|
116
104
|
type: "date",
|
|
117
105
|
columnName: "updated_at",
|
|
118
|
-
autoValue: "on_update"
|
|
119
|
-
ui: { hideFromCollection: true,
|
|
120
|
-
disabled: { hidden: true } }
|
|
106
|
+
autoValue: "on_update"
|
|
121
107
|
}
|
|
122
|
-
}
|
|
123
|
-
listProperties: ["displayName", "email", "roles", "createdAt"],
|
|
124
|
-
propertiesOrder: ["id", "email", "displayName", "roles", "createdAt"]
|
|
108
|
+
}
|
|
125
109
|
});
|
package/src/util/collections.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DefaultSelectedViewBuilder,
|
|
3
|
-
DefaultSelectedViewParams,
|
|
4
2
|
CollectionConfig,
|
|
5
3
|
Properties,
|
|
6
4
|
Property
|
|
@@ -85,34 +83,8 @@ export function sortProperties<M extends Record<string, unknown>>(properties: Pr
|
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
export function resolveDefaultSelectedView(
|
|
89
|
-
defaultSelectedView: string | DefaultSelectedViewBuilder | undefined,
|
|
90
|
-
params: DefaultSelectedViewParams
|
|
91
|
-
) {
|
|
92
|
-
if (!defaultSelectedView) {
|
|
93
|
-
return undefined;
|
|
94
|
-
} else if (typeof defaultSelectedView === "string") {
|
|
95
|
-
return defaultSelectedView;
|
|
96
|
-
} else {
|
|
97
|
-
return defaultSelectedView(params);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
86
|
|
|
102
|
-
export function getLocalChangesBackup(collection: CollectionConfig) {
|
|
103
|
-
if (!collection.localChangesBackup) {
|
|
104
|
-
return "manual_apply";
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return collection.localChangesBackup;
|
|
108
|
-
}
|
|
109
87
|
|
|
110
|
-
/**
|
|
111
|
-
* Returns the primary keys for a entity collection by inspecting the properties
|
|
112
|
-
* and finding any properties with `isId`.
|
|
113
|
-
* Fallbacks to `["id"]` if no properties are marked as `isId: true`.
|
|
114
|
-
* @param collection
|
|
115
|
-
*/
|
|
116
88
|
export function getPrimaryKeys<M extends Record<string, unknown>>(collection: CollectionConfig<M>): Extract<keyof M, string>[] {
|
|
117
89
|
const properties = collection.properties;
|
|
118
90
|
if (!properties) {
|
package/src/util/conditions.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import jsonLogic from "json-logic-js";
|
|
2
2
|
import {
|
|
3
3
|
ArrayProperty,
|
|
4
|
-
|
|
4
|
+
AuthState,
|
|
5
5
|
ConditionContext,
|
|
6
6
|
EnumValueConfig,
|
|
7
7
|
JsonLogicRule,
|
|
@@ -123,7 +123,7 @@ export function buildConditionContext(params: {
|
|
|
123
123
|
path: string;
|
|
124
124
|
entityId?: string;
|
|
125
125
|
index?: number;
|
|
126
|
-
authController:
|
|
126
|
+
authController: AuthState;
|
|
127
127
|
}): ConditionContext {
|
|
128
128
|
const {
|
|
129
129
|
propertyKey,
|
|
@@ -161,191 +161,3 @@ export function buildConditionContext(params: {
|
|
|
161
161
|
/**
|
|
162
162
|
* Apply PropertyConditions to a resolved property, evaluating all JSON Logic rules.
|
|
163
163
|
*/
|
|
164
|
-
export function applyPropertyConditions(
|
|
165
|
-
property: Property,
|
|
166
|
-
context: ConditionContext
|
|
167
|
-
): Property {
|
|
168
|
-
const { conditions } = property;
|
|
169
|
-
if (!conditions) return property;
|
|
170
|
-
|
|
171
|
-
const result = { ...property };
|
|
172
|
-
|
|
173
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
174
|
-
// FIELD STATE CONDITIONS
|
|
175
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
176
|
-
|
|
177
|
-
// Evaluate disabled condition
|
|
178
|
-
if (conditions.disabled) {
|
|
179
|
-
const isDisabled = evaluateCondition(conditions.disabled, context);
|
|
180
|
-
if (isDisabled) {
|
|
181
|
-
result.ui = result.ui || {};
|
|
182
|
-
result.ui.disabled = {
|
|
183
|
-
clearOnDisabled: conditions.clearOnDisabled ?? false,
|
|
184
|
-
disabledMessage: conditions.disabledMessage,
|
|
185
|
-
hidden: false
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Evaluate hidden condition
|
|
191
|
-
if (conditions.hidden) {
|
|
192
|
-
const isHidden = evaluateCondition(conditions.hidden, context);
|
|
193
|
-
if (isHidden) {
|
|
194
|
-
result.ui = result.ui || {};
|
|
195
|
-
result.ui.disabled = {
|
|
196
|
-
...(typeof result.ui?.disabled === "object" ? result.ui.disabled : {}),
|
|
197
|
-
hidden: true,
|
|
198
|
-
clearOnDisabled: conditions.clearOnDisabled ?? false
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Evaluate readOnly condition
|
|
204
|
-
if (conditions.readOnly) {
|
|
205
|
-
const isReadOnly = evaluateCondition(conditions.readOnly, context);
|
|
206
|
-
if (isReadOnly) {
|
|
207
|
-
result.ui = result.ui || {};
|
|
208
|
-
result.ui.readOnly = true;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
213
|
-
// VALIDATION CONDITIONS
|
|
214
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
215
|
-
|
|
216
|
-
// Evaluate required condition
|
|
217
|
-
if (conditions.required !== undefined) {
|
|
218
|
-
const isRequired = evaluateCondition(conditions.required, context) as boolean;
|
|
219
|
-
result.validation = {
|
|
220
|
-
...result.validation,
|
|
221
|
-
required: isRequired as boolean | undefined,
|
|
222
|
-
requiredMessage: conditions.requiredMessage
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
227
|
-
// VALUE CONDITIONS
|
|
228
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
229
|
-
|
|
230
|
-
// Apply default value for new entities
|
|
231
|
-
if (context.isNew && conditions.defaultValue !== undefined) {
|
|
232
|
-
result.defaultValue = evaluateCondition(conditions.defaultValue, context) as Property["defaultValue"];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
236
|
-
// ENUM CONDITIONS
|
|
237
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
238
|
-
|
|
239
|
-
if ("enum" in result && result.enum && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) {
|
|
240
|
-
(result as Record<string, unknown>).enum = applyEnumConditions(
|
|
241
|
-
result.enum as EnumValueConfig[],
|
|
242
|
-
conditions,
|
|
243
|
-
context
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
248
|
-
// REFERENCE CONDITIONS
|
|
249
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
250
|
-
|
|
251
|
-
if (result.type === "reference") {
|
|
252
|
-
if (conditions.referencePath) {
|
|
253
|
-
(result as ReferenceProperty).path = evaluateCondition(conditions.referencePath, context) as string;
|
|
254
|
-
}
|
|
255
|
-
if (conditions.referenceFilter) {
|
|
256
|
-
(result as ReferenceProperty).fixedFilter = evaluateCondition(conditions.referenceFilter, context) as ReferenceProperty["fixedFilter"];
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
261
|
-
// ARRAY CONDITIONS
|
|
262
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
263
|
-
|
|
264
|
-
if (result.type === "array") {
|
|
265
|
-
if (conditions.canAddElements !== undefined) {
|
|
266
|
-
(result as ArrayProperty).canAddElements = evaluateCondition(conditions.canAddElements, context) as boolean;
|
|
267
|
-
}
|
|
268
|
-
if (conditions.sortable !== undefined) {
|
|
269
|
-
(result as ArrayProperty).sortable = evaluateCondition(conditions.sortable, context) as boolean;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return result;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Convert an object with numeric keys back to an array.
|
|
278
|
-
* Firestore stores arrays as {"0": "a", "1": "b"} to avoid nested arrays.
|
|
279
|
-
*/
|
|
280
|
-
function objectToArray(obj: unknown): string[] {
|
|
281
|
-
if (Array.isArray(obj)) return obj.map(String);
|
|
282
|
-
if (obj && typeof obj === "object") {
|
|
283
|
-
const keys = Object.keys(obj);
|
|
284
|
-
if (keys.length > 0 && keys.every(k => !isNaN(Number(k)))) {
|
|
285
|
-
return keys
|
|
286
|
-
.sort((a, b) => Number(a) - Number(b))
|
|
287
|
-
.map(k => (obj as Record<string, unknown>)[k])
|
|
288
|
-
.filter((v): v is string => typeof v === "string" || typeof v === "number")
|
|
289
|
-
.map(String);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return [];
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Apply enum-specific conditions to filter and modify enum values.
|
|
297
|
-
*/
|
|
298
|
-
function applyEnumConditions(
|
|
299
|
-
enumValues: EnumValueConfig[],
|
|
300
|
-
conditions: PropertyConditions,
|
|
301
|
-
context: ConditionContext
|
|
302
|
-
): EnumValueConfig[] {
|
|
303
|
-
let result = [...enumValues];
|
|
304
|
-
|
|
305
|
-
// Apply allowedEnumValues filter
|
|
306
|
-
if (conditions.allowedEnumValues) {
|
|
307
|
-
const allowed = evaluateCondition(conditions.allowedEnumValues, context);
|
|
308
|
-
// Handle both array format and object-with-numeric-keys format (Firestore workaround)
|
|
309
|
-
const allowedArray = objectToArray(allowed);
|
|
310
|
-
if (allowedArray.length > 0) {
|
|
311
|
-
result = result.filter(ev => allowedArray.includes(String(ev.id)));
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Apply excludedEnumValues filter
|
|
316
|
-
if (conditions.excludedEnumValues) {
|
|
317
|
-
const excluded = evaluateCondition(conditions.excludedEnumValues, context);
|
|
318
|
-
// Handle both array format and object-with-numeric-keys format
|
|
319
|
-
const excludedArray = objectToArray(excluded);
|
|
320
|
-
if (excludedArray.length > 0) {
|
|
321
|
-
result = result.filter(ev => !excludedArray.includes(String(ev.id)));
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Apply individual enum conditions
|
|
326
|
-
if (conditions.enumConditions) {
|
|
327
|
-
result = result
|
|
328
|
-
.map(ev => {
|
|
329
|
-
const evConditions = conditions.enumConditions?.[ev.id];
|
|
330
|
-
if (!evConditions) return ev;
|
|
331
|
-
|
|
332
|
-
// Check hidden condition first
|
|
333
|
-
if (evConditions.hidden && evaluateCondition(evConditions.hidden, context)) {
|
|
334
|
-
return null; // Will be filtered out
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Check disabled condition
|
|
338
|
-
if (evConditions.disabled && evaluateCondition(evConditions.disabled, context)) {
|
|
339
|
-
return {
|
|
340
|
-
...ev,
|
|
341
|
-
disabled: true
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return ev;
|
|
346
|
-
})
|
|
347
|
-
.filter((ev): ev is EnumValueConfig => ev !== null);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return result;
|
|
351
|
-
}
|
package/src/util/entities.ts
CHANGED
|
@@ -11,23 +11,6 @@ import {
|
|
|
11
11
|
import { DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE } from "./common";
|
|
12
12
|
import { mergeDeep } from "@rebasepro/utils";
|
|
13
13
|
|
|
14
|
-
export function isReadOnly(property: Property): boolean {
|
|
15
|
-
if (property.ui?.readOnly)
|
|
16
|
-
return true;
|
|
17
|
-
if (property.type === "date") {
|
|
18
|
-
if (property.autoValue)
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
if (property.type === "reference") {
|
|
22
|
-
return !property.path && !("Field" in (property.ui || {}) && property.ui?.Field);
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function isHidden(property: Property): boolean {
|
|
28
|
-
return typeof property.ui?.disabled === "object" && Boolean(property.ui?.disabled.hidden);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
14
|
export function isPropertyBuilder(property?: Property) {
|
|
32
15
|
return typeof property?.dynamicProps === "function";
|
|
33
16
|
}
|
package/src/util/index.ts
CHANGED
|
@@ -7,9 +7,6 @@ export * from "./paths";
|
|
|
7
7
|
export * from "./resolutions";
|
|
8
8
|
export * from "./policy";
|
|
9
9
|
export * from "./permissions";
|
|
10
|
-
export * from "./references";
|
|
11
|
-
export * from "./navigation_from_path";
|
|
12
|
-
export * from "./parent_references_from_path";
|
|
13
10
|
export * from "./builders";
|
|
14
11
|
export * from "./storage";
|
|
15
12
|
export * from "./callbacks";
|
|
@@ -17,5 +14,3 @@ export * from "./relations";
|
|
|
17
14
|
export * from "./auth-default-policies";
|
|
18
15
|
export * from "./junction-policies";
|
|
19
16
|
export * from "./conditions";
|
|
20
|
-
export * from "./navigation_utils";
|
|
21
|
-
export * from "./filter-operator-resolution";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { canCreateEntity, canEditEntity, canDeleteEntity, canReadCollection } from "./permissions";
|
|
2
|
-
import {
|
|
2
|
+
import { AuthState, CollectionConfig, Entity, User, SecurityRule } from "@rebasepro/types";
|
|
3
3
|
|
|
4
4
|
describe("Permissions Evaluator", () => {
|
|
5
5
|
|
|
@@ -25,23 +25,18 @@ describe("Permissions Evaluator", () => {
|
|
|
25
25
|
roles: ["admin"]
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
loginSkipped: false,
|
|
33
|
-
signOut: async () => { },
|
|
34
|
-
getAuthToken: async () => "token",
|
|
35
|
-
setExtra: () => { },
|
|
36
|
-
extra: undefined
|
|
28
|
+
// Just the user now. Faking `signOut`, `getAuthToken`, `authLoading` and the
|
|
29
|
+
// rest of a frontend controller was never anything these assertions read.
|
|
30
|
+
const mockAuthController: AuthState<User> = {
|
|
31
|
+
user: mockUser
|
|
37
32
|
};
|
|
38
33
|
|
|
39
|
-
const adminAuthController:
|
|
34
|
+
const adminAuthController: AuthState<User> = {
|
|
40
35
|
...mockAuthController,
|
|
41
36
|
user: adminUser
|
|
42
37
|
};
|
|
43
38
|
|
|
44
|
-
const unauthenticatedController:
|
|
39
|
+
const unauthenticatedController: AuthState<User> = {
|
|
45
40
|
...mockAuthController,
|
|
46
41
|
user: null
|
|
47
42
|
};
|
|
@@ -709,7 +704,7 @@ isAnonymous: false,
|
|
|
709
704
|
photoURL: null,
|
|
710
705
|
roles: ["editor"]
|
|
711
706
|
};
|
|
712
|
-
const editorAuth:
|
|
707
|
+
const editorAuth: AuthState<User> = { ...mockAuthController,
|
|
713
708
|
user: editorUser };
|
|
714
709
|
|
|
715
710
|
const collection = createMockCollection([
|
|
@@ -820,7 +815,7 @@ using: "status = 'draft'" }
|
|
|
820
815
|
test("58. Missing authController ('blankAuth') handled gracefully", () => {
|
|
821
816
|
const collection = createMockCollection([{ operation: "insert",
|
|
822
817
|
roles: ["admin"] }]);
|
|
823
|
-
const blankAuth = { user: null } as unknown as
|
|
818
|
+
const blankAuth = { user: null } as unknown as AuthState<User>;
|
|
824
819
|
expect(canCreateEntity(collection, blankAuth, "test", null)).toBe(false);
|
|
825
820
|
});
|
|
826
821
|
|
package/src/util/permissions.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Entity, CollectionConfig, getDataSourceCapabilities, SecurityOperation, SecurityRule, User } from "@rebasepro/types";
|
|
1
|
+
import { AuthState, Entity, CollectionConfig, getDataSourceCapabilities, SecurityOperation, SecurityRule, User } from "@rebasepro/types";
|
|
2
2
|
import { securityRuleToConditions } from "./policy/securityRuleToConditions";
|
|
3
3
|
import { evaluatePolicy, PolicyEvalContext, TriState } from "./policy/evaluatePolicy";
|
|
4
4
|
|
|
@@ -6,10 +6,13 @@ import { evaluatePolicy, PolicyEvalContext, TriState } from "./policy/evaluatePo
|
|
|
6
6
|
* Minimal auth context for permission checking.
|
|
7
7
|
* Only requires the user object — avoids forcing callers to construct
|
|
8
8
|
* a full AuthController just to check permissions.
|
|
9
|
+
*
|
|
10
|
+
* An alias, not a second definition: {@link AuthState} in `@rebasepro/types` is
|
|
11
|
+
* the same shape and is what `dynamicProps` and the JSON-Logic condition context
|
|
12
|
+
* now take, so declaring it twice would be the `WhereFilterOp` mistake again —
|
|
13
|
+
* two copies that agree only by luck.
|
|
9
14
|
*/
|
|
10
|
-
export
|
|
11
|
-
user: USER | null;
|
|
12
|
-
}
|
|
15
|
+
export type AuthContext<USER extends User = User> = AuthState<USER>;
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* How to resolve a policy result that cannot be decided client-side (a raw-SQL
|