@rebasepro/common 0.5.0 → 0.6.1
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/index.es.js +2053 -2415
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2282 -2534
- package/dist/index.umd.js.map +1 -1
- package/dist/util/entities.d.ts +5 -1
- package/package.json +14 -14
- package/src/collections/CollectionRegistry.ts +1 -1
- package/src/collections/default-collections.ts +21 -9
- package/src/data/query_builder.ts +7 -3
- package/src/util/entities.ts +14 -4
- package/src/util/relations.ts +5 -3
- package/src/util/resolutions.ts +3 -2
package/dist/index.es.js
CHANGED
|
@@ -1,2536 +1,2174 @@
|
|
|
1
1
|
import { EntityReference, EntityRelation, getDataSourceCapabilities } from "@rebasepro/types";
|
|
2
|
-
import {
|
|
2
|
+
import { deepClone, generateForeignKeyName, getIn, isDefaultFieldConfigId, mergeDeep, randomString, removeFunctions, toSnakeCase } from "@rebasepro/utils";
|
|
3
3
|
import jsonLogic from "json-logic-js";
|
|
4
4
|
import { deepEqual } from "fast-equals";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
//#region src/util/common.ts
|
|
6
|
+
var DEFAULT_ONE_OF_TYPE = "type";
|
|
7
|
+
var DEFAULT_ONE_OF_VALUE = "value";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/util/entities.ts
|
|
7
10
|
function isReadOnly(property) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
11
|
+
if (property.ui?.readOnly) return true;
|
|
12
|
+
if (property.type === "date") {
|
|
13
|
+
if (property.autoValue) return true;
|
|
14
|
+
}
|
|
15
|
+
if (property.type === "reference") return !property.path && !("Field" in (property.ui || {}) && property.ui?.Field);
|
|
16
|
+
return false;
|
|
16
17
|
}
|
|
17
18
|
function isHidden(property) {
|
|
18
|
-
|
|
19
|
+
return typeof property.ui?.disabled === "object" && Boolean(property.ui?.disabled.hidden);
|
|
19
20
|
}
|
|
20
21
|
function isPropertyBuilder(property) {
|
|
21
|
-
|
|
22
|
+
return typeof property?.dynamicProps === "function";
|
|
22
23
|
}
|
|
23
24
|
function getDefaultValuesFor(properties) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
...b
|
|
34
|
-
}), {});
|
|
25
|
+
if (!properties) return {};
|
|
26
|
+
return Object.entries(properties).map(([key, property]) => {
|
|
27
|
+
if (!property) return {};
|
|
28
|
+
const value = getDefaultValueFor(property);
|
|
29
|
+
return value === void 0 ? {} : { [key]: value };
|
|
30
|
+
}).reduce((a, b) => ({
|
|
31
|
+
...a,
|
|
32
|
+
...b
|
|
33
|
+
}), {});
|
|
35
34
|
}
|
|
36
35
|
function getDefaultValueFor(property) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} else {
|
|
46
|
-
return getDefaultValueFortype(property.type);
|
|
47
|
-
}
|
|
36
|
+
if (!property) return void 0;
|
|
37
|
+
if (isPropertyBuilder(property)) return void 0;
|
|
38
|
+
if (property.defaultValue || property.defaultValue === null) return property.defaultValue;
|
|
39
|
+
else if (property.type === "map" && property.properties) {
|
|
40
|
+
const defaultValuesFor = getDefaultValuesFor(property.properties);
|
|
41
|
+
if (Object.keys(defaultValuesFor).length === 0) return void 0;
|
|
42
|
+
return defaultValuesFor;
|
|
43
|
+
} else return getDefaultValueFortype(property.type);
|
|
48
44
|
}
|
|
49
45
|
function getDefaultValueFortype(type) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (status === "existing" && property.autoValue === "on_update") {
|
|
79
|
-
return timestampNowValue;
|
|
80
|
-
} else if ((status === "new" || status === "copy") && (property.autoValue === "on_update" || property.autoValue === "on_create")) {
|
|
81
|
-
return timestampNowValue;
|
|
82
|
-
} else {
|
|
83
|
-
return inputValue;
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
return inputValue;
|
|
87
|
-
}
|
|
88
|
-
}) ?? {};
|
|
89
|
-
}
|
|
46
|
+
if (type === "string") return null;
|
|
47
|
+
else if (type === "number") return null;
|
|
48
|
+
else if (type === "boolean") return false;
|
|
49
|
+
else if (type === "date") return null;
|
|
50
|
+
else if (type === "array") return [];
|
|
51
|
+
else if (type === "map") return {};
|
|
52
|
+
else if (type === "vector") return null;
|
|
53
|
+
else if (type === "binary") return null;
|
|
54
|
+
else return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Update the automatic values in an entity before save
|
|
58
|
+
* @group Driver
|
|
59
|
+
*/
|
|
60
|
+
function updateDateAutoValues({ inputValues, properties, status, timestampNowValue }) {
|
|
61
|
+
return traverseValuesProperties(inputValues, properties, (inputValue, property) => {
|
|
62
|
+
if (property.type === "date") if (status === "existing" && property.autoValue === "on_update") return timestampNowValue;
|
|
63
|
+
else if ((status === "new" || status === "copy") && (property.autoValue === "on_update" || property.autoValue === "on_create")) return timestampNowValue;
|
|
64
|
+
else return inputValue;
|
|
65
|
+
else return inputValue;
|
|
66
|
+
}) ?? {};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Add missing required fields, expected in the collection, to the values of an entity
|
|
70
|
+
* @param values
|
|
71
|
+
* @param properties
|
|
72
|
+
* @group Driver
|
|
73
|
+
*/
|
|
90
74
|
function sanitizeData(values, properties) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
75
|
+
const result = values;
|
|
76
|
+
Object.entries(properties).forEach(([key, property]) => {
|
|
77
|
+
if (values && values[key] !== void 0) result[key] = values[key];
|
|
78
|
+
else if (property.validation?.required) result[key] = null;
|
|
79
|
+
});
|
|
80
|
+
return result;
|
|
97
81
|
}
|
|
98
82
|
function getReferenceFrom(entity) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
if (typeof entity.id !== "string") throw new Error("Only string IDs are supported in references");
|
|
84
|
+
return new EntityReference({
|
|
85
|
+
id: entity.id,
|
|
86
|
+
path: entity.path,
|
|
87
|
+
driver: entity.driver,
|
|
88
|
+
databaseId: entity.databaseId
|
|
89
|
+
});
|
|
106
90
|
}
|
|
107
91
|
function getRelationFrom(entity) {
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
92
|
+
return new EntityRelation(entity.id, entity.path, entity);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Normalize a value into a proper EntityRelation instance.
|
|
96
|
+
* Handles EntityRelation class instances, and plain objects
|
|
97
|
+
* with `__type === "relation"` or an `isEntityRelation()` method.
|
|
98
|
+
*
|
|
99
|
+
* When `propertyType` is `"relation"`, also accepts plain objects that
|
|
100
|
+
* have `id` and `path` fields — these are relation-shaped objects from
|
|
101
|
+
* edge cases in the data pipeline (REST fallback, stale cache, custom data source).
|
|
102
|
+
*
|
|
103
|
+
* Returns null if the value cannot be coerced.
|
|
104
|
+
*/
|
|
105
|
+
function normalizeToEntityRelation(value, propertyType) {
|
|
106
|
+
if (value instanceof EntityRelation) return value;
|
|
107
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
108
|
+
const obj = value;
|
|
109
|
+
if (!(obj.__type === "relation" || obj.__type === "reference" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation() || typeof obj.isEntityReference === "function" && obj.isEntityReference() || propertyType === "relation" && typeof obj.id !== "undefined" && typeof obj.path === "string")) return null;
|
|
110
|
+
return new EntityRelation(obj.id, obj.path, obj.data);
|
|
117
111
|
}
|
|
118
112
|
function traverseValuesProperties(inputValues, properties, operation) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}), {});
|
|
132
|
-
const result = mergeDeep(safeInputValues, updatedValues);
|
|
133
|
-
if (!result || Object.keys(result).length === 0) return void 0;
|
|
134
|
-
return result;
|
|
113
|
+
const safeInputValues = inputValues ?? {};
|
|
114
|
+
const result = mergeDeep(safeInputValues, Object.entries(properties).map(([key, property]) => {
|
|
115
|
+
const updatedValue = traverseValueProperty(safeInputValues && safeInputValues[key], property, operation);
|
|
116
|
+
if (updatedValue === null) return null;
|
|
117
|
+
if (updatedValue === void 0) return void 0;
|
|
118
|
+
return { [key]: updatedValue };
|
|
119
|
+
}).reduce((a, b) => ({
|
|
120
|
+
...a,
|
|
121
|
+
...b
|
|
122
|
+
}), {}));
|
|
123
|
+
if (!result || Object.keys(result).length === 0) return void 0;
|
|
124
|
+
return result;
|
|
135
125
|
}
|
|
136
126
|
function traverseValueProperty(inputValue, property, operation) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
return value;
|
|
171
|
-
}
|
|
127
|
+
let value;
|
|
128
|
+
if (property.type === "map" && property.properties) value = traverseValuesProperties(inputValue, property.properties, operation);
|
|
129
|
+
else if (property.type === "array") {
|
|
130
|
+
const of = property.of;
|
|
131
|
+
if (of && Array.isArray(inputValue) && !Array.isArray(of)) value = inputValue.map((e) => traverseValueProperty(e, of, operation));
|
|
132
|
+
else if (of && Array.isArray(inputValue) && Array.isArray(of)) value = inputValue.map((e, i) => {
|
|
133
|
+
if (i < of.length) return traverseValueProperty(e, of[i], operation);
|
|
134
|
+
return null;
|
|
135
|
+
}).filter(Boolean);
|
|
136
|
+
else if (property.oneOf && Array.isArray(inputValue)) {
|
|
137
|
+
const typeField = property.oneOf?.typeField ?? "type";
|
|
138
|
+
const valueField = property.oneOf?.valueField ?? "value";
|
|
139
|
+
value = inputValue.map((e) => {
|
|
140
|
+
if (e === null) return null;
|
|
141
|
+
if (typeof e !== "object") return e;
|
|
142
|
+
const rec = e;
|
|
143
|
+
const type = rec[typeField];
|
|
144
|
+
const childProperty = property.oneOf?.properties[type];
|
|
145
|
+
if (!type || !childProperty) return e;
|
|
146
|
+
return {
|
|
147
|
+
[typeField]: type,
|
|
148
|
+
[valueField]: traverseValueProperty(rec[valueField], childProperty, operation)
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
} else value = inputValue;
|
|
152
|
+
} else value = operation(inputValue, property);
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Create a lightweight relation stub for CMS views.
|
|
157
|
+
* Replaces inline `{ id, path, __type: "relation" }` object literals.
|
|
158
|
+
*/
|
|
172
159
|
function createRelationRef(id, path) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
160
|
+
return {
|
|
161
|
+
id,
|
|
162
|
+
path,
|
|
163
|
+
__type: "relation"
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Create a hydrated relation reference that includes the full entity data.
|
|
168
|
+
* Used when entity data has been pre-fetched (e.g., via batch loading or JOINs).
|
|
169
|
+
*/
|
|
179
170
|
function createRelationRefWithData(id, path, data) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
171
|
+
return {
|
|
172
|
+
id,
|
|
173
|
+
path,
|
|
174
|
+
__type: "relation",
|
|
175
|
+
data
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/util/collections.ts
|
|
187
180
|
function sortProperties(properties, propertiesOrder) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
[key]: {
|
|
237
|
-
...property,
|
|
238
|
-
properties: sortProperties(property.properties, property.propertiesOrder)
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
} else {
|
|
242
|
-
return {
|
|
243
|
-
[key]: property
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}).reduce((a, b) => ({
|
|
247
|
-
...a,
|
|
248
|
-
...b
|
|
249
|
-
}), {});
|
|
250
|
-
return {
|
|
251
|
-
...orderedResult,
|
|
252
|
-
...missingProperties
|
|
253
|
-
};
|
|
254
|
-
} catch (e) {
|
|
255
|
-
console.error("Error sorting properties", e);
|
|
256
|
-
return properties;
|
|
257
|
-
}
|
|
181
|
+
try {
|
|
182
|
+
const propertiesKeys = Object.keys(properties);
|
|
183
|
+
if (!propertiesOrder || propertiesOrder.length === 0) return propertiesKeys.map((key) => {
|
|
184
|
+
const property = properties[key];
|
|
185
|
+
if (!isPropertyBuilder(property) && property?.type === "map" && property.properties) return { [key]: {
|
|
186
|
+
...property,
|
|
187
|
+
properties: sortProperties(property.properties, property.propertiesOrder)
|
|
188
|
+
} };
|
|
189
|
+
else return { [key]: property };
|
|
190
|
+
}).reduce((a, b) => ({
|
|
191
|
+
...a,
|
|
192
|
+
...b
|
|
193
|
+
}), {});
|
|
194
|
+
const validOrderKeys = propertiesOrder.filter((key) => {
|
|
195
|
+
return !key.includes(".") && properties[key];
|
|
196
|
+
});
|
|
197
|
+
const processedKeys = new Set(validOrderKeys);
|
|
198
|
+
const orderedResult = validOrderKeys.map((key) => {
|
|
199
|
+
const property = properties[key];
|
|
200
|
+
if (!isPropertyBuilder(property) && property?.type === "map" && property.properties) return { [key]: {
|
|
201
|
+
...property,
|
|
202
|
+
properties: sortProperties(property.properties, property.propertiesOrder)
|
|
203
|
+
} };
|
|
204
|
+
else return { [key]: property };
|
|
205
|
+
}).reduce((a, b) => ({
|
|
206
|
+
...a,
|
|
207
|
+
...b
|
|
208
|
+
}), {});
|
|
209
|
+
const missingProperties = propertiesKeys.filter((key) => !processedKeys.has(key)).map((key) => {
|
|
210
|
+
const property = properties[key];
|
|
211
|
+
if (!isPropertyBuilder(property) && property?.type === "map" && property.properties) return { [key]: {
|
|
212
|
+
...property,
|
|
213
|
+
properties: sortProperties(property.properties, property.propertiesOrder)
|
|
214
|
+
} };
|
|
215
|
+
else return { [key]: property };
|
|
216
|
+
}).reduce((a, b) => ({
|
|
217
|
+
...a,
|
|
218
|
+
...b
|
|
219
|
+
}), {});
|
|
220
|
+
return {
|
|
221
|
+
...orderedResult,
|
|
222
|
+
...missingProperties
|
|
223
|
+
};
|
|
224
|
+
} catch (e) {
|
|
225
|
+
console.error("Error sorting properties", e);
|
|
226
|
+
return properties;
|
|
227
|
+
}
|
|
258
228
|
}
|
|
259
229
|
function resolveDefaultSelectedView(defaultSelectedView, params) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return defaultSelectedView;
|
|
264
|
-
} else {
|
|
265
|
-
return defaultSelectedView(params);
|
|
266
|
-
}
|
|
230
|
+
if (!defaultSelectedView) return;
|
|
231
|
+
else if (typeof defaultSelectedView === "string") return defaultSelectedView;
|
|
232
|
+
else return defaultSelectedView(params);
|
|
267
233
|
}
|
|
268
234
|
function getLocalChangesBackup(collection) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
235
|
+
if (!collection.localChangesBackup) return "manual_apply";
|
|
236
|
+
return collection.localChangesBackup;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Returns the primary keys for an entity collection by inspecting the properties
|
|
240
|
+
* and finding any properties with `isId`.
|
|
241
|
+
* Fallbacks to `["id"]` if no properties are marked as `isId: true`.
|
|
242
|
+
* @param collection
|
|
243
|
+
*/
|
|
274
244
|
function getPrimaryKeys(collection) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return ["id"];
|
|
284
|
-
}
|
|
245
|
+
const properties = collection.properties;
|
|
246
|
+
if (!properties) return ["id"];
|
|
247
|
+
const ids = Object.entries(properties).filter(([key, prop]) => typeof prop === "object" && prop !== null && "isId" in prop && Boolean(prop.isId)).map(([key]) => key);
|
|
248
|
+
if (ids.length > 0) return ids;
|
|
249
|
+
return ["id"];
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/util/enums.ts
|
|
285
253
|
function enumToObjectEntries(enumValues) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
...value,
|
|
298
|
-
id
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
}
|
|
254
|
+
if (Array.isArray(enumValues)) return enumValues;
|
|
255
|
+
else return Object.entries(enumValues).map(([id, value]) => {
|
|
256
|
+
if (typeof value === "string") return {
|
|
257
|
+
id,
|
|
258
|
+
label: value
|
|
259
|
+
};
|
|
260
|
+
else return {
|
|
261
|
+
...value,
|
|
262
|
+
id
|
|
263
|
+
};
|
|
264
|
+
});
|
|
303
265
|
}
|
|
304
266
|
function getLabelOrConfigFrom(enumValues, key) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
267
|
+
if (key === null || key === void 0) return void 0;
|
|
268
|
+
return enumValues.find((entry) => String(entry.id) === String(key));
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/util/paths.ts
|
|
272
|
+
var COLLECTION_PATH_SEPARATOR = "::";
|
|
273
|
+
/**
|
|
274
|
+
* Remove the entity ids from a given path
|
|
275
|
+
* `products/B44RG6APH/locales` => `products::locales`
|
|
276
|
+
* @param path
|
|
277
|
+
*/
|
|
309
278
|
function stripCollectionPath(path) {
|
|
310
|
-
|
|
279
|
+
return segmentsToStrippedPath(fullPathToCollectionSegments(path));
|
|
311
280
|
}
|
|
312
281
|
function segmentsToStrippedPath(paths) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
282
|
+
if (paths.length === 1) return paths[0];
|
|
283
|
+
return paths.reduce((a, b) => `${a}::${b}`);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Extract the collection path routes
|
|
287
|
+
* `products/B44RG6APH/locales` => [`products`, `locales`]
|
|
288
|
+
* @param path
|
|
289
|
+
*/
|
|
316
290
|
function fullPathToCollectionSegments(path) {
|
|
317
|
-
|
|
291
|
+
return path.split("/").filter((e, i) => i % 2 === 0);
|
|
318
292
|
}
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/util/relations.ts
|
|
319
295
|
function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
} else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
|
|
413
|
-
let isManyToManyInverse = false;
|
|
414
|
-
if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) {
|
|
415
|
-
try {
|
|
416
|
-
const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
|
|
417
|
-
for (const targetRel of targetRelations) {
|
|
418
|
-
if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
|
|
419
|
-
isManyToManyInverse = true;
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
if (!isManyToManyInverse && targetCollection.properties) {
|
|
424
|
-
for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
|
|
425
|
-
if (prop.type !== "relation") continue;
|
|
426
|
-
const relProp = prop;
|
|
427
|
-
const relName = relProp.relationName || propKey;
|
|
428
|
-
if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
|
|
429
|
-
isManyToManyInverse = true;
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
} catch (e) {
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (!isManyToManyInverse && !newRelation.foreignKeyOnTarget) {
|
|
438
|
-
newRelation.foreignKeyOnTarget = generateForeignKeyName(sourceName);
|
|
439
|
-
}
|
|
440
|
-
} else if (newRelation.cardinality === "many" && newRelation.direction === "owning") {
|
|
441
|
-
const sourceTableName = getTableName(sourceCollection);
|
|
442
|
-
const targetTableName = getTableName(targetCollection);
|
|
443
|
-
newRelation.through = {
|
|
444
|
-
table: newRelation.through?.table ?? [sourceTableName, targetTableName].sort().join("_"),
|
|
445
|
-
sourceColumn: newRelation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
|
|
446
|
-
targetColumn: newRelation.through?.targetColumn ?? generateForeignKeyName(newRelation.relationName)
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (newRelation.cardinality === "one" && newRelation.direction === "owning" && !newRelation.localKey && !newRelation.joinPath) {
|
|
451
|
-
throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'owning' one-to-one relation requires a 'localKey'. Check the relation config for '${newRelation.relationName}'`);
|
|
452
|
-
}
|
|
453
|
-
if (newRelation.cardinality === "one" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) {
|
|
454
|
-
throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-one relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
|
|
455
|
-
}
|
|
456
|
-
if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath && !newRelation.inverseRelationName) {
|
|
457
|
-
throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-many relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
|
|
458
|
-
}
|
|
459
|
-
return newRelation;
|
|
460
|
-
}
|
|
461
|
-
const _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
|
|
296
|
+
if (!relation.target) throw new Error("Relation is missing a `target` collection.");
|
|
297
|
+
const rawTarget = relation.target;
|
|
298
|
+
let targetCollection;
|
|
299
|
+
if (typeof rawTarget === "string") {
|
|
300
|
+
if (resolveCollection) targetCollection = resolveCollection(rawTarget);
|
|
301
|
+
if (!targetCollection) targetCollection = {
|
|
302
|
+
slug: rawTarget,
|
|
303
|
+
name: rawTarget
|
|
304
|
+
};
|
|
305
|
+
} else if (typeof rawTarget === "function") {
|
|
306
|
+
const evaluated = rawTarget();
|
|
307
|
+
if (typeof evaluated === "string") {
|
|
308
|
+
if (resolveCollection) targetCollection = resolveCollection(evaluated);
|
|
309
|
+
if (!targetCollection) targetCollection = {
|
|
310
|
+
slug: evaluated,
|
|
311
|
+
name: evaluated
|
|
312
|
+
};
|
|
313
|
+
} else targetCollection = evaluated;
|
|
314
|
+
} else if (rawTarget && typeof rawTarget === "object") targetCollection = rawTarget;
|
|
315
|
+
if (!targetCollection) throw new Error("Relation is missing a valid `target` collection.");
|
|
316
|
+
const newRelation = { ...relation };
|
|
317
|
+
newRelation.target = () => {
|
|
318
|
+
if (typeof rawTarget === "string") return resolveCollection && resolveCollection(rawTarget) || targetCollection;
|
|
319
|
+
else if (typeof rawTarget === "function") {
|
|
320
|
+
const evaluated = rawTarget();
|
|
321
|
+
if (typeof evaluated === "string") return resolveCollection && resolveCollection(evaluated) || targetCollection;
|
|
322
|
+
return evaluated;
|
|
323
|
+
}
|
|
324
|
+
return targetCollection;
|
|
325
|
+
};
|
|
326
|
+
if (!newRelation.relationName) newRelation.relationName = toSnakeCase(targetCollection.slug);
|
|
327
|
+
if (!newRelation.direction) if (newRelation.foreignKeyOnTarget) newRelation.direction = "inverse";
|
|
328
|
+
else if (newRelation.through) newRelation.direction = "owning";
|
|
329
|
+
else if (newRelation.cardinality === "many") newRelation.direction = "inverse";
|
|
330
|
+
else newRelation.direction = "owning";
|
|
331
|
+
if (!newRelation.joinPath) {
|
|
332
|
+
const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
|
|
333
|
+
if (newRelation.cardinality === "one" && newRelation.direction === "owning") {
|
|
334
|
+
if (!newRelation.localKey) newRelation.localKey = generateForeignKeyName(newRelation.relationName);
|
|
335
|
+
} else if (newRelation.cardinality === "one" && newRelation.direction === "inverse") {
|
|
336
|
+
if (!newRelation.foreignKeyOnTarget) {
|
|
337
|
+
let foundForeignKey = false;
|
|
338
|
+
try {
|
|
339
|
+
const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
|
|
340
|
+
for (const targetRel of targetRelations) if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) try {
|
|
341
|
+
if (targetRel.target().slug === sourceCollection.slug) {
|
|
342
|
+
newRelation.foreignKeyOnTarget = targetRel.localKey;
|
|
343
|
+
foundForeignKey = true;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
} catch (e) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
} catch (e) {}
|
|
350
|
+
if (!foundForeignKey) newRelation.foreignKeyOnTarget = generateForeignKeyName(newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName);
|
|
351
|
+
}
|
|
352
|
+
} else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
|
|
353
|
+
let isManyToManyInverse = false;
|
|
354
|
+
if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) try {
|
|
355
|
+
const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
|
|
356
|
+
for (const targetRel of targetRelations) if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
|
|
357
|
+
isManyToManyInverse = true;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
if (!isManyToManyInverse && targetCollection.properties) for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
|
|
361
|
+
if (prop.type !== "relation") continue;
|
|
362
|
+
const relProp = prop;
|
|
363
|
+
if ((relProp.relationName || propKey) === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
|
|
364
|
+
isManyToManyInverse = true;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} catch (e) {}
|
|
369
|
+
if (!isManyToManyInverse && !newRelation.foreignKeyOnTarget) newRelation.foreignKeyOnTarget = generateForeignKeyName(sourceName);
|
|
370
|
+
} else if (newRelation.cardinality === "many" && newRelation.direction === "owning") {
|
|
371
|
+
const sourceTableName = getTableName(sourceCollection);
|
|
372
|
+
const targetTableName = getTableName(targetCollection);
|
|
373
|
+
newRelation.through = {
|
|
374
|
+
table: newRelation.through?.table ?? [sourceTableName, targetTableName].sort().join("_"),
|
|
375
|
+
sourceColumn: newRelation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
|
|
376
|
+
targetColumn: newRelation.through?.targetColumn ?? generateForeignKeyName(newRelation.relationName)
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (newRelation.cardinality === "one" && newRelation.direction === "owning" && !newRelation.localKey && !newRelation.joinPath) throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'owning' one-to-one relation requires a 'localKey'. Check the relation config for '${newRelation.relationName}'`);
|
|
381
|
+
if (newRelation.cardinality === "one" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-one relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
|
|
382
|
+
if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath && !newRelation.inverseRelationName) throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-many relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
|
|
383
|
+
return newRelation;
|
|
384
|
+
}
|
|
385
|
+
/** WeakMap cache — same collection instance always yields the same relation map. */
|
|
386
|
+
var _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
|
|
462
387
|
function resolveCollectionRelations(collection) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
cardinality: relProp.cardinality || "one",
|
|
515
|
-
direction: relProp.direction || "owning",
|
|
516
|
-
inverseRelationName: relProp.inverseRelationName,
|
|
517
|
-
localKey: relProp.localKey,
|
|
518
|
-
foreignKeyOnTarget: relProp.foreignKeyOnTarget,
|
|
519
|
-
through: relProp.through,
|
|
520
|
-
joinPath: relProp.joinPath,
|
|
521
|
-
onUpdate: relProp.onUpdate,
|
|
522
|
-
onDelete: relProp.onDelete,
|
|
523
|
-
overrides: relProp.overrides
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
527
|
-
return void 0;
|
|
388
|
+
const cached = _resolvedRelationsCache.get(collection);
|
|
389
|
+
if (cached) return cached;
|
|
390
|
+
if (!getDataSourceCapabilities(collection.driver).supportsRelations) return {};
|
|
391
|
+
const relCollection = collection;
|
|
392
|
+
const relations = {};
|
|
393
|
+
const registeredRelationNames = /* @__PURE__ */ new Set();
|
|
394
|
+
if (relCollection.relations) relCollection.relations.forEach((relation) => {
|
|
395
|
+
try {
|
|
396
|
+
const normalizedRelation = sanitizeRelation(relation, collection);
|
|
397
|
+
const relationKey = normalizedRelation.relationName;
|
|
398
|
+
if (relationKey) {
|
|
399
|
+
relations[relationKey] = normalizedRelation;
|
|
400
|
+
registeredRelationNames.add(relationKey);
|
|
401
|
+
}
|
|
402
|
+
} catch (e) {}
|
|
403
|
+
});
|
|
404
|
+
if (collection.properties) Object.entries(collection.properties).forEach(([propKey, prop]) => {
|
|
405
|
+
const relation = resolvePropertyRelation({
|
|
406
|
+
propertyKey: propKey,
|
|
407
|
+
property: prop,
|
|
408
|
+
sourceCollection: collection
|
|
409
|
+
});
|
|
410
|
+
if (relation) {
|
|
411
|
+
if (relations[propKey]) return;
|
|
412
|
+
if (!relation.relationName) relation.relationName = propKey;
|
|
413
|
+
const normalizedRelation = sanitizeRelation(relation, collection);
|
|
414
|
+
relations[propKey] = normalizedRelation;
|
|
415
|
+
registeredRelationNames.add(normalizedRelation.relationName ?? propKey);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
_resolvedRelationsCache.set(collection, relations);
|
|
419
|
+
return relations;
|
|
420
|
+
}
|
|
421
|
+
function resolvePropertyRelation({ propertyKey, property, sourceCollection }) {
|
|
422
|
+
if (property.type !== "relation") return void 0;
|
|
423
|
+
const relProp = property;
|
|
424
|
+
if (relProp.target) return {
|
|
425
|
+
relationName: relProp.relationName || propertyKey,
|
|
426
|
+
target: relProp.target,
|
|
427
|
+
cardinality: relProp.cardinality || "one",
|
|
428
|
+
direction: relProp.direction || "owning",
|
|
429
|
+
inverseRelationName: relProp.inverseRelationName,
|
|
430
|
+
localKey: relProp.localKey,
|
|
431
|
+
foreignKeyOnTarget: relProp.foreignKeyOnTarget,
|
|
432
|
+
through: relProp.through,
|
|
433
|
+
joinPath: relProp.joinPath,
|
|
434
|
+
onUpdate: relProp.onUpdate,
|
|
435
|
+
onDelete: relProp.onDelete,
|
|
436
|
+
overrides: relProp.overrides
|
|
437
|
+
};
|
|
438
|
+
console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
528
439
|
}
|
|
529
440
|
function getTableName(collection) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
|
|
441
|
+
if (getDataSourceCapabilities(collection.driver).supportsRelations) return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
|
|
442
|
+
return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
|
|
534
443
|
}
|
|
535
444
|
function getTableVarName(tableName) {
|
|
536
|
-
|
|
445
|
+
return tableName.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
|
|
537
446
|
}
|
|
538
447
|
function getEnumVarName(tableName, propName) {
|
|
539
|
-
|
|
540
|
-
const propVar = propName.charAt(0).toUpperCase() + propName.slice(1);
|
|
541
|
-
return `${tableVar}${propVar}`;
|
|
448
|
+
return `${getTableVarName(tableName)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`;
|
|
542
449
|
}
|
|
543
450
|
function getColumnName(fullColumn) {
|
|
544
|
-
|
|
545
|
-
}
|
|
451
|
+
return fullColumn.includes(".") ? fullColumn.split(".").pop() : fullColumn;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Look up a relation by key with forgiving normalization.
|
|
455
|
+
*
|
|
456
|
+
* `resolveCollectionRelations` stores each relation under a single canonical
|
|
457
|
+
* key (no aliases). This helper tries the given key as-is, then falls back to
|
|
458
|
+
* slug form (underscores → hyphens) and snake_case form (hyphens → underscores)
|
|
459
|
+
* so that callers that receive a key from external input (URL path segments,
|
|
460
|
+
* user-provided config, etc.) can still find the right entry.
|
|
461
|
+
*/
|
|
546
462
|
function findRelation(resolvedRelations, key) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
463
|
+
if (resolvedRelations[key]) return resolvedRelations[key];
|
|
464
|
+
const slugKey = key.replace(/_/g, "-");
|
|
465
|
+
if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];
|
|
466
|
+
const snakeKey = key.replace(/-/g, "_");
|
|
467
|
+
if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
|
|
468
|
+
}
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region src/util/resolutions.ts
|
|
554
471
|
function resolveProperty(props) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (customField.property) {
|
|
622
|
-
const restConfigProperty = {
|
|
623
|
-
...customField.property
|
|
624
|
-
};
|
|
625
|
-
delete restConfigProperty.propertyConfig;
|
|
626
|
-
const customFieldProperty = resolveProperty({
|
|
627
|
-
property: {
|
|
628
|
-
name: "",
|
|
629
|
-
...restConfigProperty
|
|
630
|
-
},
|
|
631
|
-
ignoreMissingFields,
|
|
632
|
-
...rest
|
|
633
|
-
});
|
|
634
|
-
if (customFieldProperty) {
|
|
635
|
-
resolvedProperty = mergeDeep(customFieldProperty, resolvedProperty);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return resolvedProperty;
|
|
472
|
+
const { property, ignoreMissingFields = false, ...rest } = props;
|
|
473
|
+
let resultProperty;
|
|
474
|
+
if (isPropertyBuilder(property)) {
|
|
475
|
+
const path = rest.path;
|
|
476
|
+
if (!path) resultProperty = property;
|
|
477
|
+
else {
|
|
478
|
+
const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : void 0;
|
|
479
|
+
const dynamicProps = property.dynamicProps?.({
|
|
480
|
+
...rest,
|
|
481
|
+
path,
|
|
482
|
+
propertyValue: usedPropertyValue,
|
|
483
|
+
values: rest.values ?? {},
|
|
484
|
+
previousValues: rest.previousValues ?? rest.values ?? {}
|
|
485
|
+
});
|
|
486
|
+
resultProperty = mergeDeep(property, dynamicProps ?? {});
|
|
487
|
+
}
|
|
488
|
+
} else resultProperty = property;
|
|
489
|
+
if (resultProperty?.dynamicProps && rest.path) {
|
|
490
|
+
const path = rest.path;
|
|
491
|
+
const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : void 0;
|
|
492
|
+
const dynamicPropsResult = resultProperty.dynamicProps({
|
|
493
|
+
...rest,
|
|
494
|
+
path,
|
|
495
|
+
propertyValue: usedPropertyValue,
|
|
496
|
+
values: rest.values ?? {},
|
|
497
|
+
previousValues: rest.previousValues ?? rest.values ?? {}
|
|
498
|
+
});
|
|
499
|
+
if (dynamicPropsResult) resultProperty = mergeDeep(resultProperty, dynamicPropsResult);
|
|
500
|
+
}
|
|
501
|
+
let resolvedProperty;
|
|
502
|
+
if (resultProperty?.type === "map" && resultProperty.properties) {
|
|
503
|
+
const properties = resolveProperties({
|
|
504
|
+
ignoreMissingFields,
|
|
505
|
+
...rest,
|
|
506
|
+
properties: resultProperty.properties
|
|
507
|
+
});
|
|
508
|
+
resolvedProperty = {
|
|
509
|
+
...resultProperty,
|
|
510
|
+
properties
|
|
511
|
+
};
|
|
512
|
+
} else if (resultProperty?.type === "array") resolvedProperty = resultProperty;
|
|
513
|
+
else if ((resultProperty?.type === "string" || resultProperty?.type === "number") && resultProperty.enum) resolvedProperty = resolvePropertyEnum(resultProperty);
|
|
514
|
+
else resolvedProperty = resultProperty;
|
|
515
|
+
if (resolvedProperty?.propertyConfig && !isDefaultFieldConfigId(resolvedProperty.propertyConfig)) {
|
|
516
|
+
const cmsFields = rest.propertyConfigs;
|
|
517
|
+
if (!cmsFields && !ignoreMissingFields) throw Error(`Trying to resolve a property with key '${resolvedProperty.propertyConfig}' that inherits from a custom property config but no custom property configs were provided. Use the property 'propertyConfigs' in your app config to provide them`);
|
|
518
|
+
const customField = cmsFields?.[resolvedProperty.propertyConfig];
|
|
519
|
+
if (!customField) {
|
|
520
|
+
console.warn(`Trying to resolve a property with key '${resolvedProperty.propertyConfig}' that inherits from a custom property config but no custom property config with that key was found. Check the 'propertyConfigs' in your app config`);
|
|
521
|
+
return resolvedProperty;
|
|
522
|
+
}
|
|
523
|
+
if (customField.property) {
|
|
524
|
+
const restConfigProperty = { ...customField.property };
|
|
525
|
+
delete restConfigProperty.propertyConfig;
|
|
526
|
+
const customFieldProperty = resolveProperty({
|
|
527
|
+
property: {
|
|
528
|
+
name: "",
|
|
529
|
+
...restConfigProperty
|
|
530
|
+
},
|
|
531
|
+
ignoreMissingFields,
|
|
532
|
+
...rest
|
|
533
|
+
});
|
|
534
|
+
if (customFieldProperty) resolvedProperty = mergeDeep(customFieldProperty, resolvedProperty);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return resolvedProperty;
|
|
640
538
|
}
|
|
641
539
|
function resolveRelationProperty(property, relations, propertyKey) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
540
|
+
if (property.relation) return property;
|
|
541
|
+
const name = property.relationName || propertyKey;
|
|
542
|
+
const relation = name ? relations.find((rel) => rel.relationName === name) : void 0;
|
|
543
|
+
if (!relation) throw Error(`Relation ${name ?? "(unnamed)"} not found`);
|
|
544
|
+
return {
|
|
545
|
+
...property,
|
|
546
|
+
relation
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Resolve enum aliases for a string or number property
|
|
551
|
+
* @param property
|
|
552
|
+
*/
|
|
655
553
|
function resolvePropertyEnum(property) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
})
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
...props
|
|
738
|
-
});
|
|
739
|
-
}).filter((e) => Boolean(e)) : [];
|
|
740
|
-
return resolvedProperties;
|
|
741
|
-
} else if (!("Field" in (property.ui || {}) && property.ui?.Field)) {
|
|
742
|
-
throw Error(`The array property (${propertyKey}) needs to declare an 'of' or a 'oneOf' property, or provide a custom \`Field\` component`);
|
|
743
|
-
} else {
|
|
744
|
-
return [];
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
function getArrayResolvedProperties({
|
|
748
|
-
propertyKey,
|
|
749
|
-
propertyValue,
|
|
750
|
-
property,
|
|
751
|
-
...props
|
|
752
|
-
}) {
|
|
753
|
-
const of = property.of;
|
|
754
|
-
if (!of) throw Error(`Trying to resolve an array property (${propertyKey}) without providing an 'of' property`);
|
|
755
|
-
return Array.isArray(propertyValue) ? propertyValue.map((v, index) => {
|
|
756
|
-
return resolveProperty({
|
|
757
|
-
propertyKey: `${propertyKey}.${index}`,
|
|
758
|
-
property: Array.isArray(of) ? of[index] : of,
|
|
759
|
-
...props,
|
|
760
|
-
index
|
|
761
|
-
});
|
|
762
|
-
}).filter((e) => Boolean(e)) : [];
|
|
554
|
+
if (typeof property.enum === "object") return {
|
|
555
|
+
...property,
|
|
556
|
+
enum: enumToObjectEntries(property.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? []
|
|
557
|
+
};
|
|
558
|
+
return property;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Resolve enums and arrays for properties
|
|
562
|
+
* @param properties
|
|
563
|
+
* @param value
|
|
564
|
+
*/
|
|
565
|
+
function resolveProperties({ propertyKey, properties, ignoreMissingFields, ...props }) {
|
|
566
|
+
return Object.entries(properties).map(([key, property]) => {
|
|
567
|
+
const childResolvedProperty = resolveProperty({
|
|
568
|
+
propertyKey: propertyKey ? `${propertyKey}.${key}` : void 0,
|
|
569
|
+
property,
|
|
570
|
+
ignoreMissingFields,
|
|
571
|
+
...props
|
|
572
|
+
});
|
|
573
|
+
if (!childResolvedProperty) return {};
|
|
574
|
+
return { [key]: childResolvedProperty };
|
|
575
|
+
}).filter((a) => a !== null).reduce((a, b) => ({
|
|
576
|
+
...a,
|
|
577
|
+
...b
|
|
578
|
+
}), {});
|
|
579
|
+
}
|
|
580
|
+
function resolveArrayProperties({ propertyKey, property, ignoreMissingFields = false, ...props }) {
|
|
581
|
+
const propertyValue = propertyKey ? getIn(props.values, propertyKey) : void 0;
|
|
582
|
+
if (property.of) if (Array.isArray(property.of)) return property.of.map((p, index) => {
|
|
583
|
+
return resolveProperty({
|
|
584
|
+
propertyKey: `${propertyKey}.${index}`,
|
|
585
|
+
property: p,
|
|
586
|
+
ignoreMissingFields,
|
|
587
|
+
...props,
|
|
588
|
+
index
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
else {
|
|
592
|
+
const of = property.of;
|
|
593
|
+
const resolvedProperties = getArrayResolvedProperties({
|
|
594
|
+
propertyValue,
|
|
595
|
+
propertyKey,
|
|
596
|
+
property,
|
|
597
|
+
ignoreMissingFields,
|
|
598
|
+
...props
|
|
599
|
+
});
|
|
600
|
+
const { values, previousValues, ...rest } = props;
|
|
601
|
+
if (!resolveProperty({
|
|
602
|
+
property: of,
|
|
603
|
+
ignoreMissingFields,
|
|
604
|
+
...rest
|
|
605
|
+
}) && !ignoreMissingFields) throw Error("When using a property builder as the 'of' prop of an ArrayProperty, you must return a valid child property");
|
|
606
|
+
return resolvedProperties;
|
|
607
|
+
}
|
|
608
|
+
else if (property.oneOf) {
|
|
609
|
+
const typeField = property.oneOf?.typeField ?? "type";
|
|
610
|
+
return Array.isArray(propertyValue) ? propertyValue.map((v, index) => {
|
|
611
|
+
const type = v && v[typeField];
|
|
612
|
+
const childProperty = property.oneOf?.properties[type];
|
|
613
|
+
if (!type || !childProperty) return null;
|
|
614
|
+
return resolveProperty({
|
|
615
|
+
propertyKey: `${propertyKey}.${index}`,
|
|
616
|
+
property: childProperty,
|
|
617
|
+
ignoreMissingFields,
|
|
618
|
+
...props
|
|
619
|
+
});
|
|
620
|
+
}).filter((e) => Boolean(e)) : [];
|
|
621
|
+
} else if (!("Field" in (property.ui || {}) && property.ui?.Field)) throw Error(`The array property (${propertyKey}) needs to declare an 'of' or a 'oneOf' property, or provide a custom \`Field\` component`);
|
|
622
|
+
else return [];
|
|
623
|
+
}
|
|
624
|
+
function getArrayResolvedProperties({ propertyKey, propertyValue, property, ...props }) {
|
|
625
|
+
const of = property.of;
|
|
626
|
+
if (!of) throw Error(`Trying to resolve an array property (${propertyKey}) without providing an 'of' property`);
|
|
627
|
+
return Array.isArray(propertyValue) ? propertyValue.map((v, index) => {
|
|
628
|
+
return resolveProperty({
|
|
629
|
+
propertyKey: `${propertyKey}.${index}`,
|
|
630
|
+
property: Array.isArray(of) ? of[index] : of,
|
|
631
|
+
...props,
|
|
632
|
+
index
|
|
633
|
+
});
|
|
634
|
+
}).filter((e) => Boolean(e)) : [];
|
|
763
635
|
}
|
|
764
636
|
function resolveEnumValues(input) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
return input;
|
|
772
|
-
} else {
|
|
773
|
-
return void 0;
|
|
774
|
-
}
|
|
637
|
+
if (typeof input === "object") return Object.entries(input).map(([id, value]) => typeof value === "string" ? {
|
|
638
|
+
id,
|
|
639
|
+
label: value
|
|
640
|
+
} : value);
|
|
641
|
+
else if (Array.isArray(input)) return input;
|
|
642
|
+
else return;
|
|
775
643
|
}
|
|
776
644
|
function getSubcollections(collection) {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
...baseOverrides
|
|
807
|
-
};
|
|
808
|
-
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
809
|
-
}).filter((c) => Boolean(c));
|
|
810
|
-
}
|
|
811
|
-
return [];
|
|
812
|
-
}
|
|
645
|
+
if (collection.childCollections) return collection.childCollections() ?? [];
|
|
646
|
+
if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) return collection.subcollections() ?? [];
|
|
647
|
+
if (getDataSourceCapabilities(collection.driver).supportsRelations) {
|
|
648
|
+
const resolvedRelations = resolveCollectionRelations(collection);
|
|
649
|
+
return Object.values(resolvedRelations).filter((r) => r.cardinality === "many").map((r) => {
|
|
650
|
+
const target = r.target();
|
|
651
|
+
if (!target) return void 0;
|
|
652
|
+
const relationKey = r.relationName || target.slug;
|
|
653
|
+
let customName;
|
|
654
|
+
if (collection.properties) {
|
|
655
|
+
const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
|
|
656
|
+
if (prop && prop[1].name) customName = prop[1].name;
|
|
657
|
+
}
|
|
658
|
+
const baseOverrides = { slug: relationKey };
|
|
659
|
+
if (customName) {
|
|
660
|
+
baseOverrides.name = customName;
|
|
661
|
+
baseOverrides.singularName = customName;
|
|
662
|
+
}
|
|
663
|
+
const targetWithOverrides = {
|
|
664
|
+
...target,
|
|
665
|
+
...baseOverrides
|
|
666
|
+
};
|
|
667
|
+
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
668
|
+
}).filter((c) => Boolean(c));
|
|
669
|
+
}
|
|
670
|
+
return [];
|
|
671
|
+
}
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region src/util/permissions.ts
|
|
813
674
|
function evaluateAST(sqlString, auth, entity) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
if (match2 && match2[1]) {
|
|
885
|
-
return entity.values[match2[1]] === auth.user?.uid;
|
|
886
|
-
}
|
|
887
|
-
const simpleEqualityMatch = cleanedSQL.match(/^\{?([\w_]+)\}?\s*(=|!=)\s*'([^']+)'$/i);
|
|
888
|
-
if (simpleEqualityMatch) {
|
|
889
|
-
const field = simpleEqualityMatch[1];
|
|
890
|
-
const operator = simpleEqualityMatch[2];
|
|
891
|
-
const value = simpleEqualityMatch[3];
|
|
892
|
-
const entityValue = entity.values[field];
|
|
893
|
-
if (operator === "=") return entityValue === value;
|
|
894
|
-
if (operator === "!=") return entityValue !== value;
|
|
895
|
-
}
|
|
896
|
-
return true;
|
|
675
|
+
if (!entity) return true;
|
|
676
|
+
let cleanedSQL = sqlString.trim();
|
|
677
|
+
while (cleanedSQL.startsWith("(") && cleanedSQL.endsWith(")")) {
|
|
678
|
+
let openCount = 0;
|
|
679
|
+
let isEnclosing = true;
|
|
680
|
+
for (let i = 0; i < cleanedSQL.length - 1; i++) {
|
|
681
|
+
if (cleanedSQL[i] === "(") openCount++;
|
|
682
|
+
else if (cleanedSQL[i] === ")") openCount--;
|
|
683
|
+
if (openCount === 0) {
|
|
684
|
+
isEnclosing = false;
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (isEnclosing) cleanedSQL = cleanedSQL.substring(1, cleanedSQL.length - 1).trim();
|
|
689
|
+
else break;
|
|
690
|
+
}
|
|
691
|
+
const splitByTopLevel = (str, delimiter) => {
|
|
692
|
+
const parts = [];
|
|
693
|
+
let current = "";
|
|
694
|
+
let openCount = 0;
|
|
695
|
+
let i = 0;
|
|
696
|
+
while (i < str.length) {
|
|
697
|
+
if (str[i] === "(") openCount++;
|
|
698
|
+
else if (str[i] === ")") openCount--;
|
|
699
|
+
if (openCount === 0 && str.substring(i).toUpperCase().startsWith(delimiter)) {
|
|
700
|
+
parts.push(current);
|
|
701
|
+
current = "";
|
|
702
|
+
i += delimiter.length;
|
|
703
|
+
} else {
|
|
704
|
+
current += str[i];
|
|
705
|
+
i++;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
parts.push(current);
|
|
709
|
+
return parts;
|
|
710
|
+
};
|
|
711
|
+
const orParts = splitByTopLevel(cleanedSQL, " OR ");
|
|
712
|
+
if (orParts.length > 1) return orParts.some((part) => evaluateAST(part, auth, entity));
|
|
713
|
+
const andParts = splitByTopLevel(cleanedSQL, " AND ");
|
|
714
|
+
if (andParts.length > 1) return andParts.every((part) => evaluateAST(part, auth, entity));
|
|
715
|
+
const upperSQL = cleanedSQL.toUpperCase();
|
|
716
|
+
if (upperSQL.includes(" IN ") || upperSQL.includes(" EXISTS ")) return true;
|
|
717
|
+
const roleIntersectMatch = cleanedSQL.match(/string_to_array\s*\(\s*auth\.roles\(\)\s*,\s*','\s*\)\s*&&\s*ARRAY\[(.*?)\]/i);
|
|
718
|
+
if (roleIntersectMatch && roleIntersectMatch[1]) {
|
|
719
|
+
const requiredRoles = roleIntersectMatch[1].split(",").map((r) => r.trim().replace(/'/g, ""));
|
|
720
|
+
const userRoles = auth.user?.roles || [];
|
|
721
|
+
return requiredRoles.some((r) => userRoles.includes(r));
|
|
722
|
+
}
|
|
723
|
+
const roleContainMatch = cleanedSQL.match(/string_to_array\s*\(\s*auth\.roles\(\)\s*,\s*','\s*\)\s*@>\s*ARRAY\[(.*?)\]/i);
|
|
724
|
+
if (roleContainMatch && roleContainMatch[1]) {
|
|
725
|
+
const requiredRoles = roleContainMatch[1].split(",").map((r) => r.trim().replace(/'/g, ""));
|
|
726
|
+
const userRoles = auth.user?.roles || [];
|
|
727
|
+
return requiredRoles.every((r) => userRoles.includes(r));
|
|
728
|
+
}
|
|
729
|
+
const pattern1 = /* @__PURE__ */ new RegExp("^\\{?([a-zA-Z0-9_]+)\\}?\\s*=\\s*(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))");
|
|
730
|
+
const pattern2 = /* @__PURE__ */ new RegExp("^(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))\\s*=\\s*\\{?([a-zA-Z0-9_]+)\\}?");
|
|
731
|
+
const match1 = cleanedSQL.match(pattern1);
|
|
732
|
+
if (match1 && match1[1]) return entity.values[match1[1]] === auth.user?.uid;
|
|
733
|
+
const match2 = cleanedSQL.match(pattern2);
|
|
734
|
+
if (match2 && match2[1]) return entity.values[match2[1]] === auth.user?.uid;
|
|
735
|
+
const simpleEqualityMatch = cleanedSQL.match(/^\{?([\w_]+)\}?\s*(=|!=)\s*'([^']+)'$/i);
|
|
736
|
+
if (simpleEqualityMatch) {
|
|
737
|
+
const field = simpleEqualityMatch[1];
|
|
738
|
+
const operator = simpleEqualityMatch[2];
|
|
739
|
+
const value = simpleEqualityMatch[3];
|
|
740
|
+
const entityValue = entity.values[field];
|
|
741
|
+
if (operator === "=") return entityValue === value;
|
|
742
|
+
if (operator === "!=") return entityValue !== value;
|
|
743
|
+
}
|
|
744
|
+
return true;
|
|
897
745
|
}
|
|
898
746
|
function evaluateRule(rule, auth, entity) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if (rule.using && !evaluateAST(rule.using, auth, entity)) return false;
|
|
907
|
-
if (rule.withCheck && !evaluateAST(rule.withCheck, auth, entity)) return false;
|
|
908
|
-
return true;
|
|
747
|
+
if (rule.access === "public") return true;
|
|
748
|
+
if (rule.ownerField) {
|
|
749
|
+
if (!entity) {} else if (entity.values[rule.ownerField] !== auth.user?.uid) return false;
|
|
750
|
+
}
|
|
751
|
+
if (rule.using && !evaluateAST(rule.using, auth, entity)) return false;
|
|
752
|
+
if (rule.withCheck && !evaluateAST(rule.withCheck, auth, entity)) return false;
|
|
753
|
+
return true;
|
|
909
754
|
}
|
|
910
755
|
function checkOperation(collection, authContext, entity, targetOperation) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
if (deniedByRestrictive) return false;
|
|
938
|
-
const hasPermissive = roleApplicableRules.some((r) => (r.mode || "permissive") === "permissive");
|
|
939
|
-
if (hasPermissive) {
|
|
940
|
-
return grantedByPermissive;
|
|
941
|
-
} else {
|
|
942
|
-
return false;
|
|
943
|
-
}
|
|
756
|
+
const securityRules = getDataSourceCapabilities(collection.driver).supportsRLS ? collection.securityRules : void 0;
|
|
757
|
+
if (!securityRules || securityRules.length === 0) return true;
|
|
758
|
+
const applicableRules = securityRules.filter((r) => r.operation === targetOperation || r.operation === "all" || r.operations?.includes(targetOperation) || r.operations?.includes("all"));
|
|
759
|
+
if (applicableRules.length === 0) return false;
|
|
760
|
+
const userRoles = [...authContext.user?.roles ?? [], "public"];
|
|
761
|
+
const roleApplicableRules = applicableRules.filter((rule) => {
|
|
762
|
+
if (!rule.roles || rule.roles.length === 0) return true;
|
|
763
|
+
return rule.roles.some((r) => userRoles.includes(r));
|
|
764
|
+
});
|
|
765
|
+
if (roleApplicableRules.length === 0) return false;
|
|
766
|
+
let grantedByPermissive = false;
|
|
767
|
+
let deniedByRestrictive = false;
|
|
768
|
+
for (const rule of roleApplicableRules) {
|
|
769
|
+
const mode = rule.mode || "permissive";
|
|
770
|
+
const passed = evaluateRule(rule, authContext, entity);
|
|
771
|
+
if (mode === "restrictive" && !passed) {
|
|
772
|
+
deniedByRestrictive = true;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
if (mode === "permissive" && passed) grantedByPermissive = true;
|
|
776
|
+
}
|
|
777
|
+
if (deniedByRestrictive) return false;
|
|
778
|
+
if (roleApplicableRules.some((r) => (r.mode || "permissive") === "permissive")) return grantedByPermissive;
|
|
779
|
+
else return false;
|
|
944
780
|
}
|
|
945
781
|
function canReadCollection(collection, authContext) {
|
|
946
|
-
|
|
782
|
+
return checkOperation(collection, authContext, null, "select");
|
|
947
783
|
}
|
|
948
784
|
function canEditEntity(collection, authContext, path, entity) {
|
|
949
|
-
|
|
785
|
+
return checkOperation(collection, authContext, entity, "update");
|
|
950
786
|
}
|
|
951
787
|
function canCreateEntity(collection, authContext, path, entity) {
|
|
952
|
-
|
|
788
|
+
return checkOperation(collection, authContext, entity, "insert");
|
|
953
789
|
}
|
|
954
790
|
function canDeleteEntity(collection, authContext, path, entity) {
|
|
955
|
-
|
|
791
|
+
return checkOperation(collection, authContext, entity, "delete");
|
|
956
792
|
}
|
|
793
|
+
//#endregion
|
|
794
|
+
//#region src/util/references.ts
|
|
957
795
|
function getEntityImagePreviewPropertyKey(collection) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
return key;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
for (const key in collection.properties) {
|
|
989
|
-
const property = collection.properties[key];
|
|
990
|
-
if (property.type === "array" && !Array.isArray(property.of) && property.of?.type === "string" && property.of.storage && !property.of.storage.acceptedFiles) {
|
|
991
|
-
return key;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
return void 0;
|
|
995
|
-
}
|
|
796
|
+
for (const key in collection.properties) {
|
|
797
|
+
const property = collection.properties[key];
|
|
798
|
+
if (property.type === "string" && property.storage?.acceptedFiles?.includes("image/*")) return key;
|
|
799
|
+
}
|
|
800
|
+
for (const key in collection.properties) {
|
|
801
|
+
const property = collection.properties[key];
|
|
802
|
+
if (property.type === "array" && !Array.isArray(property.of) && property.of?.type === "string" && property.of.storage?.acceptedFiles?.includes("image/*")) return key;
|
|
803
|
+
}
|
|
804
|
+
for (const key in collection.properties) {
|
|
805
|
+
const property = collection.properties[key];
|
|
806
|
+
if (property.type === "string" && property.ui?.url === "image") return key;
|
|
807
|
+
}
|
|
808
|
+
for (const key in collection.properties) {
|
|
809
|
+
const property = collection.properties[key];
|
|
810
|
+
if (property.type === "array" && property.of && !Array.isArray(property.of) && property.of.type === "string" && property.of.url === "image") return key;
|
|
811
|
+
}
|
|
812
|
+
for (const key in collection.properties) {
|
|
813
|
+
const property = collection.properties[key];
|
|
814
|
+
if (property.type === "string" && property.storage && !property.storage.acceptedFiles) return key;
|
|
815
|
+
}
|
|
816
|
+
for (const key in collection.properties) {
|
|
817
|
+
const property = collection.properties[key];
|
|
818
|
+
if (property.type === "array" && !Array.isArray(property.of) && property.of?.type === "string" && property.of.storage && !property.of.storage.acceptedFiles) return key;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
//#endregion
|
|
822
|
+
//#region src/util/navigation_utils.ts
|
|
996
823
|
function removeInitialAndTrailingSlashes(s) {
|
|
997
|
-
|
|
824
|
+
return removeInitialSlash(removeTrailingSlash(s));
|
|
998
825
|
}
|
|
999
826
|
function removeInitialSlash(s) {
|
|
1000
|
-
|
|
1001
|
-
|
|
827
|
+
if (s.startsWith("/")) return s.slice(1);
|
|
828
|
+
else return s;
|
|
1002
829
|
}
|
|
1003
830
|
function removeTrailingSlash(s) {
|
|
1004
|
-
|
|
1005
|
-
|
|
831
|
+
if (s.endsWith("/")) return s.slice(0, -1);
|
|
832
|
+
else return s;
|
|
1006
833
|
}
|
|
1007
834
|
function addInitialSlash(s) {
|
|
1008
|
-
|
|
1009
|
-
|
|
835
|
+
if (s.startsWith("/")) return s;
|
|
836
|
+
else return `/${s}`;
|
|
1010
837
|
}
|
|
1011
838
|
function getLastSegment(path) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
839
|
+
const cleanPath = removeInitialAndTrailingSlashes(path);
|
|
840
|
+
if (cleanPath.includes("/")) {
|
|
841
|
+
const segments = cleanPath.split("/");
|
|
842
|
+
return segments[segments.length - 1];
|
|
843
|
+
}
|
|
844
|
+
return cleanPath;
|
|
1018
845
|
}
|
|
1019
846
|
function resolveCollectionPathIds(path, allCollections) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
847
|
+
let remainingPath = removeInitialAndTrailingSlashes(path);
|
|
848
|
+
if (!remainingPath) return "";
|
|
849
|
+
let currentCollections = allCollections;
|
|
850
|
+
const resolvedPathParts = [];
|
|
851
|
+
while (remainingPath.length > 0) {
|
|
852
|
+
if (!currentCollections || currentCollections.length === 0) {
|
|
853
|
+
console.warn(`resolveCollectionPathIds: Path structure implies subcollections, but none found before segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
|
|
854
|
+
resolvedPathParts.push(remainingPath);
|
|
855
|
+
remainingPath = "";
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
let foundMatch = false;
|
|
859
|
+
const potentialMatches = currentCollections.flatMap((col) => [{
|
|
860
|
+
col,
|
|
861
|
+
match: col.slug
|
|
862
|
+
}]).filter((p) => p.match && remainingPath.startsWith(p.match)).sort((a, b) => b.match.length - a.match.length);
|
|
863
|
+
if (potentialMatches.length > 0) {
|
|
864
|
+
const { col: foundCollection, match: matchString } = potentialMatches[0];
|
|
865
|
+
resolvedPathParts.push(foundCollection.slug);
|
|
866
|
+
remainingPath = removeInitialSlash(remainingPath.substring(matchString.length));
|
|
867
|
+
if (remainingPath.length === 0) {
|
|
868
|
+
foundMatch = true;
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
const idSeparatorIndex = remainingPath.indexOf("/");
|
|
872
|
+
let entityId;
|
|
873
|
+
if (idSeparatorIndex > -1) {
|
|
874
|
+
entityId = remainingPath.substring(0, idSeparatorIndex);
|
|
875
|
+
remainingPath = remainingPath.substring(idSeparatorIndex + 1);
|
|
876
|
+
} else {
|
|
877
|
+
entityId = remainingPath;
|
|
878
|
+
remainingPath = "";
|
|
879
|
+
console.warn(`resolveCollectionPathIds: Path seems to end with an entity ID "${entityId}" instead of a collection segment in original path "${path}". This might indicate an invalid input path.`);
|
|
880
|
+
}
|
|
881
|
+
resolvedPathParts.push(entityId);
|
|
882
|
+
currentCollections = getSubcollections(foundCollection);
|
|
883
|
+
foundMatch = true;
|
|
884
|
+
if (!currentCollections && remainingPath.length > 0) {
|
|
885
|
+
console.warn(`resolveCollectionPathIds: Path continues after entity ID "${entityId}", but no subcollections are defined for the preceding collection "${foundCollection.slug}" in path "${path}". Appending remaining original path.`);
|
|
886
|
+
resolvedPathParts.push(remainingPath);
|
|
887
|
+
remainingPath = "";
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (!foundMatch) {
|
|
892
|
+
console.warn(`resolveCollectionPathIds: Collection definition not found for segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
|
|
893
|
+
resolvedPathParts.push(remainingPath);
|
|
894
|
+
remainingPath = "";
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return resolvedPathParts.join("/");
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Find the corresponding view at any depth for a given path.
|
|
902
|
+
* Note that path or segments of the paths can be collection aliases.
|
|
903
|
+
* @param slugOrPath
|
|
904
|
+
* @param collections
|
|
905
|
+
*/
|
|
1078
906
|
function getCollectionBySlugWithin(slugOrPath, collections) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
907
|
+
const subpaths = removeInitialAndTrailingSlashes(slugOrPath).split("/");
|
|
908
|
+
if (subpaths.length % 2 === 0) throw Error(`getCollectionBySlug: Collection paths must have an odd number of segments: ${slugOrPath}`);
|
|
909
|
+
const subpathCombinations = getCollectionPathsCombinations(subpaths);
|
|
910
|
+
let result;
|
|
911
|
+
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
912
|
+
const subpathCombination = subpathCombinations[i];
|
|
913
|
+
const navigationEntry = collections && collections.sort((a, b) => (a.slug ?? "").localeCompare(b.slug ?? "")).find((entry) => entry.slug === subpathCombination);
|
|
914
|
+
if (navigationEntry) {
|
|
915
|
+
if (subpathCombination === slugOrPath) result = navigationEntry;
|
|
916
|
+
else if (getSubcollections(navigationEntry).length > 0) {
|
|
917
|
+
const newPath = slugOrPath.replace(subpathCombination, "").split("/").slice(2).join("/");
|
|
918
|
+
if (newPath.length > 0) result = getCollectionBySlugWithin(newPath, getSubcollections(navigationEntry));
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (result) break;
|
|
922
|
+
}
|
|
923
|
+
return result;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Get the subcollection combinations from a path:
|
|
927
|
+
* "sites/es/locales" => ["sites/es/locales", "sites"]
|
|
928
|
+
* @param subpaths
|
|
929
|
+
*/
|
|
1100
930
|
function getCollectionPathsCombinations(subpaths) {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
931
|
+
const entries = subpaths.length > 0 && subpaths.length % 2 === 0 ? subpaths.splice(0, subpaths.length - 1) : subpaths;
|
|
932
|
+
const length = entries.length;
|
|
933
|
+
const result = [];
|
|
934
|
+
for (let i = length; i > 0; i = i - 2) result.push(entries.slice(0, i).join("/"));
|
|
935
|
+
return result;
|
|
936
|
+
}
|
|
937
|
+
//#endregion
|
|
938
|
+
//#region src/util/navigation_from_path.ts
|
|
1109
939
|
function getNavigationEntriesFromPath(props) {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
currentFullPath: path2,
|
|
1163
|
-
contextEntityViews: props.contextEntityViews
|
|
1164
|
-
}));
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
break;
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
return result;
|
|
940
|
+
const { path, collections = [], currentFullPath } = props;
|
|
941
|
+
const subpathCombinations = getCollectionPathsCombinations(removeInitialAndTrailingSlashes(path).split("/"));
|
|
942
|
+
const result = [];
|
|
943
|
+
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
944
|
+
const subpathCombination = subpathCombinations[i];
|
|
945
|
+
const collection = collections && collections.find((entry) => entry.slug === subpathCombination);
|
|
946
|
+
if (collection) {
|
|
947
|
+
const collectionPath = currentFullPath && currentFullPath.length > 0 ? currentFullPath + "/" + collection.slug : collection.slug;
|
|
948
|
+
result.push({
|
|
949
|
+
type: "collection",
|
|
950
|
+
id: collection.slug,
|
|
951
|
+
slug: collectionPath,
|
|
952
|
+
path: collectionPath,
|
|
953
|
+
collection
|
|
954
|
+
});
|
|
955
|
+
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
956
|
+
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
957
|
+
if (nextSegments.length > 0) {
|
|
958
|
+
const entityId = nextSegments[0];
|
|
959
|
+
const path = collectionPath + "/" + entityId;
|
|
960
|
+
result.push({
|
|
961
|
+
type: "entity",
|
|
962
|
+
entityId,
|
|
963
|
+
slug: collectionPath,
|
|
964
|
+
path,
|
|
965
|
+
parentCollection: collection
|
|
966
|
+
});
|
|
967
|
+
if (nextSegments.length > 1) {
|
|
968
|
+
const newPath = nextSegments.slice(1).join("/");
|
|
969
|
+
if (!collection) throw Error("collection not found resolving path: " + collection);
|
|
970
|
+
const entityViews = collection.entityViews;
|
|
971
|
+
const customView = entityViews && entityViews.map((entry) => resolveEntityView(entry, props.contextEntityViews)).filter((v) => v != null).find((entry) => entry.key === newPath);
|
|
972
|
+
const subcollections = getSubcollections(collection);
|
|
973
|
+
if (customView) result.push({
|
|
974
|
+
type: "custom_view",
|
|
975
|
+
slug: collectionPath,
|
|
976
|
+
entityId,
|
|
977
|
+
path: path + "/" + customView.key,
|
|
978
|
+
view: customView
|
|
979
|
+
});
|
|
980
|
+
else if (subcollections) result.push(...getNavigationEntriesFromPath({
|
|
981
|
+
path: newPath,
|
|
982
|
+
collections: subcollections,
|
|
983
|
+
currentFullPath: path,
|
|
984
|
+
contextEntityViews: props.contextEntityViews
|
|
985
|
+
}));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return result;
|
|
1172
992
|
}
|
|
1173
993
|
function resolveEntityView(entityView, contextEntityViews) {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
} else {
|
|
1177
|
-
return entityView;
|
|
1178
|
-
}
|
|
994
|
+
if (typeof entityView === "string") return contextEntityViews?.find((entry) => entry.key === entityView);
|
|
995
|
+
else return entityView;
|
|
1179
996
|
}
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/util/parent_references_from_path.ts
|
|
1180
999
|
function getParentReferencesFromPath(props) {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
}
|
|
1000
|
+
const { path, collections = [], currentFullPath } = props;
|
|
1001
|
+
const subpathCombinations = getCollectionPathsCombinations(removeInitialAndTrailingSlashes(path).split("/"));
|
|
1002
|
+
const result = [];
|
|
1003
|
+
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
1004
|
+
const subpathCombination = subpathCombinations[i];
|
|
1005
|
+
const collection = collections && collections.find((entry) => entry.slug === subpathCombination);
|
|
1006
|
+
if (collection) {
|
|
1007
|
+
const collectionPath = currentFullPath && currentFullPath.length > 0 ? currentFullPath + "/" + collection.slug : collection.slug;
|
|
1008
|
+
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
1009
|
+
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
1010
|
+
if (nextSegments.length > 0) {
|
|
1011
|
+
const entityId = nextSegments[0];
|
|
1012
|
+
const path = collectionPath + "/" + entityId;
|
|
1013
|
+
result.push(new EntityReference({
|
|
1014
|
+
id: entityId,
|
|
1015
|
+
path: collectionPath
|
|
1016
|
+
}));
|
|
1017
|
+
if (nextSegments.length > 1) {
|
|
1018
|
+
const newPath = nextSegments.slice(1).join("/");
|
|
1019
|
+
if (!collection) throw Error("collection not found resolving path: " + collection);
|
|
1020
|
+
if (getSubcollections(collection).length > 0) result.push(...getParentReferencesFromPath({
|
|
1021
|
+
path: newPath,
|
|
1022
|
+
collections: getSubcollections(collection),
|
|
1023
|
+
currentFullPath: path
|
|
1024
|
+
}));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return result;
|
|
1031
|
+
}
|
|
1032
|
+
//#endregion
|
|
1033
|
+
//#region src/util/builders.ts
|
|
1034
|
+
/**
|
|
1035
|
+
* Identity function we use to defeat the type system of Typescript and build
|
|
1036
|
+
* collection views with all its properties
|
|
1037
|
+
* @param collection
|
|
1038
|
+
* @group Builder
|
|
1039
|
+
*/
|
|
1222
1040
|
function buildCollection(collection) {
|
|
1223
|
-
|
|
1224
|
-
}
|
|
1041
|
+
return collection;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Identity function we use to defeat the type system of Typescript and preserve
|
|
1045
|
+
* the property keys.
|
|
1046
|
+
* @param property
|
|
1047
|
+
* @group Builder
|
|
1048
|
+
*/
|
|
1225
1049
|
function buildProperty(property) {
|
|
1226
|
-
|
|
1227
|
-
}
|
|
1050
|
+
return property;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Identity function we use to defeat the type system of Typescript and preserve
|
|
1054
|
+
* the properties keys.
|
|
1055
|
+
* @param properties
|
|
1056
|
+
* @group Builder
|
|
1057
|
+
*/
|
|
1228
1058
|
function buildProperties(properties) {
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1059
|
+
return properties;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Identity function we use to defeat the type system of Typescript and preserve
|
|
1063
|
+
* the properties keys.
|
|
1064
|
+
* @param propertiesOrBuilder
|
|
1065
|
+
* @group Builder
|
|
1066
|
+
*/
|
|
1231
1067
|
function buildPropertiesOrBuilder(propertiesOrBuilder) {
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1068
|
+
return propertiesOrBuilder;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Identity function we use to defeat the type system of Typescript and preserve
|
|
1072
|
+
* the properties keys.
|
|
1073
|
+
* @param enumValues
|
|
1074
|
+
* @group Builder
|
|
1075
|
+
*/
|
|
1234
1076
|
function buildEnum(enumValues) {
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1077
|
+
return enumValues;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Identity function we use to defeat the type system of Typescript and preserve
|
|
1081
|
+
* the properties keys.
|
|
1082
|
+
* @param enumValueConfig
|
|
1083
|
+
* @group Builder
|
|
1084
|
+
*/
|
|
1237
1085
|
function buildEnumValueConfig(enumValueConfig) {
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1086
|
+
return enumValueConfig;
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Identity function we use to defeat the type system of Typescript and preserve
|
|
1090
|
+
* the properties keys.
|
|
1091
|
+
* @param callbacks
|
|
1092
|
+
* @group Builder
|
|
1093
|
+
*/
|
|
1240
1094
|
function buildEntityCallbacks(callbacks) {
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1095
|
+
return callbacks;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Identity function we use to defeat the type system of Typescript and build
|
|
1099
|
+
* additional field delegates views with all its properties
|
|
1100
|
+
* @param additionalFieldDelegate
|
|
1101
|
+
* @group Builder
|
|
1102
|
+
*/
|
|
1243
1103
|
function buildAdditionalFieldDelegate(additionalFieldDelegate) {
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
return result;
|
|
1313
|
-
}
|
|
1314
|
-
function replacePlaceholders({
|
|
1315
|
-
file,
|
|
1316
|
-
input,
|
|
1317
|
-
entityId,
|
|
1318
|
-
propertyKey,
|
|
1319
|
-
path
|
|
1320
|
-
}) {
|
|
1321
|
-
const ext = file.name.split(".").pop();
|
|
1322
|
-
let result = input.replace("{propertyKey}", propertyKey).replace("{rand}", randomString()).replace("{file}", file.name).replace("{file.type}", file.type);
|
|
1323
|
-
if (entityId) {
|
|
1324
|
-
result = result.replace("{entityId}", String(entityId));
|
|
1325
|
-
}
|
|
1326
|
-
if (path) {
|
|
1327
|
-
result = result.replace("{path}", path);
|
|
1328
|
-
}
|
|
1329
|
-
if (ext) {
|
|
1330
|
-
result = result.replace("{file.ext}", ext);
|
|
1331
|
-
const name = file.name.replace(`.${ext}`, "");
|
|
1332
|
-
result = result.replace("{file.name}", name);
|
|
1333
|
-
}
|
|
1334
|
-
if (!result) result = randomString() + "_" + file.name;
|
|
1335
|
-
return result;
|
|
1336
|
-
}
|
|
1104
|
+
return additionalFieldDelegate;
|
|
1105
|
+
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region src/util/storage.ts
|
|
1108
|
+
async function resolveStorageFilenameString({ input, storage, values, entityId, path, property, file, propertyKey }) {
|
|
1109
|
+
let result;
|
|
1110
|
+
if (typeof input === "function") {
|
|
1111
|
+
result = await input({
|
|
1112
|
+
path,
|
|
1113
|
+
entityId,
|
|
1114
|
+
values,
|
|
1115
|
+
property,
|
|
1116
|
+
file,
|
|
1117
|
+
storage,
|
|
1118
|
+
propertyKey
|
|
1119
|
+
});
|
|
1120
|
+
if (!result) console.warn("Storage callback returned empty result. Using default name value");
|
|
1121
|
+
} else result = replacePlaceholders({
|
|
1122
|
+
file,
|
|
1123
|
+
input,
|
|
1124
|
+
entityId,
|
|
1125
|
+
propertyKey,
|
|
1126
|
+
path
|
|
1127
|
+
});
|
|
1128
|
+
if (!result) result = randomString() + "_" + file.name;
|
|
1129
|
+
return result;
|
|
1130
|
+
}
|
|
1131
|
+
function resolveStoragePathString({ input, storage, values, entityId, path, property, file, propertyKey }) {
|
|
1132
|
+
let result;
|
|
1133
|
+
if (typeof input === "function") {
|
|
1134
|
+
result = input({
|
|
1135
|
+
path,
|
|
1136
|
+
entityId,
|
|
1137
|
+
values,
|
|
1138
|
+
property,
|
|
1139
|
+
file,
|
|
1140
|
+
storage,
|
|
1141
|
+
propertyKey
|
|
1142
|
+
});
|
|
1143
|
+
if (!result) console.warn("Storage callback returned empty result. Using default name value");
|
|
1144
|
+
} else result = replacePlaceholders({
|
|
1145
|
+
file,
|
|
1146
|
+
input,
|
|
1147
|
+
entityId,
|
|
1148
|
+
propertyKey,
|
|
1149
|
+
path
|
|
1150
|
+
});
|
|
1151
|
+
if (!result) result = randomString() + "_" + file.name;
|
|
1152
|
+
return result;
|
|
1153
|
+
}
|
|
1154
|
+
function replacePlaceholders({ file, input, entityId, propertyKey, path }) {
|
|
1155
|
+
const ext = file.name.split(".").pop();
|
|
1156
|
+
let result = input.replace("{propertyKey}", propertyKey).replace("{rand}", randomString()).replace("{file}", file.name).replace("{file.type}", file.type);
|
|
1157
|
+
if (entityId) result = result.replace("{entityId}", String(entityId));
|
|
1158
|
+
if (path) result = result.replace("{path}", path);
|
|
1159
|
+
if (ext) {
|
|
1160
|
+
result = result.replace("{file.ext}", ext);
|
|
1161
|
+
const name = file.name.replace(`.${ext}`, "");
|
|
1162
|
+
result = result.replace("{file.name}", name);
|
|
1163
|
+
}
|
|
1164
|
+
if (!result) result = randomString() + "_" + file.name;
|
|
1165
|
+
return result;
|
|
1166
|
+
}
|
|
1167
|
+
//#endregion
|
|
1168
|
+
//#region src/util/callbacks.ts
|
|
1169
|
+
/**
|
|
1170
|
+
* Helper function to recursively check if there are any callbacks in the properties.
|
|
1171
|
+
*/
|
|
1337
1172
|
function hasPropertyCallbacks(properties, callbackName) {
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
}
|
|
1173
|
+
if (!properties) return false;
|
|
1174
|
+
for (const property of Object.values(properties)) {
|
|
1175
|
+
if (property.callbacks?.[callbackName]) return true;
|
|
1176
|
+
if (property.type === "map" && property.properties) {
|
|
1177
|
+
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
1178
|
+
} else if (property.type === "array" && property.of) {
|
|
1179
|
+
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
1180
|
+
for (const of of ofs) {
|
|
1181
|
+
if (of.callbacks?.[callbackName]) return true;
|
|
1182
|
+
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Recursively process properties to apply field-level hooks.
|
|
1190
|
+
*/
|
|
1353
1191
|
async function processProperties(properties, values, previousValues, propsContext, callbackName) {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const propertyCallbacks = {};
|
|
1397
|
-
if (hasPropertyCallbacks(properties, "afterRead")) {
|
|
1398
|
-
propertyCallbacks.afterRead = async (props) => {
|
|
1399
|
-
const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
|
|
1400
|
-
return {
|
|
1401
|
-
...props.entity,
|
|
1402
|
-
values: processedValues
|
|
1403
|
-
};
|
|
1404
|
-
};
|
|
1405
|
-
}
|
|
1406
|
-
if (hasPropertyCallbacks(properties, "beforeSave")) {
|
|
1407
|
-
propertyCallbacks.beforeSave = async (props) => {
|
|
1408
|
-
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
1409
|
-
};
|
|
1410
|
-
}
|
|
1411
|
-
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1192
|
+
if (!values || typeof values !== "object") return values;
|
|
1193
|
+
const result = { ...values };
|
|
1194
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
1195
|
+
if (result[key] === void 0) continue;
|
|
1196
|
+
let currentValue = result[key];
|
|
1197
|
+
const previousValue = previousValues?.[key];
|
|
1198
|
+
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
1199
|
+
if (property.of && !Array.isArray(property.of)) currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
1200
|
+
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
1201
|
+
return (await processProperties({ "_tmp": property.of }, { "_tmp": item }, { "_tmp": prevItem }, propsContext, callbackName))["_tmp"];
|
|
1202
|
+
}));
|
|
1203
|
+
} else if (property.type === "map" && property.properties && typeof currentValue === "object") currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
|
|
1204
|
+
if (property.callbacks?.[callbackName]) {
|
|
1205
|
+
const cbRes = await Promise.resolve(property.callbacks[callbackName]({
|
|
1206
|
+
...propsContext,
|
|
1207
|
+
value: currentValue,
|
|
1208
|
+
previousValue
|
|
1209
|
+
}));
|
|
1210
|
+
if (cbRes !== void 0) currentValue = cbRes;
|
|
1211
|
+
}
|
|
1212
|
+
result[key] = currentValue;
|
|
1213
|
+
}
|
|
1214
|
+
return result;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Helper function to extract field-level PropertyCallbacks from a properties schema
|
|
1218
|
+
* and wrap them into an EntityCallbacks object recursively.
|
|
1219
|
+
*/
|
|
1220
|
+
var buildPropertyCallbacks = (properties) => {
|
|
1221
|
+
if (!properties) return void 0;
|
|
1222
|
+
const propertyCallbacks = {};
|
|
1223
|
+
if (hasPropertyCallbacks(properties, "afterRead")) propertyCallbacks.afterRead = async (props) => {
|
|
1224
|
+
const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
|
|
1225
|
+
return {
|
|
1226
|
+
...props.entity,
|
|
1227
|
+
values: processedValues
|
|
1228
|
+
};
|
|
1229
|
+
};
|
|
1230
|
+
if (hasPropertyCallbacks(properties, "beforeSave")) propertyCallbacks.beforeSave = async (props) => {
|
|
1231
|
+
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
1232
|
+
};
|
|
1233
|
+
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1412
1234
|
};
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1235
|
+
//#endregion
|
|
1236
|
+
//#region src/util/conditions.ts
|
|
1237
|
+
/**
|
|
1238
|
+
* Access a nested property from an object via dot notation.
|
|
1239
|
+
*/
|
|
1240
|
+
function getIn$1(obj, path) {
|
|
1241
|
+
if (!obj || !path) return void 0;
|
|
1242
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
1243
|
+
}
|
|
1244
|
+
var operationsRegistered = false;
|
|
1245
|
+
/**
|
|
1246
|
+
* Register custom JSON Logic operations for Rebase.
|
|
1247
|
+
* Call this once at app initialization.
|
|
1248
|
+
*/
|
|
1418
1249
|
function registerConditionOperations() {
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1250
|
+
if (operationsRegistered) return;
|
|
1251
|
+
jsonLogic.add_operation("hasRole", function(roleId) {
|
|
1252
|
+
return this?.user?.roles?.includes(roleId) ?? false;
|
|
1253
|
+
});
|
|
1254
|
+
jsonLogic.add_operation("hasAnyRole", function(roleIds) {
|
|
1255
|
+
if (!this?.user?.roles || !Array.isArray(roleIds)) return false;
|
|
1256
|
+
return roleIds.some((role) => this.user.roles.includes(role));
|
|
1257
|
+
});
|
|
1258
|
+
jsonLogic.add_operation("isToday", (timestamp) => {
|
|
1259
|
+
if (!timestamp) return false;
|
|
1260
|
+
const date = new Date(timestamp);
|
|
1261
|
+
const today = /* @__PURE__ */ new Date();
|
|
1262
|
+
return date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
|
1263
|
+
});
|
|
1264
|
+
jsonLogic.add_operation("isPast", (timestamp) => {
|
|
1265
|
+
if (!timestamp) return false;
|
|
1266
|
+
return timestamp < Date.now();
|
|
1267
|
+
});
|
|
1268
|
+
jsonLogic.add_operation("isFuture", (timestamp) => {
|
|
1269
|
+
if (!timestamp) return false;
|
|
1270
|
+
return timestamp > Date.now();
|
|
1271
|
+
});
|
|
1272
|
+
operationsRegistered = true;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Evaluate a JSON Logic rule against the given context.
|
|
1276
|
+
*/
|
|
1443
1277
|
function evaluateCondition(rule, context) {
|
|
1444
|
-
|
|
1445
|
-
|
|
1278
|
+
registerConditionOperations();
|
|
1279
|
+
return jsonLogic.apply(rule, context);
|
|
1446
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Convert a value to a format suitable for JSON Logic evaluation.
|
|
1283
|
+
* Specifically handles Date objects by converting them to Unix timestamps.
|
|
1284
|
+
*/
|
|
1447
1285
|
function serializeValueForConditions(value) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
if (typeof value === "object") {
|
|
1464
|
-
const result = {};
|
|
1465
|
-
for (const key of Object.keys(value)) {
|
|
1466
|
-
result[key] = serializeValueForConditions(value[key]);
|
|
1467
|
-
}
|
|
1468
|
-
return result;
|
|
1469
|
-
}
|
|
1470
|
-
return value;
|
|
1471
|
-
}
|
|
1286
|
+
if (value === null || value === void 0) return value;
|
|
1287
|
+
if (value instanceof Date) return value.getTime();
|
|
1288
|
+
if (typeof value?.toMillis === "function") return value.toMillis();
|
|
1289
|
+
if (typeof value?.toDate === "function") return value.toDate().getTime();
|
|
1290
|
+
if (Array.isArray(value)) return value.map(serializeValueForConditions);
|
|
1291
|
+
if (typeof value === "object") {
|
|
1292
|
+
const result = {};
|
|
1293
|
+
for (const key of Object.keys(value)) result[key] = serializeValueForConditions(value[key]);
|
|
1294
|
+
return result;
|
|
1295
|
+
}
|
|
1296
|
+
return value;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Build a ConditionContext from the current property resolution context.
|
|
1300
|
+
*/
|
|
1472
1301
|
function buildConditionContext(params) {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
photoURL: user?.photoURL ?? null,
|
|
1498
|
-
roles: (user?.roles ?? []).map((r) => typeof r === "string" ? r : r.id)
|
|
1499
|
-
},
|
|
1500
|
-
now: Date.now()
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1302
|
+
const { propertyKey, values, previousValues, path, entityId, index, authController } = params;
|
|
1303
|
+
const user = authController.user;
|
|
1304
|
+
const serializedValues = serializeValueForConditions(values ?? {});
|
|
1305
|
+
return {
|
|
1306
|
+
values: serializedValues,
|
|
1307
|
+
previousValues: serializeValueForConditions(previousValues ?? values ?? {}),
|
|
1308
|
+
propertyValue: propertyKey ? getIn$1(serializedValues, propertyKey) : void 0,
|
|
1309
|
+
path,
|
|
1310
|
+
entityId,
|
|
1311
|
+
isNew: !entityId,
|
|
1312
|
+
index,
|
|
1313
|
+
user: {
|
|
1314
|
+
uid: user?.uid ?? "",
|
|
1315
|
+
email: user?.email ?? null,
|
|
1316
|
+
displayName: user?.displayName ?? null,
|
|
1317
|
+
photoURL: user?.photoURL ?? null,
|
|
1318
|
+
roles: (user?.roles ?? []).map((r) => typeof r === "string" ? r : r.id)
|
|
1319
|
+
},
|
|
1320
|
+
now: Date.now()
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Apply PropertyConditions to a resolved property, evaluating all JSON Logic rules.
|
|
1325
|
+
*/
|
|
1503
1326
|
function applyPropertyConditions(property, context) {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
}
|
|
1558
|
-
if (conditions.referenceFilter) {
|
|
1559
|
-
result.fixedFilter = evaluateCondition(conditions.referenceFilter, context);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
if (result.type === "array") {
|
|
1563
|
-
if (conditions.canAddElements !== void 0) {
|
|
1564
|
-
result.canAddElements = evaluateCondition(conditions.canAddElements, context);
|
|
1565
|
-
}
|
|
1566
|
-
if (conditions.sortable !== void 0) {
|
|
1567
|
-
result.sortable = evaluateCondition(conditions.sortable, context);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
return result;
|
|
1571
|
-
}
|
|
1327
|
+
const { conditions } = property;
|
|
1328
|
+
if (!conditions) return property;
|
|
1329
|
+
const result = { ...property };
|
|
1330
|
+
if (conditions.disabled) {
|
|
1331
|
+
if (evaluateCondition(conditions.disabled, context)) {
|
|
1332
|
+
result.ui = result.ui || {};
|
|
1333
|
+
result.ui.disabled = {
|
|
1334
|
+
clearOnDisabled: conditions.clearOnDisabled ?? false,
|
|
1335
|
+
disabledMessage: conditions.disabledMessage,
|
|
1336
|
+
hidden: false
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (conditions.hidden) {
|
|
1341
|
+
if (evaluateCondition(conditions.hidden, context)) {
|
|
1342
|
+
result.ui = result.ui || {};
|
|
1343
|
+
result.ui.disabled = {
|
|
1344
|
+
...typeof result.ui?.disabled === "object" ? result.ui.disabled : {},
|
|
1345
|
+
hidden: true,
|
|
1346
|
+
clearOnDisabled: conditions.clearOnDisabled ?? false
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (conditions.readOnly) {
|
|
1351
|
+
if (evaluateCondition(conditions.readOnly, context)) {
|
|
1352
|
+
result.ui = result.ui || {};
|
|
1353
|
+
result.ui.readOnly = true;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (conditions.required !== void 0) {
|
|
1357
|
+
const isRequired = evaluateCondition(conditions.required, context);
|
|
1358
|
+
result.validation = {
|
|
1359
|
+
...result.validation,
|
|
1360
|
+
required: isRequired,
|
|
1361
|
+
requiredMessage: conditions.requiredMessage
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
if (context.isNew && conditions.defaultValue !== void 0) result.defaultValue = evaluateCondition(conditions.defaultValue, context);
|
|
1365
|
+
if ("enum" in result && result.enum && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) result.enum = applyEnumConditions(result.enum, conditions, context);
|
|
1366
|
+
if (result.type === "reference") {
|
|
1367
|
+
if (conditions.referencePath) result.path = evaluateCondition(conditions.referencePath, context);
|
|
1368
|
+
if (conditions.referenceFilter) result.fixedFilter = evaluateCondition(conditions.referenceFilter, context);
|
|
1369
|
+
}
|
|
1370
|
+
if (result.type === "array") {
|
|
1371
|
+
if (conditions.canAddElements !== void 0) result.canAddElements = evaluateCondition(conditions.canAddElements, context);
|
|
1372
|
+
if (conditions.sortable !== void 0) result.sortable = evaluateCondition(conditions.sortable, context);
|
|
1373
|
+
}
|
|
1374
|
+
return result;
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Convert an object with numeric keys back to an array.
|
|
1378
|
+
* Firestore stores arrays as {"0": "a", "1": "b"} to avoid nested arrays.
|
|
1379
|
+
*/
|
|
1572
1380
|
function objectToArray(obj) {
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1381
|
+
if (Array.isArray(obj)) return obj.map(String);
|
|
1382
|
+
if (obj && typeof obj === "object") {
|
|
1383
|
+
const keys = Object.keys(obj);
|
|
1384
|
+
if (keys.length > 0 && keys.every((k) => !isNaN(Number(k)))) return keys.sort((a, b) => Number(a) - Number(b)).map((k) => obj[k]).filter((v) => typeof v === "string" || typeof v === "number").map(String);
|
|
1385
|
+
}
|
|
1386
|
+
return [];
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Apply enum-specific conditions to filter and modify enum values.
|
|
1390
|
+
*/
|
|
1582
1391
|
function applyEnumConditions(enumValues, conditions, context) {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const normalized = path.replace(/-/g, "_");
|
|
1871
|
-
const byNormalized = this.rawCollectionsBySlug.get(normalized);
|
|
1872
|
-
if (byNormalized) return byNormalized;
|
|
1873
|
-
}
|
|
1874
|
-
return this.rawCollectionsByTableName.get(path);
|
|
1875
|
-
}
|
|
1876
|
-
/**
|
|
1877
|
-
* Get collection by resolving multi-segment paths through relations
|
|
1878
|
-
* e.g., "authors/70/posts" resolves to the posts collection
|
|
1879
|
-
*/
|
|
1880
|
-
getCollectionByPath(collectionPath) {
|
|
1881
|
-
if (!collectionPath.includes("/")) {
|
|
1882
|
-
return this.get(collectionPath);
|
|
1883
|
-
}
|
|
1884
|
-
const pathSegments = collectionPath.split("/").filter((p) => p);
|
|
1885
|
-
if (pathSegments.length < 3 || pathSegments.length % 2 === 0) {
|
|
1886
|
-
throw new Error(`Invalid relation path: ${collectionPath}. Expected format: collection/id/relation or collection/id/relation/id/relation`);
|
|
1887
|
-
}
|
|
1888
|
-
const rootCollectionPath = pathSegments[0];
|
|
1889
|
-
let currentCollection = this.get(rootCollectionPath);
|
|
1890
|
-
if (!currentCollection) {
|
|
1891
|
-
throw new Error(`Root collection not found: ${rootCollectionPath}`);
|
|
1892
|
-
}
|
|
1893
|
-
for (let i = 2; i < pathSegments.length; i += 2) {
|
|
1894
|
-
const relationKey = pathSegments[i];
|
|
1895
|
-
if (!getDataSourceCapabilities(currentCollection.driver).supportsRelations) {
|
|
1896
|
-
throw new Error(`Relation path navigation requires a collection that supports relations, but '${currentCollection.slug}' uses driver '${currentCollection.driver}'`);
|
|
1897
|
-
}
|
|
1898
|
-
const resolvedRelations = resolveCollectionRelations(currentCollection);
|
|
1899
|
-
const relation = findRelation(resolvedRelations, relationKey);
|
|
1900
|
-
if (!relation) {
|
|
1901
|
-
throw new Error(`Relation '${relationKey}' not found in collection '${currentCollection.slug}'`);
|
|
1902
|
-
}
|
|
1903
|
-
const target = relation.target();
|
|
1904
|
-
const targetRelationKey = relation.relationName || target.slug;
|
|
1905
|
-
const targetSlug = relation.overrides?.slug ?? targetRelationKey;
|
|
1906
|
-
currentCollection = this.get(targetSlug) || this.normalizeCollection(target);
|
|
1907
|
-
if (i + 1 < pathSegments.length) ;
|
|
1908
|
-
}
|
|
1909
|
-
return currentCollection;
|
|
1910
|
-
}
|
|
1911
|
-
getCollections() {
|
|
1912
|
-
if (!this.cachedCollectionsList) {
|
|
1913
|
-
this.cachedCollectionsList = Array.from(this.collectionsByTableName.values());
|
|
1914
|
-
}
|
|
1915
|
-
return this.cachedCollectionsList;
|
|
1916
|
-
}
|
|
1917
|
-
getRawCollections() {
|
|
1918
|
-
if (!this.cachedRawCollectionsList) {
|
|
1919
|
-
this.cachedRawCollectionsList = Array.from(this.rawCollectionsByTableName.values());
|
|
1920
|
-
}
|
|
1921
|
-
return this.cachedRawCollectionsList;
|
|
1922
|
-
}
|
|
1923
|
-
/**
|
|
1924
|
-
* Resolves a multi-segment path like "products/123/locales" and returns
|
|
1925
|
-
* information about the collections and entity IDs along the path
|
|
1926
|
-
*/
|
|
1927
|
-
resolvePathToCollections(path) {
|
|
1928
|
-
const pathSegments = path.split("/").filter((p) => p);
|
|
1929
|
-
if (pathSegments.length === 0) {
|
|
1930
|
-
throw new Error(`Invalid path: ${path}`);
|
|
1931
|
-
}
|
|
1932
|
-
if (pathSegments.length % 2 !== 1) {
|
|
1933
|
-
throw new Error(`Invalid collection path: ${path}. It must have an odd number of segments.`);
|
|
1934
|
-
}
|
|
1935
|
-
const collections = [];
|
|
1936
|
-
const entityIds = [];
|
|
1937
|
-
let currentCollection = this.get(pathSegments[0]);
|
|
1938
|
-
if (!currentCollection) {
|
|
1939
|
-
throw new Error(`Unknown collection path or slug: ${pathSegments[0]}`);
|
|
1940
|
-
}
|
|
1941
|
-
collections.push(currentCollection);
|
|
1942
|
-
for (let i = 1; i < pathSegments.length; i += 2) {
|
|
1943
|
-
const entityId = pathSegments[i];
|
|
1944
|
-
entityIds.push(entityId);
|
|
1945
|
-
if (i + 1 < pathSegments.length) {
|
|
1946
|
-
const subcollectionSlug = pathSegments[i + 1];
|
|
1947
|
-
const subcollections = getSubcollections(currentCollection);
|
|
1948
|
-
if (!subcollections || subcollections.length === 0) {
|
|
1949
|
-
throw new Error(`No subcollections found for ${currentCollection.slug} in path: ${path}`);
|
|
1950
|
-
}
|
|
1951
|
-
const subcollection = subcollections.find((c) => c.slug === subcollectionSlug);
|
|
1952
|
-
if (!subcollection) {
|
|
1953
|
-
throw new Error(`Subcollection '${subcollectionSlug}' not found in ${currentCollection.slug}`);
|
|
1954
|
-
}
|
|
1955
|
-
currentCollection = this.get(subcollection.slug) || this.normalizeCollection(subcollection);
|
|
1956
|
-
collections.push(currentCollection);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
return {
|
|
1960
|
-
collections,
|
|
1961
|
-
entityIds,
|
|
1962
|
-
finalCollection: currentCollection
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
const defaultUsersCollection = {
|
|
1967
|
-
name: "Users",
|
|
1968
|
-
singularName: "User",
|
|
1969
|
-
slug: "users",
|
|
1970
|
-
table: "users",
|
|
1971
|
-
schema: "rebase",
|
|
1972
|
-
icon: "Users",
|
|
1973
|
-
group: "Settings",
|
|
1974
|
-
openEntityMode: "dialog",
|
|
1975
|
-
disableDefaultActions: ["copy"],
|
|
1976
|
-
securityRules: [{
|
|
1977
|
-
operation: "select",
|
|
1978
|
-
roles: ["admin"]
|
|
1979
|
-
}, {
|
|
1980
|
-
operations: ["insert", "update", "delete"],
|
|
1981
|
-
roles: ["admin"]
|
|
1982
|
-
}],
|
|
1983
|
-
sort: ["createdAt", "desc"],
|
|
1984
|
-
properties: {
|
|
1985
|
-
id: {
|
|
1986
|
-
name: "ID",
|
|
1987
|
-
type: "string",
|
|
1988
|
-
isId: "uuid",
|
|
1989
|
-
ui: {
|
|
1990
|
-
readOnly: true
|
|
1991
|
-
}
|
|
1992
|
-
},
|
|
1993
|
-
email: {
|
|
1994
|
-
name: "Email",
|
|
1995
|
-
type: "string",
|
|
1996
|
-
validation: {
|
|
1997
|
-
required: true,
|
|
1998
|
-
unique: true
|
|
1999
|
-
}
|
|
2000
|
-
},
|
|
2001
|
-
displayName: {
|
|
2002
|
-
name: "Name",
|
|
2003
|
-
type: "string",
|
|
2004
|
-
columnName: "display_name",
|
|
2005
|
-
validation: {
|
|
2006
|
-
required: true
|
|
2007
|
-
}
|
|
2008
|
-
},
|
|
2009
|
-
photoURL: {
|
|
2010
|
-
name: "Photo URL",
|
|
2011
|
-
type: "string",
|
|
2012
|
-
columnName: "photo_url",
|
|
2013
|
-
url: "image"
|
|
2014
|
-
},
|
|
2015
|
-
roles: {
|
|
2016
|
-
name: "Roles",
|
|
2017
|
-
type: "array",
|
|
2018
|
-
columnType: "text[]",
|
|
2019
|
-
of: {
|
|
2020
|
-
name: "Role",
|
|
2021
|
-
type: "string",
|
|
2022
|
-
enum: {
|
|
2023
|
-
admin: "Admin",
|
|
2024
|
-
editor: "Editor",
|
|
2025
|
-
viewer: "Viewer"
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
},
|
|
2029
|
-
passwordHash: {
|
|
2030
|
-
name: "Password Hash",
|
|
2031
|
-
type: "string",
|
|
2032
|
-
columnName: "password_hash",
|
|
2033
|
-
ui: {
|
|
2034
|
-
hideFromCollection: true,
|
|
2035
|
-
disabled: {
|
|
2036
|
-
hidden: true
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
},
|
|
2040
|
-
emailVerified: {
|
|
2041
|
-
name: "Email Verified",
|
|
2042
|
-
type: "boolean",
|
|
2043
|
-
columnName: "email_verified",
|
|
2044
|
-
defaultValue: false,
|
|
2045
|
-
ui: {
|
|
2046
|
-
hideFromCollection: true,
|
|
2047
|
-
disabled: {
|
|
2048
|
-
hidden: true
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
},
|
|
2052
|
-
emailVerificationToken: {
|
|
2053
|
-
name: "Email Verification Token",
|
|
2054
|
-
type: "string",
|
|
2055
|
-
columnName: "email_verification_token",
|
|
2056
|
-
ui: {
|
|
2057
|
-
hideFromCollection: true,
|
|
2058
|
-
disabled: {
|
|
2059
|
-
hidden: true
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
},
|
|
2063
|
-
emailVerificationSentAt: {
|
|
2064
|
-
name: "Email Verification Sent At",
|
|
2065
|
-
type: "date",
|
|
2066
|
-
columnName: "email_verification_sent_at",
|
|
2067
|
-
ui: {
|
|
2068
|
-
hideFromCollection: true,
|
|
2069
|
-
disabled: {
|
|
2070
|
-
hidden: true
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
},
|
|
2074
|
-
metadata: {
|
|
2075
|
-
name: "Metadata",
|
|
2076
|
-
type: "map",
|
|
2077
|
-
defaultValue: {},
|
|
2078
|
-
ui: {
|
|
2079
|
-
hideFromCollection: true,
|
|
2080
|
-
disabled: {
|
|
2081
|
-
hidden: true
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
},
|
|
2085
|
-
createdAt: {
|
|
2086
|
-
name: "Created At",
|
|
2087
|
-
type: "date",
|
|
2088
|
-
columnName: "created_at",
|
|
2089
|
-
autoValue: "on_create",
|
|
2090
|
-
ui: {
|
|
2091
|
-
readOnly: true
|
|
2092
|
-
}
|
|
2093
|
-
},
|
|
2094
|
-
updatedAt: {
|
|
2095
|
-
name: "Updated At",
|
|
2096
|
-
type: "date",
|
|
2097
|
-
columnName: "updated_at",
|
|
2098
|
-
autoValue: "on_update",
|
|
2099
|
-
ui: {
|
|
2100
|
-
hideFromCollection: true,
|
|
2101
|
-
disabled: {
|
|
2102
|
-
hidden: true
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
},
|
|
2107
|
-
listProperties: ["displayName", "email", "roles", "createdAt"],
|
|
2108
|
-
propertiesOrder: ["id", "email", "displayName", "roles", "createdAt"]
|
|
1392
|
+
let result = [...enumValues];
|
|
1393
|
+
if (conditions.allowedEnumValues) {
|
|
1394
|
+
const allowedArray = objectToArray(evaluateCondition(conditions.allowedEnumValues, context));
|
|
1395
|
+
if (allowedArray.length > 0) result = result.filter((ev) => allowedArray.includes(String(ev.id)));
|
|
1396
|
+
}
|
|
1397
|
+
if (conditions.excludedEnumValues) {
|
|
1398
|
+
const excludedArray = objectToArray(evaluateCondition(conditions.excludedEnumValues, context));
|
|
1399
|
+
if (excludedArray.length > 0) result = result.filter((ev) => !excludedArray.includes(String(ev.id)));
|
|
1400
|
+
}
|
|
1401
|
+
if (conditions.enumConditions) result = result.map((ev) => {
|
|
1402
|
+
const evConditions = conditions.enumConditions?.[ev.id];
|
|
1403
|
+
if (!evConditions) return ev;
|
|
1404
|
+
if (evConditions.hidden && evaluateCondition(evConditions.hidden, context)) return null;
|
|
1405
|
+
if (evConditions.disabled && evaluateCondition(evConditions.disabled, context)) return {
|
|
1406
|
+
...ev,
|
|
1407
|
+
disabled: true
|
|
1408
|
+
};
|
|
1409
|
+
return ev;
|
|
1410
|
+
}).filter((ev) => ev !== null);
|
|
1411
|
+
return result;
|
|
1412
|
+
}
|
|
1413
|
+
//#endregion
|
|
1414
|
+
//#region src/collections/CollectionRegistry.ts
|
|
1415
|
+
var CollectionRegistry = class {
|
|
1416
|
+
collectionsByTableName = /* @__PURE__ */ new Map();
|
|
1417
|
+
collectionsBySlug = /* @__PURE__ */ new Map();
|
|
1418
|
+
rootCollections = [];
|
|
1419
|
+
cachedCollectionsList = null;
|
|
1420
|
+
rawCollectionsByTableName = /* @__PURE__ */ new Map();
|
|
1421
|
+
rawCollectionsBySlug = /* @__PURE__ */ new Map();
|
|
1422
|
+
rawRootCollections = [];
|
|
1423
|
+
cachedRawCollectionsList = null;
|
|
1424
|
+
lastRawInputSnapshot = null;
|
|
1425
|
+
constructor(collections) {
|
|
1426
|
+
if (collections) this.registerMultiple(collections);
|
|
1427
|
+
}
|
|
1428
|
+
reset() {
|
|
1429
|
+
this.collectionsByTableName.clear();
|
|
1430
|
+
this.collectionsBySlug.clear();
|
|
1431
|
+
this.rootCollections = [];
|
|
1432
|
+
this.cachedCollectionsList = null;
|
|
1433
|
+
this.rawCollectionsByTableName.clear();
|
|
1434
|
+
this.rawCollectionsBySlug.clear();
|
|
1435
|
+
this.rawRootCollections = [];
|
|
1436
|
+
this.cachedRawCollectionsList = null;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Registers a collection and its subcollections recursively.
|
|
1440
|
+
* Returns true if the collections have changed, false otherwise.
|
|
1441
|
+
*
|
|
1442
|
+
* Idempotent: compares the raw input (before normalization) against a stored
|
|
1443
|
+
* snapshot. Only re-normalizes and re-registers when the raw input actually changed.
|
|
1444
|
+
* @param collections
|
|
1445
|
+
*/
|
|
1446
|
+
registerMultiple(collections) {
|
|
1447
|
+
const rawSnapshot = collections.map((c) => removeFunctions(c));
|
|
1448
|
+
if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) return false;
|
|
1449
|
+
this.reset();
|
|
1450
|
+
collections.forEach((c) => {
|
|
1451
|
+
if (c.slug) this.collectionsBySlug.set(c.slug, c);
|
|
1452
|
+
this.collectionsByTableName.set(getTableName(c), c);
|
|
1453
|
+
});
|
|
1454
|
+
const normalizedCollections = collections.map((c) => this.normalizeCollection({ ...c }));
|
|
1455
|
+
normalizedCollections.forEach((c, index) => {
|
|
1456
|
+
const raw = deepClone(collections[index]);
|
|
1457
|
+
this.rootCollections.push(c);
|
|
1458
|
+
this.rawRootCollections.push(raw);
|
|
1459
|
+
const normalized = this.normalizeCollection(c);
|
|
1460
|
+
this.collectionsByTableName.set(getTableName(normalized), normalized);
|
|
1461
|
+
this.rawCollectionsByTableName.set(getTableName(raw), raw);
|
|
1462
|
+
if (normalized.slug) this.collectionsBySlug.set(normalized.slug, normalized);
|
|
1463
|
+
if (raw.slug) this.rawCollectionsBySlug.set(raw.slug, raw);
|
|
1464
|
+
});
|
|
1465
|
+
normalizedCollections.forEach((c) => {
|
|
1466
|
+
const subcollections = getSubcollections(c);
|
|
1467
|
+
if (subcollections && subcollections.length > 0) subcollections.forEach((subCollection) => {
|
|
1468
|
+
if (!subCollection) return;
|
|
1469
|
+
this._registerRecursively(this.normalizeCollection({ ...subCollection }), deepClone(subCollection));
|
|
1470
|
+
});
|
|
1471
|
+
});
|
|
1472
|
+
this.lastRawInputSnapshot = rawSnapshot;
|
|
1473
|
+
return true;
|
|
1474
|
+
}
|
|
1475
|
+
register(collection, rawCollection) {
|
|
1476
|
+
const raw = rawCollection ? deepClone(rawCollection) : deepClone(collection);
|
|
1477
|
+
this.rootCollections.push(collection);
|
|
1478
|
+
this.rawRootCollections.push(raw);
|
|
1479
|
+
this._registerRecursively(collection, raw);
|
|
1480
|
+
}
|
|
1481
|
+
_registerRecursively(collection, rawCollection) {
|
|
1482
|
+
if (this.collectionsByTableName.has(getTableName(collection))) return;
|
|
1483
|
+
const normalizedCollection = this.normalizeCollection(collection);
|
|
1484
|
+
this.collectionsByTableName.set(getTableName(normalizedCollection), normalizedCollection);
|
|
1485
|
+
this.rawCollectionsByTableName.set(getTableName(rawCollection), rawCollection);
|
|
1486
|
+
if (normalizedCollection.slug) this.collectionsBySlug.set(normalizedCollection.slug, normalizedCollection);
|
|
1487
|
+
if (rawCollection.slug) this.rawCollectionsBySlug.set(rawCollection.slug, rawCollection);
|
|
1488
|
+
const subcollections = getSubcollections(normalizedCollection);
|
|
1489
|
+
if (subcollections && subcollections.length > 0) subcollections.forEach((subCollection) => {
|
|
1490
|
+
if (!subCollection) return;
|
|
1491
|
+
this._registerRecursively(this.normalizeCollection({ ...subCollection }), deepClone(subCollection));
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
normalizeCollection(collection) {
|
|
1495
|
+
const result = { ...collection };
|
|
1496
|
+
const extractedRelations = this.extractRelationsFromProperties(result.properties);
|
|
1497
|
+
const relResult = result;
|
|
1498
|
+
const manualRelations = getDataSourceCapabilities(result.driver).supportsRelations ? relResult.relations ?? [] : [];
|
|
1499
|
+
const mergedRelationsRaw = [...extractedRelations];
|
|
1500
|
+
for (const manual of manualRelations) {
|
|
1501
|
+
const name = manual.relationName;
|
|
1502
|
+
if (!name) mergedRelationsRaw.push(manual);
|
|
1503
|
+
else {
|
|
1504
|
+
const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
|
|
1505
|
+
if (existingIndex === -1) mergedRelationsRaw.push(manual);
|
|
1506
|
+
else mergedRelationsRaw[existingIndex] = {
|
|
1507
|
+
...manual,
|
|
1508
|
+
...mergedRelationsRaw[existingIndex]
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
let mergedRelations = mergedRelationsRaw;
|
|
1513
|
+
if (getDataSourceCapabilities(result.driver).supportsRelations) {
|
|
1514
|
+
mergedRelations = mergedRelationsRaw.map((r) => {
|
|
1515
|
+
try {
|
|
1516
|
+
return sanitizeRelation(r, result, (slug) => this.get(slug));
|
|
1517
|
+
} catch {
|
|
1518
|
+
return r;
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
relResult.relations = mergedRelations;
|
|
1522
|
+
}
|
|
1523
|
+
result.properties = this.normalizeProperties(result.properties, mergedRelations);
|
|
1524
|
+
if (!result.childCollections) {
|
|
1525
|
+
if (getDataSourceCapabilities(result.driver).supportsSubcollections && result.subcollections) result.childCollections = result.subcollections;
|
|
1526
|
+
else if (getDataSourceCapabilities(result.driver).supportsRelations && relResult.relations) {
|
|
1527
|
+
const manyRelations = relResult.relations.filter((r) => r.cardinality === "many");
|
|
1528
|
+
if (manyRelations.length > 0) result.childCollections = () => manyRelations.map((r) => {
|
|
1529
|
+
const target = r.target();
|
|
1530
|
+
return r.overrides ? mergeDeep(target, r.overrides) : target;
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return result;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
|
|
1538
|
+
* This allows developers to define relations directly on properties without a separate
|
|
1539
|
+
* `relations[]` entry on the collection.
|
|
1540
|
+
*/
|
|
1541
|
+
extractRelationsFromProperties(properties) {
|
|
1542
|
+
const relations = [];
|
|
1543
|
+
for (const [key, property] of Object.entries(properties)) if (property.type === "relation") {
|
|
1544
|
+
const relProp = property;
|
|
1545
|
+
const target = relProp.target ?? relProp.relation?.target;
|
|
1546
|
+
if (target) {
|
|
1547
|
+
const relationName = relProp.relationName ?? relProp.relation?.relationName ?? key;
|
|
1548
|
+
relations.push({
|
|
1549
|
+
relationName,
|
|
1550
|
+
target,
|
|
1551
|
+
cardinality: relProp.cardinality ?? relProp.relation?.cardinality ?? "one",
|
|
1552
|
+
direction: relProp.direction ?? relProp.relation?.direction ?? "owning",
|
|
1553
|
+
inverseRelationName: relProp.inverseRelationName ?? relProp.relation?.inverseRelationName,
|
|
1554
|
+
localKey: relProp.localKey ?? relProp.relation?.localKey,
|
|
1555
|
+
foreignKeyOnTarget: relProp.foreignKeyOnTarget ?? relProp.relation?.foreignKeyOnTarget,
|
|
1556
|
+
through: relProp.through ?? relProp.relation?.through,
|
|
1557
|
+
joinPath: relProp.joinPath ?? relProp.relation?.joinPath,
|
|
1558
|
+
onUpdate: relProp.onUpdate ?? relProp.relation?.onUpdate,
|
|
1559
|
+
onDelete: relProp.onDelete ?? relProp.relation?.onDelete,
|
|
1560
|
+
overrides: relProp.overrides ?? relProp.relation?.overrides
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
} else if (property.type === "map" && property.properties) relations.push(...this.extractRelationsFromProperties(property.properties));
|
|
1564
|
+
return relations;
|
|
1565
|
+
}
|
|
1566
|
+
normalizeProperties(properties, relations) {
|
|
1567
|
+
const newProperties = {};
|
|
1568
|
+
for (const key in properties) newProperties[key] = this.normalizeProperty(key, properties[key], relations);
|
|
1569
|
+
return newProperties;
|
|
1570
|
+
}
|
|
1571
|
+
normalizeProperty(key, property, relations) {
|
|
1572
|
+
const newProperty = { ...property };
|
|
1573
|
+
if (newProperty.type === "map" && newProperty.properties) newProperty.properties = this.normalizeProperties(newProperty.properties, relations);
|
|
1574
|
+
else if (newProperty.type === "array") {
|
|
1575
|
+
const arrayProp = newProperty;
|
|
1576
|
+
if (arrayProp.of) if (Array.isArray(arrayProp.of)) arrayProp.of = arrayProp.of.map((p, i) => this.normalizeProperty(`${key}[${i}]`, p, relations));
|
|
1577
|
+
else arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of, relations);
|
|
1578
|
+
else if (arrayProp.oneOf && arrayProp.oneOf.properties) arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties, relations);
|
|
1579
|
+
} else if ((newProperty.type === "string" || newProperty.type === "number") && newProperty.enum) {
|
|
1580
|
+
const stringOrNumberProperty = newProperty;
|
|
1581
|
+
if (typeof stringOrNumberProperty.enum === "object" && !Array.isArray(stringOrNumberProperty.enum)) stringOrNumberProperty.enum = enumToObjectEntries(stringOrNumberProperty.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? [];
|
|
1582
|
+
} else if (newProperty.type === "relation") {
|
|
1583
|
+
const relationProperty = newProperty;
|
|
1584
|
+
const name = relationProperty.relationName || key;
|
|
1585
|
+
const relation = relations.find((r) => r.relationName === name);
|
|
1586
|
+
if (relation) relationProperty.relation = relation;
|
|
1587
|
+
else console.warn(`Could not find relation for property '${key}' with relationName: ${name}`);
|
|
1588
|
+
}
|
|
1589
|
+
return newProperty;
|
|
1590
|
+
}
|
|
1591
|
+
get(path) {
|
|
1592
|
+
const bySlug = this.collectionsBySlug.get(path);
|
|
1593
|
+
if (bySlug) return bySlug;
|
|
1594
|
+
if (path.includes("-")) {
|
|
1595
|
+
const normalized = path.replace(/-/g, "_");
|
|
1596
|
+
const byNormalized = this.collectionsBySlug.get(normalized);
|
|
1597
|
+
if (byNormalized) return byNormalized;
|
|
1598
|
+
}
|
|
1599
|
+
return this.collectionsByTableName.get(path);
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Gets the pristine, un-normalized collection exactly as it was provided.
|
|
1603
|
+
* Useful for the AST editor so it doesn't accidentally serialize injected metadata back to disk.
|
|
1604
|
+
*/
|
|
1605
|
+
getRaw(path) {
|
|
1606
|
+
const bySlug = this.rawCollectionsBySlug.get(path);
|
|
1607
|
+
if (bySlug) return bySlug;
|
|
1608
|
+
if (path.includes("-")) {
|
|
1609
|
+
const normalized = path.replace(/-/g, "_");
|
|
1610
|
+
const byNormalized = this.rawCollectionsBySlug.get(normalized);
|
|
1611
|
+
if (byNormalized) return byNormalized;
|
|
1612
|
+
}
|
|
1613
|
+
return this.rawCollectionsByTableName.get(path);
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Get collection by resolving multi-segment paths through relations
|
|
1617
|
+
* e.g., "authors/70/posts" resolves to the posts collection
|
|
1618
|
+
*/
|
|
1619
|
+
getCollectionByPath(collectionPath) {
|
|
1620
|
+
if (!collectionPath.includes("/")) return this.get(collectionPath);
|
|
1621
|
+
const pathSegments = collectionPath.split("/").filter((p) => p);
|
|
1622
|
+
if (pathSegments.length < 3 || pathSegments.length % 2 === 0) throw new Error(`Invalid relation path: ${collectionPath}. Expected format: collection/id/relation or collection/id/relation/id/relation`);
|
|
1623
|
+
const rootCollectionPath = pathSegments[0];
|
|
1624
|
+
let currentCollection = this.get(rootCollectionPath);
|
|
1625
|
+
if (!currentCollection) throw new Error(`Root collection not found: ${rootCollectionPath}`);
|
|
1626
|
+
for (let i = 2; i < pathSegments.length; i += 2) {
|
|
1627
|
+
const relationKey = pathSegments[i];
|
|
1628
|
+
if (!getDataSourceCapabilities(currentCollection.driver).supportsRelations) throw new Error(`Relation path navigation requires a collection that supports relations, but '${currentCollection.slug}' uses driver '${currentCollection.driver}'`);
|
|
1629
|
+
const relation = findRelation(resolveCollectionRelations(currentCollection), relationKey);
|
|
1630
|
+
if (!relation) throw new Error(`Relation '${relationKey}' not found in collection '${currentCollection.slug}'`);
|
|
1631
|
+
const target = relation.target();
|
|
1632
|
+
const targetRelationKey = relation.relationName || target.slug;
|
|
1633
|
+
const targetSlug = relation.overrides?.slug ?? targetRelationKey;
|
|
1634
|
+
currentCollection = this.get(targetSlug) || this.normalizeCollection(target);
|
|
1635
|
+
if (i + 1 < pathSegments.length) {}
|
|
1636
|
+
}
|
|
1637
|
+
return currentCollection;
|
|
1638
|
+
}
|
|
1639
|
+
getCollections() {
|
|
1640
|
+
if (!this.cachedCollectionsList) this.cachedCollectionsList = Array.from(this.collectionsByTableName.values());
|
|
1641
|
+
return this.cachedCollectionsList;
|
|
1642
|
+
}
|
|
1643
|
+
getRawCollections() {
|
|
1644
|
+
if (!this.cachedRawCollectionsList) this.cachedRawCollectionsList = Array.from(this.rawCollectionsByTableName.values());
|
|
1645
|
+
return this.cachedRawCollectionsList;
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Resolves a multi-segment path like "products/123/locales" and returns
|
|
1649
|
+
* information about the collections and entity IDs along the path
|
|
1650
|
+
*/
|
|
1651
|
+
resolvePathToCollections(path) {
|
|
1652
|
+
const pathSegments = path.split("/").filter((p) => p);
|
|
1653
|
+
if (pathSegments.length === 0) throw new Error(`Invalid path: ${path}`);
|
|
1654
|
+
if (pathSegments.length % 2 !== 1) throw new Error(`Invalid collection path: ${path}. It must have an odd number of segments.`);
|
|
1655
|
+
const collections = [];
|
|
1656
|
+
const entityIds = [];
|
|
1657
|
+
let currentCollection = this.get(pathSegments[0]);
|
|
1658
|
+
if (!currentCollection) throw new Error(`Unknown collection path or slug: ${pathSegments[0]}`);
|
|
1659
|
+
collections.push(currentCollection);
|
|
1660
|
+
for (let i = 1; i < pathSegments.length; i += 2) {
|
|
1661
|
+
const entityId = pathSegments[i];
|
|
1662
|
+
entityIds.push(entityId);
|
|
1663
|
+
if (i + 1 < pathSegments.length) {
|
|
1664
|
+
const subcollectionSlug = pathSegments[i + 1];
|
|
1665
|
+
const subcollections = getSubcollections(currentCollection);
|
|
1666
|
+
if (!subcollections || subcollections.length === 0) throw new Error(`No subcollections found for ${currentCollection.slug} in path: ${path}`);
|
|
1667
|
+
const subcollection = subcollections.find((c) => c.slug === subcollectionSlug);
|
|
1668
|
+
if (!subcollection) throw new Error(`Subcollection '${subcollectionSlug}' not found in ${currentCollection.slug}`);
|
|
1669
|
+
currentCollection = this.get(subcollection.slug) || this.normalizeCollection(subcollection);
|
|
1670
|
+
collections.push(currentCollection);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
return {
|
|
1674
|
+
collections,
|
|
1675
|
+
entityIds,
|
|
1676
|
+
finalCollection: currentCollection
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
2109
1679
|
};
|
|
1680
|
+
//#endregion
|
|
1681
|
+
//#region src/collections/default-collections.ts
|
|
1682
|
+
/**
|
|
1683
|
+
* Default users collection.
|
|
1684
|
+
*
|
|
1685
|
+
* Prepended to the developer's collections array by the admin and server.
|
|
1686
|
+
* Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
|
|
1687
|
+
* override by defining their own collection with `slug: "users"`.
|
|
1688
|
+
*/
|
|
1689
|
+
var defaultUsersCollection = {
|
|
1690
|
+
name: "Users",
|
|
1691
|
+
singularName: "User",
|
|
1692
|
+
slug: "users",
|
|
1693
|
+
auth: true,
|
|
1694
|
+
table: "users",
|
|
1695
|
+
schema: "rebase",
|
|
1696
|
+
icon: "Users",
|
|
1697
|
+
group: "Settings",
|
|
1698
|
+
openEntityMode: "dialog",
|
|
1699
|
+
disableDefaultActions: ["copy"],
|
|
1700
|
+
securityRules: [{
|
|
1701
|
+
operation: "select",
|
|
1702
|
+
roles: ["admin"]
|
|
1703
|
+
}, {
|
|
1704
|
+
operations: [
|
|
1705
|
+
"insert",
|
|
1706
|
+
"update",
|
|
1707
|
+
"delete"
|
|
1708
|
+
],
|
|
1709
|
+
roles: ["admin"]
|
|
1710
|
+
}],
|
|
1711
|
+
sort: ["createdAt", "desc"],
|
|
1712
|
+
properties: {
|
|
1713
|
+
id: {
|
|
1714
|
+
name: "ID",
|
|
1715
|
+
type: "string",
|
|
1716
|
+
isId: "uuid",
|
|
1717
|
+
ui: { readOnly: true }
|
|
1718
|
+
},
|
|
1719
|
+
email: {
|
|
1720
|
+
name: "Email",
|
|
1721
|
+
type: "string",
|
|
1722
|
+
validation: {
|
|
1723
|
+
required: true,
|
|
1724
|
+
unique: true
|
|
1725
|
+
}
|
|
1726
|
+
},
|
|
1727
|
+
displayName: {
|
|
1728
|
+
name: "Name",
|
|
1729
|
+
type: "string",
|
|
1730
|
+
columnName: "display_name",
|
|
1731
|
+
validation: { required: true }
|
|
1732
|
+
},
|
|
1733
|
+
photoURL: {
|
|
1734
|
+
name: "Photo URL",
|
|
1735
|
+
type: "string",
|
|
1736
|
+
columnName: "photo_url",
|
|
1737
|
+
url: "image"
|
|
1738
|
+
},
|
|
1739
|
+
roles: {
|
|
1740
|
+
name: "Roles",
|
|
1741
|
+
type: "array",
|
|
1742
|
+
columnType: "text[]",
|
|
1743
|
+
of: {
|
|
1744
|
+
name: "Role",
|
|
1745
|
+
type: "string",
|
|
1746
|
+
enum: {
|
|
1747
|
+
admin: "Admin",
|
|
1748
|
+
editor: "Editor",
|
|
1749
|
+
viewer: "Viewer"
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
},
|
|
1753
|
+
passwordHash: {
|
|
1754
|
+
name: "Password Hash",
|
|
1755
|
+
type: "string",
|
|
1756
|
+
columnName: "password_hash",
|
|
1757
|
+
ui: {
|
|
1758
|
+
hideFromCollection: true,
|
|
1759
|
+
disabled: { hidden: true }
|
|
1760
|
+
}
|
|
1761
|
+
},
|
|
1762
|
+
emailVerified: {
|
|
1763
|
+
name: "Email Verified",
|
|
1764
|
+
type: "boolean",
|
|
1765
|
+
columnName: "email_verified",
|
|
1766
|
+
defaultValue: false,
|
|
1767
|
+
ui: {
|
|
1768
|
+
hideFromCollection: true,
|
|
1769
|
+
disabled: { hidden: true }
|
|
1770
|
+
}
|
|
1771
|
+
},
|
|
1772
|
+
emailVerificationToken: {
|
|
1773
|
+
name: "Email Verification Token",
|
|
1774
|
+
type: "string",
|
|
1775
|
+
columnName: "email_verification_token",
|
|
1776
|
+
ui: {
|
|
1777
|
+
hideFromCollection: true,
|
|
1778
|
+
disabled: { hidden: true }
|
|
1779
|
+
}
|
|
1780
|
+
},
|
|
1781
|
+
emailVerificationSentAt: {
|
|
1782
|
+
name: "Email Verification Sent At",
|
|
1783
|
+
type: "date",
|
|
1784
|
+
columnName: "email_verification_sent_at",
|
|
1785
|
+
ui: {
|
|
1786
|
+
hideFromCollection: true,
|
|
1787
|
+
disabled: { hidden: true }
|
|
1788
|
+
}
|
|
1789
|
+
},
|
|
1790
|
+
metadata: {
|
|
1791
|
+
name: "Metadata",
|
|
1792
|
+
type: "map",
|
|
1793
|
+
keyValue: true,
|
|
1794
|
+
properties: {},
|
|
1795
|
+
defaultValue: {},
|
|
1796
|
+
ui: {
|
|
1797
|
+
hideFromCollection: true,
|
|
1798
|
+
disabled: { hidden: true }
|
|
1799
|
+
}
|
|
1800
|
+
},
|
|
1801
|
+
createdAt: {
|
|
1802
|
+
name: "Created At",
|
|
1803
|
+
type: "date",
|
|
1804
|
+
columnName: "created_at",
|
|
1805
|
+
autoValue: "on_create",
|
|
1806
|
+
ui: { readOnly: true }
|
|
1807
|
+
},
|
|
1808
|
+
updatedAt: {
|
|
1809
|
+
name: "Updated At",
|
|
1810
|
+
type: "date",
|
|
1811
|
+
columnName: "updated_at",
|
|
1812
|
+
autoValue: "on_update",
|
|
1813
|
+
ui: {
|
|
1814
|
+
hideFromCollection: true,
|
|
1815
|
+
disabled: { hidden: true }
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
},
|
|
1819
|
+
listProperties: [
|
|
1820
|
+
"displayName",
|
|
1821
|
+
"email",
|
|
1822
|
+
"roles",
|
|
1823
|
+
"createdAt"
|
|
1824
|
+
],
|
|
1825
|
+
propertiesOrder: [
|
|
1826
|
+
"id",
|
|
1827
|
+
"email",
|
|
1828
|
+
"displayName",
|
|
1829
|
+
"roles",
|
|
1830
|
+
"createdAt"
|
|
1831
|
+
]
|
|
1832
|
+
};
|
|
1833
|
+
//#endregion
|
|
1834
|
+
//#region src/data/query_builder.ts
|
|
2110
1835
|
function or(...conditions) {
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
1836
|
+
return {
|
|
1837
|
+
type: "or",
|
|
1838
|
+
conditions
|
|
1839
|
+
};
|
|
2115
1840
|
}
|
|
2116
1841
|
function and(...conditions) {
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
1842
|
+
return {
|
|
1843
|
+
type: "and",
|
|
1844
|
+
conditions
|
|
1845
|
+
};
|
|
2121
1846
|
}
|
|
2122
1847
|
function cond(column, operator, value) {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
1848
|
+
return {
|
|
1849
|
+
column,
|
|
1850
|
+
operator,
|
|
1851
|
+
value
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
var QueryBuilder = class {
|
|
1855
|
+
collection;
|
|
1856
|
+
params = { where: {} };
|
|
1857
|
+
constructor(collection) {
|
|
1858
|
+
this.collection = collection;
|
|
1859
|
+
}
|
|
1860
|
+
where(columnOrCondition, operator, value) {
|
|
1861
|
+
if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
|
|
1862
|
+
this.params.logical = columnOrCondition;
|
|
1863
|
+
return this;
|
|
1864
|
+
}
|
|
1865
|
+
if (!this.params.where) this.params.where = {};
|
|
1866
|
+
const column = columnOrCondition;
|
|
1867
|
+
const condition = [operator, value];
|
|
1868
|
+
const existing = this.params.where[column];
|
|
1869
|
+
if (existing === void 0) this.params.where[column] = condition;
|
|
1870
|
+
else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) this.params.where[column].push(condition);
|
|
1871
|
+
else {
|
|
1872
|
+
let firstCondition;
|
|
1873
|
+
if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") firstCondition = existing;
|
|
1874
|
+
else firstCondition = ["==", existing];
|
|
1875
|
+
this.params.where[column] = [firstCondition, condition];
|
|
1876
|
+
}
|
|
1877
|
+
return this;
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Order the results by a specific column.
|
|
1881
|
+
* @example
|
|
1882
|
+
* client.collection('users').orderBy('createdAt', 'desc').find()
|
|
1883
|
+
*/
|
|
1884
|
+
orderBy(column, ascending = "asc") {
|
|
1885
|
+
this.params.orderBy = `${column}:${ascending}`;
|
|
1886
|
+
return this;
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Limit the number of results returned.
|
|
1890
|
+
*/
|
|
1891
|
+
limit(count) {
|
|
1892
|
+
this.params.limit = count;
|
|
1893
|
+
return this;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Skip the first N results.
|
|
1897
|
+
*/
|
|
1898
|
+
offset(count) {
|
|
1899
|
+
this.params.offset = count;
|
|
1900
|
+
return this;
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Set a free-text search string if supported by the backend.
|
|
1904
|
+
*/
|
|
1905
|
+
search(searchString) {
|
|
1906
|
+
this.params.searchString = searchString;
|
|
1907
|
+
return this;
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Include related entities in the response.
|
|
1911
|
+
* Relations will be populated with full entity data instead of just IDs.
|
|
1912
|
+
*
|
|
1913
|
+
* @param relations - Relation names to include, or "*" for all.
|
|
1914
|
+
* @example
|
|
1915
|
+
* // Include specific relations
|
|
1916
|
+
* client.data.posts.include("tags", "author").find()
|
|
1917
|
+
*
|
|
1918
|
+
* // Include all relations
|
|
1919
|
+
* client.data.posts.include("*").find()
|
|
1920
|
+
*/
|
|
1921
|
+
include(...relations) {
|
|
1922
|
+
this.params.include = relations;
|
|
1923
|
+
return this;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Execute the find query and return the results.
|
|
1927
|
+
*/
|
|
1928
|
+
async find() {
|
|
1929
|
+
return this.collection.find(this.params);
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Listen to realtime updates matching this query.
|
|
1933
|
+
*/
|
|
1934
|
+
listen(onUpdate, onError) {
|
|
1935
|
+
if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
1936
|
+
return this.collection.listen(this.params, onUpdate, onError);
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
//#endregion
|
|
1940
|
+
//#region src/data/buildRebaseData.ts
|
|
1941
|
+
/**
|
|
1942
|
+
* Convert where-clause filter object to the internal DataDriver FilterValues format.
|
|
1943
|
+
*
|
|
1944
|
+
* Supports multiple value formats:
|
|
1945
|
+
* - PostgREST string: { status: "eq.published", age: "gte.18" }
|
|
1946
|
+
* - Equality shorthand: { company_profile_id: null, status: "active", age: 18 }
|
|
1947
|
+
* - Tuple syntax: { age: [">=", 18], role: ["in", ["admin", "editor"]] }
|
|
1948
|
+
*
|
|
1949
|
+
* Internal: { status: ["==", "published"], age: [">=", 18] }
|
|
1950
|
+
*/
|
|
2224
1951
|
function convertWhereToFilter(where) {
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
const mappedOp = operatorMap[op];
|
|
2291
|
-
if (mappedOp) {
|
|
2292
|
-
filter[field] = [mappedOp, value];
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
return Object.keys(filter).length > 0 ? filter : void 0;
|
|
2297
|
-
}
|
|
1952
|
+
if (!where) return void 0;
|
|
1953
|
+
const operatorMap = {
|
|
1954
|
+
"eq": "==",
|
|
1955
|
+
"neq": "!=",
|
|
1956
|
+
"gt": ">",
|
|
1957
|
+
"gte": ">=",
|
|
1958
|
+
"lt": "<",
|
|
1959
|
+
"lte": "<=",
|
|
1960
|
+
"in": "in",
|
|
1961
|
+
"nin": "not-in",
|
|
1962
|
+
"not-in": "not-in",
|
|
1963
|
+
"cs": "array-contains",
|
|
1964
|
+
"csa": "array-contains-any",
|
|
1965
|
+
"==": "==",
|
|
1966
|
+
"!=": "!=",
|
|
1967
|
+
">": ">",
|
|
1968
|
+
">=": ">=",
|
|
1969
|
+
"<": "<",
|
|
1970
|
+
"<=": "<=",
|
|
1971
|
+
"array-contains": "array-contains",
|
|
1972
|
+
"array-contains-any": "array-contains-any"
|
|
1973
|
+
};
|
|
1974
|
+
const filter = {};
|
|
1975
|
+
for (const [field, rawValue] of Object.entries(where)) {
|
|
1976
|
+
if (rawValue === null) {
|
|
1977
|
+
filter[field] = ["==", null];
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
if (typeof rawValue === "boolean") {
|
|
1981
|
+
filter[field] = ["==", rawValue];
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
if (typeof rawValue === "number") {
|
|
1985
|
+
filter[field] = ["==", rawValue];
|
|
1986
|
+
continue;
|
|
1987
|
+
}
|
|
1988
|
+
if (Array.isArray(rawValue)) {
|
|
1989
|
+
const mappedConditions = (Array.isArray(rawValue[0]) ? rawValue : [rawValue]).map(([rawOp, val]) => {
|
|
1990
|
+
return [operatorMap[rawOp] ?? "==", val];
|
|
1991
|
+
});
|
|
1992
|
+
filter[field] = Array.isArray(rawValue[0]) ? mappedConditions : mappedConditions[0];
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
if (typeof rawValue === "string") {
|
|
1996
|
+
const dotIndex = rawValue.indexOf(".");
|
|
1997
|
+
if (dotIndex === -1) {
|
|
1998
|
+
filter[field] = ["==", rawValue];
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
const op = rawValue.substring(0, dotIndex);
|
|
2002
|
+
let value = rawValue.substring(dotIndex + 1);
|
|
2003
|
+
if (typeof value === "string" && value.startsWith("(") && value.endsWith(")")) value = value.slice(1, -1).split(",").map((v) => v.trim());
|
|
2004
|
+
if (value === "null") value = null;
|
|
2005
|
+
else if (value === "true") value = true;
|
|
2006
|
+
else if (value === "false") value = false;
|
|
2007
|
+
else if (typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "") value = Number(value);
|
|
2008
|
+
const mappedOp = operatorMap[op];
|
|
2009
|
+
if (mappedOp) filter[field] = [mappedOp, value];
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return Object.keys(filter).length > 0 ? filter : void 0;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Parse an orderBy string like "created_at:desc" into [field, direction].
|
|
2016
|
+
*/
|
|
2298
2017
|
function parseOrderBy(orderBy) {
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
const direction = parts[1] || "asc";
|
|
2303
|
-
return [field, direction];
|
|
2018
|
+
if (!orderBy) return void 0;
|
|
2019
|
+
const parts = orderBy.split(":");
|
|
2020
|
+
return [parts[0], parts[1] || "asc"];
|
|
2304
2021
|
}
|
|
2305
2022
|
function createDriverAccessor(driver, slug) {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2023
|
+
const accessor = {
|
|
2024
|
+
async find(params) {
|
|
2025
|
+
const orderParsed = parseOrderBy(params?.orderBy);
|
|
2026
|
+
const entities = await driver.fetchCollection({
|
|
2027
|
+
path: slug,
|
|
2028
|
+
limit: params?.limit,
|
|
2029
|
+
offset: params?.offset,
|
|
2030
|
+
filter: convertWhereToFilter(params?.where),
|
|
2031
|
+
orderBy: orderParsed?.[0],
|
|
2032
|
+
order: orderParsed?.[1],
|
|
2033
|
+
searchString: params?.searchString
|
|
2034
|
+
});
|
|
2035
|
+
const limit = params?.limit ?? 20;
|
|
2036
|
+
const offset = params?.offset ?? 0;
|
|
2037
|
+
return {
|
|
2038
|
+
data: entities,
|
|
2039
|
+
meta: {
|
|
2040
|
+
total: entities.length,
|
|
2041
|
+
limit,
|
|
2042
|
+
offset,
|
|
2043
|
+
hasMore: entities.length >= limit
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
},
|
|
2047
|
+
async findById(id) {
|
|
2048
|
+
return driver.fetchEntity({
|
|
2049
|
+
path: slug,
|
|
2050
|
+
entityId: id
|
|
2051
|
+
});
|
|
2052
|
+
},
|
|
2053
|
+
async create(data, id) {
|
|
2054
|
+
return driver.saveEntity({
|
|
2055
|
+
path: slug,
|
|
2056
|
+
values: data,
|
|
2057
|
+
entityId: id,
|
|
2058
|
+
status: "new"
|
|
2059
|
+
});
|
|
2060
|
+
},
|
|
2061
|
+
async update(id, data) {
|
|
2062
|
+
return driver.saveEntity({
|
|
2063
|
+
path: slug,
|
|
2064
|
+
values: data,
|
|
2065
|
+
entityId: id,
|
|
2066
|
+
status: "existing"
|
|
2067
|
+
});
|
|
2068
|
+
},
|
|
2069
|
+
async delete(id) {
|
|
2070
|
+
return driver.deleteEntity({ entity: {
|
|
2071
|
+
id,
|
|
2072
|
+
path: slug,
|
|
2073
|
+
values: {}
|
|
2074
|
+
} });
|
|
2075
|
+
},
|
|
2076
|
+
deleteAll: driver.deleteAll ? async () => {
|
|
2077
|
+
return driver.deleteAll(slug);
|
|
2078
|
+
} : void 0,
|
|
2079
|
+
count: driver.countEntities ? async (params) => {
|
|
2080
|
+
return driver.countEntities({
|
|
2081
|
+
path: slug,
|
|
2082
|
+
filter: convertWhereToFilter(params?.where)
|
|
2083
|
+
});
|
|
2084
|
+
} : void 0,
|
|
2085
|
+
listen: driver.listenCollection ? (params, onUpdate, onError) => {
|
|
2086
|
+
const orderParsed = parseOrderBy(params?.orderBy);
|
|
2087
|
+
const limit = params?.limit ?? 20;
|
|
2088
|
+
const offset = params?.offset ?? 0;
|
|
2089
|
+
return driver.listenCollection({
|
|
2090
|
+
path: slug,
|
|
2091
|
+
limit: params?.limit,
|
|
2092
|
+
offset: params?.offset,
|
|
2093
|
+
filter: convertWhereToFilter(params?.where),
|
|
2094
|
+
orderBy: orderParsed?.[0],
|
|
2095
|
+
order: orderParsed?.[1],
|
|
2096
|
+
searchString: params?.searchString,
|
|
2097
|
+
onUpdate: (entities) => {
|
|
2098
|
+
onUpdate({
|
|
2099
|
+
data: entities,
|
|
2100
|
+
meta: {
|
|
2101
|
+
total: entities.length,
|
|
2102
|
+
limit,
|
|
2103
|
+
offset,
|
|
2104
|
+
hasMore: entities.length >= limit
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
},
|
|
2108
|
+
onError
|
|
2109
|
+
});
|
|
2110
|
+
} : void 0,
|
|
2111
|
+
listenById: driver.listenEntity ? (id, onUpdate, onError) => {
|
|
2112
|
+
return driver.listenEntity({
|
|
2113
|
+
path: slug,
|
|
2114
|
+
entityId: id,
|
|
2115
|
+
onUpdate: (entity) => onUpdate(entity ?? void 0),
|
|
2116
|
+
onError
|
|
2117
|
+
});
|
|
2118
|
+
} : void 0,
|
|
2119
|
+
where(columnOrCondition, operator, value) {
|
|
2120
|
+
const builder = new QueryBuilder(accessor);
|
|
2121
|
+
if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
|
|
2122
|
+
return builder.where(columnOrCondition, operator, value);
|
|
2123
|
+
},
|
|
2124
|
+
orderBy(column, ascending) {
|
|
2125
|
+
return new QueryBuilder(accessor).orderBy(column, ascending);
|
|
2126
|
+
},
|
|
2127
|
+
limit(count) {
|
|
2128
|
+
return new QueryBuilder(accessor).limit(count);
|
|
2129
|
+
},
|
|
2130
|
+
offset(count) {
|
|
2131
|
+
return new QueryBuilder(accessor).offset(count);
|
|
2132
|
+
},
|
|
2133
|
+
search(searchString) {
|
|
2134
|
+
return new QueryBuilder(accessor).search(searchString);
|
|
2135
|
+
},
|
|
2136
|
+
include(...relations) {
|
|
2137
|
+
return new QueryBuilder(accessor).include(...relations);
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
return accessor;
|
|
2141
|
+
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Build a `RebaseData` object from a `DataDriver` using JavaScript Proxy.
|
|
2144
|
+
*
|
|
2145
|
+
* This is the key bridge: any property access like `data.products` returns
|
|
2146
|
+
* a `CollectionAccessor` backed by the underlying DataDriver, without
|
|
2147
|
+
* needing per-collection code generation.
|
|
2148
|
+
*
|
|
2149
|
+
* @example
|
|
2150
|
+
* const data = buildRebaseData(driver);
|
|
2151
|
+
* await data.products.create({ name: "Camera", price: 299 });
|
|
2152
|
+
* const { data: items } = await data.products.find({ where: { status: "eq.published" } });
|
|
2153
|
+
*/
|
|
2430
2154
|
function buildRebaseData(driver) {
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
});
|
|
2452
|
-
}
|
|
2453
|
-
export {
|
|
2454
|
-
COLLECTION_PATH_SEPARATOR,
|
|
2455
|
-
CollectionRegistry,
|
|
2456
|
-
DEFAULT_ONE_OF_TYPE,
|
|
2457
|
-
DEFAULT_ONE_OF_VALUE,
|
|
2458
|
-
QueryBuilder,
|
|
2459
|
-
addInitialSlash,
|
|
2460
|
-
and,
|
|
2461
|
-
applyPropertyConditions,
|
|
2462
|
-
buildAdditionalFieldDelegate,
|
|
2463
|
-
buildCollection,
|
|
2464
|
-
buildConditionContext,
|
|
2465
|
-
buildEntityCallbacks,
|
|
2466
|
-
buildEnum,
|
|
2467
|
-
buildEnumValueConfig,
|
|
2468
|
-
buildProperties,
|
|
2469
|
-
buildPropertiesOrBuilder,
|
|
2470
|
-
buildProperty,
|
|
2471
|
-
buildPropertyCallbacks,
|
|
2472
|
-
buildRebaseData,
|
|
2473
|
-
canCreateEntity,
|
|
2474
|
-
canDeleteEntity,
|
|
2475
|
-
canEditEntity,
|
|
2476
|
-
canReadCollection,
|
|
2477
|
-
checkOperation,
|
|
2478
|
-
cond,
|
|
2479
|
-
createRelationRef,
|
|
2480
|
-
createRelationRefWithData,
|
|
2481
|
-
defaultUsersCollection,
|
|
2482
|
-
enumToObjectEntries,
|
|
2483
|
-
evaluateCondition,
|
|
2484
|
-
findRelation,
|
|
2485
|
-
fullPathToCollectionSegments,
|
|
2486
|
-
getArrayResolvedProperties,
|
|
2487
|
-
getCollectionBySlugWithin,
|
|
2488
|
-
getCollectionPathsCombinations,
|
|
2489
|
-
getColumnName,
|
|
2490
|
-
getDefaultValueFor,
|
|
2491
|
-
getDefaultValueFortype,
|
|
2492
|
-
getDefaultValuesFor,
|
|
2493
|
-
getEntityImagePreviewPropertyKey,
|
|
2494
|
-
getEnumVarName,
|
|
2495
|
-
getLabelOrConfigFrom,
|
|
2496
|
-
getLastSegment,
|
|
2497
|
-
getLocalChangesBackup,
|
|
2498
|
-
getNavigationEntriesFromPath,
|
|
2499
|
-
getParentReferencesFromPath,
|
|
2500
|
-
getPrimaryKeys,
|
|
2501
|
-
getReferenceFrom,
|
|
2502
|
-
getRelationFrom,
|
|
2503
|
-
getSubcollections,
|
|
2504
|
-
getTableName,
|
|
2505
|
-
getTableVarName,
|
|
2506
|
-
isHidden,
|
|
2507
|
-
isPropertyBuilder,
|
|
2508
|
-
isReadOnly,
|
|
2509
|
-
normalizeToEntityRelation,
|
|
2510
|
-
or,
|
|
2511
|
-
registerConditionOperations,
|
|
2512
|
-
removeInitialAndTrailingSlashes,
|
|
2513
|
-
removeInitialSlash,
|
|
2514
|
-
removeTrailingSlash,
|
|
2515
|
-
resolveArrayProperties,
|
|
2516
|
-
resolveCollectionPathIds,
|
|
2517
|
-
resolveCollectionRelations,
|
|
2518
|
-
resolveDefaultSelectedView,
|
|
2519
|
-
resolveEnumValues,
|
|
2520
|
-
resolveProperties,
|
|
2521
|
-
resolveProperty,
|
|
2522
|
-
resolvePropertyEnum,
|
|
2523
|
-
resolvePropertyRelation,
|
|
2524
|
-
resolveRelationProperty,
|
|
2525
|
-
resolveStorageFilenameString,
|
|
2526
|
-
resolveStoragePathString,
|
|
2527
|
-
sanitizeData,
|
|
2528
|
-
sanitizeRelation,
|
|
2529
|
-
segmentsToStrippedPath,
|
|
2530
|
-
sortProperties,
|
|
2531
|
-
stripCollectionPath,
|
|
2532
|
-
traverseValueProperty,
|
|
2533
|
-
traverseValuesProperties,
|
|
2534
|
-
updateDateAutoValues
|
|
2535
|
-
};
|
|
2536
|
-
//# sourceMappingURL=index.es.js.map
|
|
2155
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2156
|
+
function getAccessor(slug) {
|
|
2157
|
+
let accessor = cache.get(slug);
|
|
2158
|
+
if (!accessor) {
|
|
2159
|
+
accessor = createDriverAccessor(driver, slug);
|
|
2160
|
+
cache.set(slug, accessor);
|
|
2161
|
+
}
|
|
2162
|
+
return accessor;
|
|
2163
|
+
}
|
|
2164
|
+
return new Proxy({ collection: getAccessor }, { get(_target, prop) {
|
|
2165
|
+
if (prop === "collection") return getAccessor;
|
|
2166
|
+
if (typeof prop === "symbol") return void 0;
|
|
2167
|
+
if (prop === "then" || prop === "toJSON" || prop === "$$typeof") return void 0;
|
|
2168
|
+
return getAccessor(toSnakeCase(prop));
|
|
2169
|
+
} });
|
|
2170
|
+
}
|
|
2171
|
+
//#endregion
|
|
2172
|
+
export { COLLECTION_PATH_SEPARATOR, CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, QueryBuilder, addInitialSlash, and, applyPropertyConditions, buildAdditionalFieldDelegate, buildCollection, buildConditionContext, buildEntityCallbacks, buildEnum, buildEnumValueConfig, buildProperties, buildPropertiesOrBuilder, buildProperty, buildPropertyCallbacks, buildRebaseData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, cond, createRelationRef, createRelationRefWithData, defaultUsersCollection, enumToObjectEntries, evaluateCondition, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getColumnName, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityImagePreviewPropertyKey, getEnumVarName, getLabelOrConfigFrom, getLastSegment, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, isHidden, isPropertyBuilder, isReadOnly, normalizeToEntityRelation, or, registerConditionOperations, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionRelations, resolveDefaultSelectedView, resolveEnumValues, resolveProperties, resolveProperty, resolvePropertyEnum, resolvePropertyRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, sanitizeData, sanitizeRelation, segmentsToStrippedPath, sortProperties, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues };
|
|
2173
|
+
|
|
2174
|
+
//# sourceMappingURL=index.es.js.map
|