@rebasepro/common 0.0.1-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/collections/CollectionRegistry.d.ts +48 -0
- package/dist/collections/index.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.es.js +2380 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +2379 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/util/arrays.d.ts +1 -0
- package/dist/util/builders.d.ts +64 -0
- package/dist/util/callbacks.d.ts +6 -0
- package/dist/util/collections.d.ts +11 -0
- package/dist/util/common.d.ts +2 -0
- package/dist/util/conditions.d.ts +26 -0
- package/dist/util/dates.d.ts +1 -0
- package/dist/util/entities.d.ts +28 -0
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/enums.d.ts +3 -0
- package/dist/util/fields.d.ts +2 -0
- package/dist/util/flatten_object.d.ts +5 -0
- package/dist/util/hash.d.ts +1 -0
- package/dist/util/index.d.ts +26 -0
- package/dist/util/names.d.ts +22 -0
- package/dist/util/navigation_from_path.d.ts +29 -0
- package/dist/util/navigation_utils.d.ts +31 -0
- package/dist/util/objects.d.ts +26 -0
- package/dist/util/os.d.ts +2 -0
- package/dist/util/parent_references_from_path.d.ts +6 -0
- package/dist/util/paths.d.ts +14 -0
- package/dist/util/permissions.d.ts +5 -0
- package/dist/util/permissions.test.d.ts +1 -0
- package/dist/util/plurals.d.ts +16 -0
- package/dist/util/references.d.ts +2 -0
- package/dist/util/regexp.d.ts +7 -0
- package/dist/util/relations.d.ts +12 -0
- package/dist/util/resolutions.d.ts +74 -0
- package/dist/util/storage.d.ts +24 -0
- package/dist/util/strings.d.ts +7 -0
- package/package.json +118 -0
- package/src/collections/CollectionRegistry.ts +319 -0
- package/src/collections/index.ts +1 -0
- package/src/index.ts +2 -0
- package/src/util/arrays.ts +3 -0
- package/src/util/builders.ts +138 -0
- package/src/util/callbacks.ts +115 -0
- package/src/util/collections.ts +126 -0
- package/src/util/common.ts +2 -0
- package/src/util/conditions.ts +348 -0
- package/src/util/dates.ts +1 -0
- package/src/util/entities.ts +212 -0
- package/src/util/entity_actions.ts +28 -0
- package/src/util/enums.ts +26 -0
- package/src/util/fields.ts +28 -0
- package/src/util/flatten_object.ts +45 -0
- package/src/util/hash.ts +11 -0
- package/src/util/index.ts +26 -0
- package/src/util/names.ts +30 -0
- package/src/util/navigation_from_path.ts +121 -0
- package/src/util/navigation_utils.ts +222 -0
- package/src/util/objects.ts +376 -0
- package/src/util/os.ts +13 -0
- package/src/util/parent_references_from_path.ts +57 -0
- package/src/util/paths.ts +27 -0
- package/src/util/permissions.test.ts +716 -0
- package/src/util/permissions.ts +235 -0
- package/src/util/plurals.ts +188 -0
- package/src/util/references.ts +34 -0
- package/src/util/regexp.ts +32 -0
- package/src/util/relations.ts +211 -0
- package/src/util/resolutions.ts +383 -0
- package/src/util/storage.ts +144 -0
- package/src/util/strings.ts +84 -0
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,2380 @@
|
|
|
1
|
+
import { GeoPoint, EntityReference, EntityRelation } from "@rebasepro/types";
|
|
2
|
+
import hash from "object-hash";
|
|
3
|
+
import jsonLogic from "json-logic-js";
|
|
4
|
+
import { deepEqual } from "fast-equals";
|
|
5
|
+
import cloneDeep from "lodash/cloneDeep.js";
|
|
6
|
+
const DEFAULT_ONE_OF_TYPE = "type";
|
|
7
|
+
const DEFAULT_ONE_OF_VALUE = "value";
|
|
8
|
+
const isEmptyArray = (value) => Array.isArray(value) && value.length === 0;
|
|
9
|
+
const isFunction = (obj) => typeof obj === "function";
|
|
10
|
+
const isInteger = (obj) => String(Math.floor(Number(obj))) === String(obj);
|
|
11
|
+
const isNaN$1 = (obj) => obj !== obj;
|
|
12
|
+
function getIn$1(obj, key, def, p = 0) {
|
|
13
|
+
const path = toPath(key);
|
|
14
|
+
while (obj && p < path.length) {
|
|
15
|
+
obj = obj[path[p++]];
|
|
16
|
+
}
|
|
17
|
+
if (p !== path.length && !obj) {
|
|
18
|
+
return def;
|
|
19
|
+
}
|
|
20
|
+
return obj === void 0 ? def : obj;
|
|
21
|
+
}
|
|
22
|
+
function setIn(obj, path, value) {
|
|
23
|
+
const res = clone(obj);
|
|
24
|
+
let resVal = res;
|
|
25
|
+
let i = 0;
|
|
26
|
+
const pathArray = toPath(path);
|
|
27
|
+
for (; i < pathArray.length - 1; i++) {
|
|
28
|
+
const currentPath = pathArray[i];
|
|
29
|
+
const currentObj = getIn$1(obj, pathArray.slice(0, i + 1));
|
|
30
|
+
if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {
|
|
31
|
+
resVal = resVal[currentPath] = clone(currentObj);
|
|
32
|
+
} else {
|
|
33
|
+
const nextPath = pathArray[i + 1];
|
|
34
|
+
resVal = resVal[currentPath] = isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if ((i === 0 ? obj : resVal)[pathArray[i]] === value) {
|
|
38
|
+
return obj;
|
|
39
|
+
}
|
|
40
|
+
if (value === void 0) {
|
|
41
|
+
delete resVal[pathArray[i]];
|
|
42
|
+
} else {
|
|
43
|
+
resVal[pathArray[i]] = value;
|
|
44
|
+
}
|
|
45
|
+
if (i === 0 && value === void 0) {
|
|
46
|
+
delete res[pathArray[i]];
|
|
47
|
+
}
|
|
48
|
+
return res;
|
|
49
|
+
}
|
|
50
|
+
function clone(value) {
|
|
51
|
+
if (Array.isArray(value)) {
|
|
52
|
+
return [...value];
|
|
53
|
+
} else if (typeof value === "object" && value !== null) {
|
|
54
|
+
return {
|
|
55
|
+
...value
|
|
56
|
+
};
|
|
57
|
+
} else {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function toPath(value) {
|
|
62
|
+
if (Array.isArray(value)) return value;
|
|
63
|
+
return value.replace(/\[(\d+)]/g, ".$1").replace(/^\./, "").replace(/\.$/, "").split(".");
|
|
64
|
+
}
|
|
65
|
+
const pick = (obj, ...args) => ({
|
|
66
|
+
...args.reduce((res, key) => ({
|
|
67
|
+
...res,
|
|
68
|
+
[key]: obj[key]
|
|
69
|
+
}), {})
|
|
70
|
+
});
|
|
71
|
+
function isObject(item) {
|
|
72
|
+
return !!item && typeof item === "object" && !Array.isArray(item);
|
|
73
|
+
}
|
|
74
|
+
function isPlainObject(obj) {
|
|
75
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const proto = Object.getPrototypeOf(obj);
|
|
79
|
+
return proto === Object.prototype;
|
|
80
|
+
}
|
|
81
|
+
function mergeDeep(target, source, ignoreUndefined = false) {
|
|
82
|
+
if (!isObject(target)) {
|
|
83
|
+
return target;
|
|
84
|
+
}
|
|
85
|
+
const output = {
|
|
86
|
+
...target
|
|
87
|
+
};
|
|
88
|
+
if (!isObject(source)) {
|
|
89
|
+
return output;
|
|
90
|
+
}
|
|
91
|
+
for (const key in source) {
|
|
92
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
93
|
+
const sourceValue = source[key];
|
|
94
|
+
const outputValue = output[key];
|
|
95
|
+
if (ignoreUndefined && sourceValue === void 0) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (sourceValue instanceof Date) {
|
|
99
|
+
output[key] = new Date(sourceValue.getTime());
|
|
100
|
+
} else if (Array.isArray(sourceValue)) {
|
|
101
|
+
if (Array.isArray(outputValue)) {
|
|
102
|
+
const newArray = [];
|
|
103
|
+
const maxLength = Math.max(outputValue.length, sourceValue.length);
|
|
104
|
+
for (let i = 0; i < maxLength; i++) {
|
|
105
|
+
const sourceItem = sourceValue[i];
|
|
106
|
+
const targetItem = outputValue[i];
|
|
107
|
+
if (i >= sourceValue.length) {
|
|
108
|
+
newArray[i] = targetItem;
|
|
109
|
+
} else if (i >= outputValue.length) {
|
|
110
|
+
newArray[i] = sourceItem;
|
|
111
|
+
} else if (sourceItem === null) {
|
|
112
|
+
newArray[i] = targetItem;
|
|
113
|
+
} else if (isPlainObject(sourceItem) && isPlainObject(targetItem)) {
|
|
114
|
+
newArray[i] = mergeDeep(targetItem, sourceItem, ignoreUndefined);
|
|
115
|
+
} else {
|
|
116
|
+
newArray[i] = sourceItem;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
output[key] = newArray;
|
|
120
|
+
} else {
|
|
121
|
+
output[key] = [...sourceValue];
|
|
122
|
+
}
|
|
123
|
+
} else if (isPlainObject(sourceValue)) {
|
|
124
|
+
if (isPlainObject(outputValue)) {
|
|
125
|
+
output[key] = mergeDeep(outputValue, sourceValue, ignoreUndefined);
|
|
126
|
+
} else {
|
|
127
|
+
output[key] = sourceValue;
|
|
128
|
+
}
|
|
129
|
+
} else if (isObject(sourceValue)) {
|
|
130
|
+
output[key] = sourceValue;
|
|
131
|
+
} else {
|
|
132
|
+
output[key] = sourceValue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return output;
|
|
137
|
+
}
|
|
138
|
+
function getValueInPath(o, path) {
|
|
139
|
+
if (!o) return void 0;
|
|
140
|
+
if (typeof o === "object") {
|
|
141
|
+
if (path in o) {
|
|
142
|
+
return o[path];
|
|
143
|
+
}
|
|
144
|
+
if (path.includes(".") || path.includes("[")) {
|
|
145
|
+
let pathSegments = path.split(/[.[]/);
|
|
146
|
+
if (path.includes("[")) {
|
|
147
|
+
pathSegments = pathSegments.map((segment) => segment.replace("]", ""));
|
|
148
|
+
}
|
|
149
|
+
const firstSegment = pathSegments[0];
|
|
150
|
+
const isArrayAndIndexExists = Array.isArray(o[firstSegment]) && !isNaN$1(parseInt(pathSegments[1]));
|
|
151
|
+
const nextObject = isArrayAndIndexExists ? o[firstSegment][parseInt(pathSegments[1])] : o[firstSegment];
|
|
152
|
+
const nextPath = pathSegments.slice(isArrayAndIndexExists ? 2 : 1).join(".");
|
|
153
|
+
if (nextPath === "") return nextObject;
|
|
154
|
+
return getValueInPath(nextObject, nextPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
function removeInPath(o, path) {
|
|
160
|
+
let currentObject = {
|
|
161
|
+
...o
|
|
162
|
+
};
|
|
163
|
+
const parts = path.split(".");
|
|
164
|
+
const last = parts.pop();
|
|
165
|
+
for (const part of parts) {
|
|
166
|
+
currentObject = currentObject[part];
|
|
167
|
+
}
|
|
168
|
+
if (last) delete currentObject[last];
|
|
169
|
+
return currentObject;
|
|
170
|
+
}
|
|
171
|
+
function removeFunctions(o) {
|
|
172
|
+
if (o === void 0) return void 0;
|
|
173
|
+
if (o === null) return null;
|
|
174
|
+
if (typeof o === "object") {
|
|
175
|
+
if (Array.isArray(o)) {
|
|
176
|
+
return o.map((v) => removeFunctions(v));
|
|
177
|
+
}
|
|
178
|
+
if (!isPlainObject(o)) {
|
|
179
|
+
return o;
|
|
180
|
+
}
|
|
181
|
+
return Object.entries(o).filter(([_, value]) => typeof value !== "function").map(([key, value]) => {
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
return {
|
|
184
|
+
[key]: value.map((v) => removeFunctions(v))
|
|
185
|
+
};
|
|
186
|
+
} else if (typeof value === "object") {
|
|
187
|
+
return {
|
|
188
|
+
[key]: removeFunctions(value)
|
|
189
|
+
};
|
|
190
|
+
} else return {
|
|
191
|
+
[key]: value
|
|
192
|
+
};
|
|
193
|
+
}).reduce((a, b) => ({
|
|
194
|
+
...a,
|
|
195
|
+
...b
|
|
196
|
+
}), {});
|
|
197
|
+
}
|
|
198
|
+
return o;
|
|
199
|
+
}
|
|
200
|
+
function getHashValue(v) {
|
|
201
|
+
if (!v) return null;
|
|
202
|
+
if (typeof v === "object" && v !== null) {
|
|
203
|
+
if ("id" in v) return String(v.id);
|
|
204
|
+
else if (v instanceof Date) return v.toLocaleString();
|
|
205
|
+
else if (v instanceof GeoPoint) return hash(v);
|
|
206
|
+
}
|
|
207
|
+
return hash(v, {
|
|
208
|
+
ignoreUnknown: true
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function removeUndefined(value, removeEmptyStrings) {
|
|
212
|
+
if (typeof value === "function") {
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
if (Array.isArray(value)) {
|
|
216
|
+
return value.map((v) => removeUndefined(v, removeEmptyStrings));
|
|
217
|
+
}
|
|
218
|
+
if (typeof value === "object") {
|
|
219
|
+
if (value === null) return value;
|
|
220
|
+
if (!isPlainObject(value)) {
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
const res = {};
|
|
224
|
+
Object.keys(value).forEach((key) => {
|
|
225
|
+
if (!isEmptyObject(value)) {
|
|
226
|
+
const childRes = removeUndefined(value[key], removeEmptyStrings);
|
|
227
|
+
const isString = typeof childRes === "string";
|
|
228
|
+
const shouldKeepIfString = !removeEmptyStrings || removeEmptyStrings && !isString || removeEmptyStrings && isString && childRes !== "";
|
|
229
|
+
if (childRes !== void 0 && !isEmptyObject(childRes) && shouldKeepIfString) res[key] = childRes;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return res;
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
function removeNulls(value) {
|
|
237
|
+
if (typeof value === "function") {
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
if (Array.isArray(value)) {
|
|
241
|
+
return value.map((v) => removeNulls(v));
|
|
242
|
+
}
|
|
243
|
+
if (typeof value === "object") {
|
|
244
|
+
if (value === null) return value;
|
|
245
|
+
if (!isPlainObject(value)) {
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
const res = {};
|
|
249
|
+
const obj = value;
|
|
250
|
+
Object.keys(obj).forEach((key) => {
|
|
251
|
+
if (obj[key] !== null) res[key] = removeNulls(obj[key]);
|
|
252
|
+
});
|
|
253
|
+
return res;
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
function isEmptyObject(obj) {
|
|
258
|
+
return obj && Object.getPrototypeOf(obj) === Object.prototype && Object.keys(obj).length === 0;
|
|
259
|
+
}
|
|
260
|
+
function removePropsIfExisting(source, comparison) {
|
|
261
|
+
const isObject2 = (val) => typeof val === "object" && val !== null;
|
|
262
|
+
const isArray = (val) => Array.isArray(val);
|
|
263
|
+
if (!isObject2(source) || !isObject2(comparison)) {
|
|
264
|
+
return source;
|
|
265
|
+
}
|
|
266
|
+
const res = isArray(source) ? [...source] : {
|
|
267
|
+
...source
|
|
268
|
+
};
|
|
269
|
+
if (isArray(res)) {
|
|
270
|
+
for (let i = res.length - 1; i >= 0; i--) {
|
|
271
|
+
if (res[i] === comparison[i]) {
|
|
272
|
+
res.splice(i, 1);
|
|
273
|
+
} else if (isObject2(res[i]) && isObject2(comparison[i])) {
|
|
274
|
+
res[i] = removePropsIfExisting(res[i], comparison[i]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
Object.keys(comparison).forEach((key) => {
|
|
279
|
+
if (key in res) {
|
|
280
|
+
if (isObject2(res[key]) && isObject2(comparison[key])) {
|
|
281
|
+
res[key] = removePropsIfExisting(res[key], comparison[key]);
|
|
282
|
+
} else if (res[key] === comparison[key]) {
|
|
283
|
+
delete res[key];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return res;
|
|
289
|
+
}
|
|
290
|
+
function isReadOnly(property) {
|
|
291
|
+
if (property.readOnly) return true;
|
|
292
|
+
if (property.type === "date") {
|
|
293
|
+
if (property.autoValue) return true;
|
|
294
|
+
}
|
|
295
|
+
if (property.type === "reference") {
|
|
296
|
+
return !property.path && !property.Field;
|
|
297
|
+
}
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
function isHidden(property) {
|
|
301
|
+
return typeof property.disabled === "object" && Boolean(property.disabled.hidden);
|
|
302
|
+
}
|
|
303
|
+
function isPropertyBuilder(property) {
|
|
304
|
+
return typeof property?.dynamicProps === "function";
|
|
305
|
+
}
|
|
306
|
+
function getDefaultValuesFor(properties) {
|
|
307
|
+
if (!properties) return {};
|
|
308
|
+
return Object.entries(properties).map(([key, property]) => {
|
|
309
|
+
if (!property) return {};
|
|
310
|
+
const value = getDefaultValueFor(property);
|
|
311
|
+
return value === void 0 ? {} : {
|
|
312
|
+
[key]: value
|
|
313
|
+
};
|
|
314
|
+
}).reduce((a, b) => ({
|
|
315
|
+
...a,
|
|
316
|
+
...b
|
|
317
|
+
}), {});
|
|
318
|
+
}
|
|
319
|
+
function getDefaultValueFor(property) {
|
|
320
|
+
if (!property) return void 0;
|
|
321
|
+
if (isPropertyBuilder(property)) return void 0;
|
|
322
|
+
if (property.defaultValue || property.defaultValue === null) {
|
|
323
|
+
return property.defaultValue;
|
|
324
|
+
} else if (property.type === "map" && property.properties) {
|
|
325
|
+
const defaultValuesFor = getDefaultValuesFor(property.properties);
|
|
326
|
+
if (Object.keys(defaultValuesFor).length === 0) return void 0;
|
|
327
|
+
return defaultValuesFor;
|
|
328
|
+
} else {
|
|
329
|
+
return getDefaultValueFortype(property.type);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function getDefaultValueFortype(type) {
|
|
333
|
+
if (type === "string") {
|
|
334
|
+
return null;
|
|
335
|
+
} else if (type === "number") {
|
|
336
|
+
return null;
|
|
337
|
+
} else if (type === "boolean") {
|
|
338
|
+
return false;
|
|
339
|
+
} else if (type === "date") {
|
|
340
|
+
return null;
|
|
341
|
+
} else if (type === "array") {
|
|
342
|
+
return [];
|
|
343
|
+
} else if (type === "map") {
|
|
344
|
+
return {};
|
|
345
|
+
} else {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function updateDateAutoValues({
|
|
350
|
+
inputValues,
|
|
351
|
+
properties,
|
|
352
|
+
status,
|
|
353
|
+
timestampNowValue
|
|
354
|
+
}) {
|
|
355
|
+
return traverseValuesProperties(inputValues, properties, (inputValue, property) => {
|
|
356
|
+
if (property.type === "date") {
|
|
357
|
+
if (status === "existing" && property.autoValue === "on_update") {
|
|
358
|
+
return timestampNowValue;
|
|
359
|
+
} else if ((status === "new" || status === "copy") && (property.autoValue === "on_update" || property.autoValue === "on_create")) {
|
|
360
|
+
return timestampNowValue;
|
|
361
|
+
} else {
|
|
362
|
+
return inputValue;
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
return inputValue;
|
|
366
|
+
}
|
|
367
|
+
}) ?? {};
|
|
368
|
+
}
|
|
369
|
+
function sanitizeData(values, properties) {
|
|
370
|
+
const result = values;
|
|
371
|
+
Object.entries(properties).forEach(([key, property]) => {
|
|
372
|
+
if (values && values[key] !== void 0) result[key] = values[key];
|
|
373
|
+
else if (property.validation?.required) result[key] = null;
|
|
374
|
+
});
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
function getReferenceFrom(entity) {
|
|
378
|
+
if (typeof entity.id !== "string") throw new Error("Only string IDs are supported in references");
|
|
379
|
+
return new EntityReference({
|
|
380
|
+
id: entity.id,
|
|
381
|
+
path: entity.path,
|
|
382
|
+
datasource: entity.datasource,
|
|
383
|
+
databaseId: entity.databaseId
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function getRelationFrom(entity) {
|
|
387
|
+
return new EntityRelation(entity.id, entity.path);
|
|
388
|
+
}
|
|
389
|
+
function traverseValuesProperties(inputValues, properties, operation) {
|
|
390
|
+
const safeInputValues = inputValues ?? {};
|
|
391
|
+
const updatedValues = Object.entries(properties).map(([key, property]) => {
|
|
392
|
+
const inputValue = safeInputValues && safeInputValues[key];
|
|
393
|
+
const updatedValue = traverseValueProperty(inputValue, property, operation);
|
|
394
|
+
if (updatedValue === null) return null;
|
|
395
|
+
if (updatedValue === void 0) return void 0;
|
|
396
|
+
return {
|
|
397
|
+
[key]: updatedValue
|
|
398
|
+
};
|
|
399
|
+
}).reduce((a, b) => ({
|
|
400
|
+
...a,
|
|
401
|
+
...b
|
|
402
|
+
}), {});
|
|
403
|
+
const result = mergeDeep(safeInputValues, updatedValues);
|
|
404
|
+
if (!result || Object.keys(result).length === 0) return void 0;
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
function traverseValueProperty(inputValue, property, operation) {
|
|
408
|
+
let value;
|
|
409
|
+
if (property.type === "map" && property.properties) {
|
|
410
|
+
value = traverseValuesProperties(inputValue, property.properties, operation);
|
|
411
|
+
} else if (property.type === "array") {
|
|
412
|
+
const of = property.of;
|
|
413
|
+
if (of && Array.isArray(inputValue) && !Array.isArray(of)) {
|
|
414
|
+
value = inputValue.map((e) => traverseValueProperty(e, of, operation));
|
|
415
|
+
} else if (of && Array.isArray(inputValue) && Array.isArray(of)) {
|
|
416
|
+
value = inputValue.map((e, i) => {
|
|
417
|
+
if (i < of.length) return traverseValueProperty(e, of[i], operation);
|
|
418
|
+
return null;
|
|
419
|
+
}).filter(Boolean);
|
|
420
|
+
} else if (property.oneOf && Array.isArray(inputValue)) {
|
|
421
|
+
const typeField = property.oneOf?.typeField ?? DEFAULT_ONE_OF_TYPE;
|
|
422
|
+
const valueField = property.oneOf?.valueField ?? DEFAULT_ONE_OF_VALUE;
|
|
423
|
+
value = inputValue.map((e) => {
|
|
424
|
+
if (e === null) return null;
|
|
425
|
+
if (typeof e !== "object") return e;
|
|
426
|
+
const rec = e;
|
|
427
|
+
const type = rec[typeField];
|
|
428
|
+
const childProperty = property.oneOf?.properties[type];
|
|
429
|
+
if (!type || !childProperty) return e;
|
|
430
|
+
return {
|
|
431
|
+
[typeField]: type,
|
|
432
|
+
[valueField]: traverseValueProperty(rec[valueField], childProperty, operation)
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
} else {
|
|
436
|
+
value = inputValue;
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
value = operation(inputValue, property);
|
|
440
|
+
}
|
|
441
|
+
return value;
|
|
442
|
+
}
|
|
443
|
+
function sortProperties(properties, propertiesOrder) {
|
|
444
|
+
try {
|
|
445
|
+
const propertiesKeys = Object.keys(properties);
|
|
446
|
+
if (!propertiesOrder || propertiesOrder.length === 0) {
|
|
447
|
+
return propertiesKeys.map((key) => {
|
|
448
|
+
const property = properties[key];
|
|
449
|
+
if (!isPropertyBuilder(property) && property?.type === "map" && property.properties) {
|
|
450
|
+
return {
|
|
451
|
+
[key]: {
|
|
452
|
+
...property,
|
|
453
|
+
properties: sortProperties(property.properties, property.propertiesOrder)
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
} else {
|
|
457
|
+
return {
|
|
458
|
+
[key]: property
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}).reduce((a, b) => ({
|
|
462
|
+
...a,
|
|
463
|
+
...b
|
|
464
|
+
}), {});
|
|
465
|
+
}
|
|
466
|
+
const validOrderKeys = propertiesOrder.filter((key) => {
|
|
467
|
+
return !key.includes(".") && properties[key];
|
|
468
|
+
});
|
|
469
|
+
const processedKeys = new Set(validOrderKeys);
|
|
470
|
+
const orderedResult = validOrderKeys.map((key) => {
|
|
471
|
+
const property = properties[key];
|
|
472
|
+
if (!isPropertyBuilder(property) && property?.type === "map" && property.properties) {
|
|
473
|
+
return {
|
|
474
|
+
[key]: {
|
|
475
|
+
...property,
|
|
476
|
+
properties: sortProperties(property.properties, property.propertiesOrder)
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
} else {
|
|
480
|
+
return {
|
|
481
|
+
[key]: property
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}).reduce((a, b) => ({
|
|
485
|
+
...a,
|
|
486
|
+
...b
|
|
487
|
+
}), {});
|
|
488
|
+
const missingProperties = propertiesKeys.filter((key) => !processedKeys.has(key)).map((key) => {
|
|
489
|
+
const property = properties[key];
|
|
490
|
+
if (!isPropertyBuilder(property) && property?.type === "map" && property.properties) {
|
|
491
|
+
return {
|
|
492
|
+
[key]: {
|
|
493
|
+
...property,
|
|
494
|
+
properties: sortProperties(property.properties, property.propertiesOrder)
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
} else {
|
|
498
|
+
return {
|
|
499
|
+
[key]: property
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}).reduce((a, b) => ({
|
|
503
|
+
...a,
|
|
504
|
+
...b
|
|
505
|
+
}), {});
|
|
506
|
+
return {
|
|
507
|
+
...orderedResult,
|
|
508
|
+
...missingProperties
|
|
509
|
+
};
|
|
510
|
+
} catch (e) {
|
|
511
|
+
console.error("Error sorting properties", e);
|
|
512
|
+
return properties;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function resolveDefaultSelectedView(defaultSelectedView, params) {
|
|
516
|
+
if (!defaultSelectedView) {
|
|
517
|
+
return void 0;
|
|
518
|
+
} else if (typeof defaultSelectedView === "string") {
|
|
519
|
+
return defaultSelectedView;
|
|
520
|
+
} else {
|
|
521
|
+
return defaultSelectedView(params);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function getLocalChangesBackup(collection) {
|
|
525
|
+
if (!collection.localChangesBackup) {
|
|
526
|
+
return "manual_apply";
|
|
527
|
+
}
|
|
528
|
+
return collection.localChangesBackup;
|
|
529
|
+
}
|
|
530
|
+
function getPrimaryKeys(collection) {
|
|
531
|
+
const properties = collection.properties;
|
|
532
|
+
if (!properties) {
|
|
533
|
+
return ["id"];
|
|
534
|
+
}
|
|
535
|
+
const ids = Object.entries(properties).filter(([key, prop]) => typeof prop === "object" && prop !== null && "isId" in prop && Boolean(prop.isId)).map(([key]) => key);
|
|
536
|
+
if (ids.length > 0) {
|
|
537
|
+
return ids;
|
|
538
|
+
}
|
|
539
|
+
return ["id"];
|
|
540
|
+
}
|
|
541
|
+
const kebabCaseRegex = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g;
|
|
542
|
+
const toKebabCase = (str) => {
|
|
543
|
+
const regExpMatchArray = str.match(kebabCaseRegex);
|
|
544
|
+
if (!regExpMatchArray) return "";
|
|
545
|
+
return regExpMatchArray.map((x) => x.toLowerCase()).join("-");
|
|
546
|
+
};
|
|
547
|
+
const snakeCaseRegex = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g;
|
|
548
|
+
const toSnakeCase = (str) => {
|
|
549
|
+
const regExpMatchArray = str.match(snakeCaseRegex);
|
|
550
|
+
if (!regExpMatchArray) return "";
|
|
551
|
+
return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
|
|
552
|
+
};
|
|
553
|
+
function randomString(strLength = 5) {
|
|
554
|
+
return Math.random().toString(36).slice(2, 2 + strLength);
|
|
555
|
+
}
|
|
556
|
+
function randomColor() {
|
|
557
|
+
return Math.floor(Math.random() * 16777215).toString(16);
|
|
558
|
+
}
|
|
559
|
+
function slugify(text, separator = "_", lowercase = true) {
|
|
560
|
+
if (!text) return "";
|
|
561
|
+
const from = "ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;-";
|
|
562
|
+
const to = `aaaaaeeeeeiiiiooooouuuunc${separator}${separator}${separator}${separator}${separator}${separator}${separator}`;
|
|
563
|
+
for (let i = 0, l = from.length; i < l; i++) {
|
|
564
|
+
text = text.replace(new RegExp(from.charAt(i), "g"), to.charAt(i));
|
|
565
|
+
}
|
|
566
|
+
text = text.toString().replace(/\s+/g, separator).replace(/&/g, separator).replace(/[^\w\\-]+/g, "").replace(new RegExp("\\" + separator + "\\" + separator + "+", "g"), separator).trim().replace(/^\s+|\s+$/g, "");
|
|
567
|
+
return lowercase ? text.toLowerCase() : text;
|
|
568
|
+
}
|
|
569
|
+
function unslugify(slug) {
|
|
570
|
+
if (!slug) return "";
|
|
571
|
+
if (slug.includes("-") || slug.includes("_") || !slug.includes(" ")) {
|
|
572
|
+
const result = slug.replace(/[-_]/g, " ");
|
|
573
|
+
return result.replace(/\w\S*/g, function(txt) {
|
|
574
|
+
return txt.charAt(0).toUpperCase() + txt.substring(1);
|
|
575
|
+
}).trim();
|
|
576
|
+
} else {
|
|
577
|
+
return slug.trim();
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function prettifyIdentifier(input) {
|
|
581
|
+
if (!input) return "";
|
|
582
|
+
let text = input;
|
|
583
|
+
text = text.replace(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g, "$1$3 $2$4");
|
|
584
|
+
text = text.replace(/[_-]+/g, " ");
|
|
585
|
+
const s = text.trim().replace(/\b\w/g, (char) => char.toUpperCase());
|
|
586
|
+
console.log("Prettified identifier:", {
|
|
587
|
+
input,
|
|
588
|
+
s
|
|
589
|
+
});
|
|
590
|
+
return s;
|
|
591
|
+
}
|
|
592
|
+
const defaultDateFormat = "MMMM dd, yyyy, HH:mm:ss";
|
|
593
|
+
function enumToObjectEntries(enumValues) {
|
|
594
|
+
if (Array.isArray(enumValues)) {
|
|
595
|
+
return enumValues;
|
|
596
|
+
} else {
|
|
597
|
+
return Object.entries(enumValues).map(([id, value]) => {
|
|
598
|
+
if (typeof value === "string") {
|
|
599
|
+
return {
|
|
600
|
+
id,
|
|
601
|
+
label: value
|
|
602
|
+
};
|
|
603
|
+
} else {
|
|
604
|
+
return {
|
|
605
|
+
...value,
|
|
606
|
+
id
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function getLabelOrConfigFrom(enumValues, key) {
|
|
613
|
+
if (key === null || key === void 0) return void 0;
|
|
614
|
+
return enumValues.find((entry) => String(entry.id) === String(key));
|
|
615
|
+
}
|
|
616
|
+
const COLLECTION_PATH_SEPARATOR = "::";
|
|
617
|
+
function stripCollectionPath(path) {
|
|
618
|
+
return segmentsToStrippedPath(fullPathToCollectionSegments(path));
|
|
619
|
+
}
|
|
620
|
+
function segmentsToStrippedPath(paths) {
|
|
621
|
+
if (paths.length === 1) return paths[0];
|
|
622
|
+
return paths.reduce((a, b) => `${a}${COLLECTION_PATH_SEPARATOR}${b}`);
|
|
623
|
+
}
|
|
624
|
+
function fullPathToCollectionSegments(path) {
|
|
625
|
+
return path.split("/").filter((e, i) => i % 2 === 0);
|
|
626
|
+
}
|
|
627
|
+
function serializeRegExp(input) {
|
|
628
|
+
if (!input) return "";
|
|
629
|
+
return input.toString();
|
|
630
|
+
}
|
|
631
|
+
function hydrateRegExp(input) {
|
|
632
|
+
if (!input) return void 0;
|
|
633
|
+
const fragments = input.match(/\/(.*?)\/([a-z]*)?$/i);
|
|
634
|
+
if (fragments) {
|
|
635
|
+
return new RegExp(fragments[1], fragments[2] || "");
|
|
636
|
+
} else {
|
|
637
|
+
return new RegExp(input, "");
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function isValidRegExp(input) {
|
|
641
|
+
const fullRegexp = input.match(/\/((?![*+?])(?:[^\r\n[/\\]|\\.|\[(?:[^\r\n\]\\]|\\.)*])+)\/((?:g(?:im?|mi?)?|i(?:gm?|mg?)?|m(?:gi?|ig?)?)?)/);
|
|
642
|
+
if (fullRegexp) return true;
|
|
643
|
+
const simpleRegexp = input.match(/((?![*+?])(?:[^\r\n[/\\]|\\.|\[(?:[^\r\n\]\\]|\\.)*])+)/);
|
|
644
|
+
return !!simpleRegexp;
|
|
645
|
+
}
|
|
646
|
+
function isDefaultFieldConfigId(id) {
|
|
647
|
+
return ["text_field", "multiline", "markdown", "url", "email", "switch", "select", "multi_select", "number_input", "number_select", "multi_number_select", "file_upload", "multi_file_upload", "reference_as_string", "reference", "multi_references", "relation", "date_time", "group", "key_value", "repeat", "custom_array", "block"].includes(id);
|
|
648
|
+
}
|
|
649
|
+
function resolveProperty(props) {
|
|
650
|
+
const {
|
|
651
|
+
property,
|
|
652
|
+
ignoreMissingFields = false,
|
|
653
|
+
...rest
|
|
654
|
+
} = props;
|
|
655
|
+
let resultProperty;
|
|
656
|
+
if (isPropertyBuilder(property)) {
|
|
657
|
+
const path = rest.path;
|
|
658
|
+
if (!path) throw Error("Trying to resolve a property builder without specifying the entity path");
|
|
659
|
+
const usedPropertyValue = rest.propertyKey ? getIn$1(rest.values, rest.propertyKey) : void 0;
|
|
660
|
+
const dynamicProps = property.dynamicProps?.({
|
|
661
|
+
...rest,
|
|
662
|
+
path,
|
|
663
|
+
propertyValue: usedPropertyValue,
|
|
664
|
+
values: rest.values ?? {},
|
|
665
|
+
previousValues: rest.previousValues ?? rest.values ?? {}
|
|
666
|
+
});
|
|
667
|
+
resultProperty = mergeDeep(property, dynamicProps ?? {});
|
|
668
|
+
} else {
|
|
669
|
+
resultProperty = property;
|
|
670
|
+
}
|
|
671
|
+
if (resultProperty.dynamicProps) {
|
|
672
|
+
const path = rest.path;
|
|
673
|
+
if (!path) throw Error("Trying to resolve dynamicProps without specifying the entity path");
|
|
674
|
+
const usedPropertyValue = rest.propertyKey ? getIn$1(rest.values, rest.propertyKey) : void 0;
|
|
675
|
+
const dynamicPropsResult = resultProperty.dynamicProps({
|
|
676
|
+
...rest,
|
|
677
|
+
path,
|
|
678
|
+
propertyValue: usedPropertyValue,
|
|
679
|
+
values: rest.values ?? {},
|
|
680
|
+
previousValues: rest.previousValues ?? rest.values ?? {}
|
|
681
|
+
});
|
|
682
|
+
if (dynamicPropsResult) {
|
|
683
|
+
resultProperty = mergeDeep(resultProperty, dynamicPropsResult);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
let resolvedProperty;
|
|
687
|
+
if (resultProperty?.type === "map" && resultProperty.properties) {
|
|
688
|
+
const properties = resolveProperties({
|
|
689
|
+
ignoreMissingFields,
|
|
690
|
+
...rest,
|
|
691
|
+
properties: resultProperty.properties
|
|
692
|
+
});
|
|
693
|
+
resolvedProperty = {
|
|
694
|
+
...resultProperty,
|
|
695
|
+
properties
|
|
696
|
+
};
|
|
697
|
+
} else if (resultProperty?.type === "array") {
|
|
698
|
+
resolvedProperty = resultProperty;
|
|
699
|
+
} else if ((resultProperty?.type === "string" || resultProperty?.type === "number") && resultProperty.enum) {
|
|
700
|
+
resolvedProperty = resolvePropertyEnum(resultProperty);
|
|
701
|
+
} else {
|
|
702
|
+
resolvedProperty = resultProperty;
|
|
703
|
+
}
|
|
704
|
+
if (resolvedProperty?.propertyConfig && !isDefaultFieldConfigId(resolvedProperty.propertyConfig)) {
|
|
705
|
+
const cmsFields = rest.propertyConfigs;
|
|
706
|
+
if (!cmsFields && !ignoreMissingFields) {
|
|
707
|
+
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`);
|
|
708
|
+
}
|
|
709
|
+
const customField = cmsFields?.[resolvedProperty.propertyConfig];
|
|
710
|
+
if (!customField) {
|
|
711
|
+
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`);
|
|
712
|
+
return resolvedProperty;
|
|
713
|
+
}
|
|
714
|
+
if (customField.property) {
|
|
715
|
+
const {
|
|
716
|
+
propertyConfig: _unused,
|
|
717
|
+
...restConfigProperty
|
|
718
|
+
} = customField.property;
|
|
719
|
+
const customFieldProperty = resolveProperty({
|
|
720
|
+
property: {
|
|
721
|
+
name: "",
|
|
722
|
+
...restConfigProperty
|
|
723
|
+
},
|
|
724
|
+
ignoreMissingFields,
|
|
725
|
+
...rest
|
|
726
|
+
});
|
|
727
|
+
if (customFieldProperty) {
|
|
728
|
+
resolvedProperty = mergeDeep(customFieldProperty, resolvedProperty);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return resolvedProperty;
|
|
733
|
+
}
|
|
734
|
+
function resolveRelationProperty(property, relations) {
|
|
735
|
+
const relation = relations.find((rel) => rel.relationName === property.relationName);
|
|
736
|
+
if (!relation) {
|
|
737
|
+
throw Error(`Relation ${property.relationName} not found`);
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
...property,
|
|
741
|
+
relation
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
function resolvePropertyEnum(property) {
|
|
745
|
+
if (typeof property.enum === "object") {
|
|
746
|
+
return {
|
|
747
|
+
...property,
|
|
748
|
+
enum: enumToObjectEntries(property.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? []
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return property;
|
|
752
|
+
}
|
|
753
|
+
function resolveProperties({
|
|
754
|
+
propertyKey,
|
|
755
|
+
properties,
|
|
756
|
+
ignoreMissingFields,
|
|
757
|
+
...props
|
|
758
|
+
}) {
|
|
759
|
+
return Object.entries(properties).map(([key, property]) => {
|
|
760
|
+
const childResolvedProperty = resolveProperty({
|
|
761
|
+
propertyKey: propertyKey ? `${propertyKey}.${key}` : void 0,
|
|
762
|
+
property,
|
|
763
|
+
ignoreMissingFields,
|
|
764
|
+
...props
|
|
765
|
+
});
|
|
766
|
+
if (!childResolvedProperty) return {};
|
|
767
|
+
return {
|
|
768
|
+
[key]: childResolvedProperty
|
|
769
|
+
};
|
|
770
|
+
}).filter((a) => a !== null).reduce((a, b) => ({
|
|
771
|
+
...a,
|
|
772
|
+
...b
|
|
773
|
+
}), {});
|
|
774
|
+
}
|
|
775
|
+
function resolveArrayProperties({
|
|
776
|
+
propertyKey,
|
|
777
|
+
property,
|
|
778
|
+
ignoreMissingFields = false,
|
|
779
|
+
...props
|
|
780
|
+
}) {
|
|
781
|
+
const propertyValue = propertyKey ? getIn$1(props.values, propertyKey) : void 0;
|
|
782
|
+
if (property.of) {
|
|
783
|
+
if (Array.isArray(property.of)) {
|
|
784
|
+
return property.of.map((p, index) => {
|
|
785
|
+
return resolveProperty({
|
|
786
|
+
propertyKey: `${propertyKey}.${index}`,
|
|
787
|
+
property: p,
|
|
788
|
+
ignoreMissingFields,
|
|
789
|
+
...props,
|
|
790
|
+
index
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
} else {
|
|
794
|
+
const of = property.of;
|
|
795
|
+
const resolvedProperties = getArrayResolvedProperties({
|
|
796
|
+
propertyValue,
|
|
797
|
+
propertyKey,
|
|
798
|
+
property,
|
|
799
|
+
ignoreMissingFields,
|
|
800
|
+
...props
|
|
801
|
+
});
|
|
802
|
+
const {
|
|
803
|
+
values,
|
|
804
|
+
previousValues,
|
|
805
|
+
...rest
|
|
806
|
+
} = props;
|
|
807
|
+
const ofProperty = resolveProperty({
|
|
808
|
+
// we don't want to pass the values of the parent entity
|
|
809
|
+
property: of,
|
|
810
|
+
ignoreMissingFields,
|
|
811
|
+
...rest
|
|
812
|
+
});
|
|
813
|
+
if (!ofProperty && !ignoreMissingFields) throw Error("When using a property builder as the 'of' prop of an ArrayProperty, you must return a valid child property");
|
|
814
|
+
return resolvedProperties;
|
|
815
|
+
}
|
|
816
|
+
} else if (property.oneOf) {
|
|
817
|
+
const typeField = property.oneOf?.typeField ?? DEFAULT_ONE_OF_TYPE;
|
|
818
|
+
const resolvedProperties = Array.isArray(propertyValue) ? propertyValue.map((v, index) => {
|
|
819
|
+
const type = v && v[typeField];
|
|
820
|
+
const childProperty = property.oneOf?.properties[type];
|
|
821
|
+
if (!type || !childProperty) return null;
|
|
822
|
+
return resolveProperty({
|
|
823
|
+
propertyKey: `${propertyKey}.${index}`,
|
|
824
|
+
property: childProperty,
|
|
825
|
+
ignoreMissingFields,
|
|
826
|
+
...props
|
|
827
|
+
});
|
|
828
|
+
}).filter((e) => Boolean(e)) : [];
|
|
829
|
+
return resolvedProperties;
|
|
830
|
+
} else if (!property.Field) {
|
|
831
|
+
throw Error(`The array property (${propertyKey}) needs to declare an 'of' or a 'oneOf' property, or provide a custom \`Field\` component`);
|
|
832
|
+
} else {
|
|
833
|
+
return [];
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
function getArrayResolvedProperties({
|
|
837
|
+
propertyKey,
|
|
838
|
+
propertyValue,
|
|
839
|
+
property,
|
|
840
|
+
...props
|
|
841
|
+
}) {
|
|
842
|
+
const of = property.of;
|
|
843
|
+
if (!of) throw Error(`Trying to resolve an array property (${propertyKey}) without providing an 'of' property`);
|
|
844
|
+
return Array.isArray(propertyValue) ? propertyValue.map((v, index) => {
|
|
845
|
+
return resolveProperty({
|
|
846
|
+
propertyKey: `${propertyKey}.${index}`,
|
|
847
|
+
property: Array.isArray(of) ? of[index] : of,
|
|
848
|
+
...props,
|
|
849
|
+
index
|
|
850
|
+
});
|
|
851
|
+
}).filter((e) => Boolean(e)) : [];
|
|
852
|
+
}
|
|
853
|
+
function resolveEnumValues(input) {
|
|
854
|
+
if (typeof input === "object") {
|
|
855
|
+
return Object.entries(input).map(([id, value]) => typeof value === "string" ? {
|
|
856
|
+
id,
|
|
857
|
+
label: value
|
|
858
|
+
} : value);
|
|
859
|
+
} else if (Array.isArray(input)) {
|
|
860
|
+
return input;
|
|
861
|
+
} else {
|
|
862
|
+
return void 0;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function resolveEntityView$1(entityView, contextEntityViews) {
|
|
866
|
+
if (typeof entityView === "string") {
|
|
867
|
+
return contextEntityViews?.find((entry) => entry.key === entityView);
|
|
868
|
+
} else {
|
|
869
|
+
return entityView;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function resolveEntityAction(entityAction, contextEntityActions) {
|
|
873
|
+
if (typeof entityAction === "string") {
|
|
874
|
+
return contextEntityActions?.find((entry) => entry.key === entityAction);
|
|
875
|
+
} else {
|
|
876
|
+
return entityAction;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit) {
|
|
880
|
+
const resolvedEntityViews = customViews ? customViews.map((e) => resolveEntityView$1(e, customizationController.entityViews)).filter((e) => Boolean(e)) : [];
|
|
881
|
+
const selectedEntityView = resolvedEntityViews.find((e) => e.key === selectedTab);
|
|
882
|
+
const selectedSecondaryForm = customViews && resolvedEntityViews.filter((e) => e.includeActions).find((e) => e.key === selectedTab);
|
|
883
|
+
return {
|
|
884
|
+
resolvedEntityViews,
|
|
885
|
+
selectedEntityView,
|
|
886
|
+
selectedSecondaryForm
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function getSubcollections(collection) {
|
|
890
|
+
const subcollections = [];
|
|
891
|
+
subcollections.push(...collection.subcollections?.() ?? []);
|
|
892
|
+
subcollections.push(...(collection.relations ?? []).filter((rel) => rel.cardinality === "many").map((relation) => {
|
|
893
|
+
const targetCollection = relation.target();
|
|
894
|
+
const overrides = relation.overrides;
|
|
895
|
+
return overrides ? mergeDeep(targetCollection, overrides) : targetCollection;
|
|
896
|
+
}) ?? []);
|
|
897
|
+
return subcollections;
|
|
898
|
+
}
|
|
899
|
+
function removeInitialAndTrailingSlashes(s) {
|
|
900
|
+
return removeInitialSlash(removeTrailingSlash(s));
|
|
901
|
+
}
|
|
902
|
+
function removeInitialSlash(s) {
|
|
903
|
+
if (s.startsWith("/")) return s.slice(1);
|
|
904
|
+
else return s;
|
|
905
|
+
}
|
|
906
|
+
function removeTrailingSlash(s) {
|
|
907
|
+
if (s.endsWith("/")) return s.slice(0, -1);
|
|
908
|
+
else return s;
|
|
909
|
+
}
|
|
910
|
+
function addInitialSlash(s) {
|
|
911
|
+
if (s.startsWith("/")) return s;
|
|
912
|
+
else return `/${s}`;
|
|
913
|
+
}
|
|
914
|
+
function getLastSegment(path) {
|
|
915
|
+
const cleanPath = removeInitialAndTrailingSlashes(path);
|
|
916
|
+
if (cleanPath.includes("/")) {
|
|
917
|
+
const segments = cleanPath.split("/");
|
|
918
|
+
return segments[segments.length - 1];
|
|
919
|
+
}
|
|
920
|
+
return cleanPath;
|
|
921
|
+
}
|
|
922
|
+
function resolveCollectionPathIds(path, allCollections) {
|
|
923
|
+
let remainingPath = removeInitialAndTrailingSlashes(path);
|
|
924
|
+
if (!remainingPath) {
|
|
925
|
+
return "";
|
|
926
|
+
}
|
|
927
|
+
let currentCollections = allCollections;
|
|
928
|
+
const resolvedPathParts = [];
|
|
929
|
+
while (remainingPath.length > 0) {
|
|
930
|
+
if (!currentCollections || currentCollections.length === 0) {
|
|
931
|
+
console.warn(`resolveCollectionPathIds: Path structure implies subcollections, but none found before segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
|
|
932
|
+
resolvedPathParts.push(remainingPath);
|
|
933
|
+
remainingPath = "";
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
let foundMatch = false;
|
|
937
|
+
const potentialMatches = currentCollections.flatMap((col) => [{
|
|
938
|
+
col,
|
|
939
|
+
match: col.slug
|
|
940
|
+
}]).filter((p) => p.match && remainingPath.startsWith(p.match)).sort((a, b) => b.match.length - a.match.length);
|
|
941
|
+
if (potentialMatches.length > 0) {
|
|
942
|
+
const {
|
|
943
|
+
col: foundCollection,
|
|
944
|
+
match: matchString
|
|
945
|
+
} = potentialMatches[0];
|
|
946
|
+
resolvedPathParts.push(foundCollection.dbPath);
|
|
947
|
+
remainingPath = removeInitialSlash(remainingPath.substring(matchString.length));
|
|
948
|
+
if (remainingPath.length === 0) {
|
|
949
|
+
foundMatch = true;
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
const idSeparatorIndex = remainingPath.indexOf("/");
|
|
953
|
+
let entityId;
|
|
954
|
+
if (idSeparatorIndex > -1) {
|
|
955
|
+
entityId = remainingPath.substring(0, idSeparatorIndex);
|
|
956
|
+
remainingPath = remainingPath.substring(idSeparatorIndex + 1);
|
|
957
|
+
} else {
|
|
958
|
+
entityId = remainingPath;
|
|
959
|
+
remainingPath = "";
|
|
960
|
+
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.`);
|
|
961
|
+
}
|
|
962
|
+
resolvedPathParts.push(entityId);
|
|
963
|
+
currentCollections = getSubcollections(foundCollection);
|
|
964
|
+
foundMatch = true;
|
|
965
|
+
if (!currentCollections && remainingPath.length > 0) {
|
|
966
|
+
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.`);
|
|
967
|
+
resolvedPathParts.push(remainingPath);
|
|
968
|
+
remainingPath = "";
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (!foundMatch) {
|
|
973
|
+
console.warn(`resolveCollectionPathIds: Collection definition not found for segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
|
|
974
|
+
resolvedPathParts.push(remainingPath);
|
|
975
|
+
remainingPath = "";
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return resolvedPathParts.join("/");
|
|
980
|
+
}
|
|
981
|
+
function getCollectionBySlugWithin(slugOrPath, collections) {
|
|
982
|
+
const subpaths = removeInitialAndTrailingSlashes(slugOrPath).split("/");
|
|
983
|
+
if (subpaths.length % 2 === 0) {
|
|
984
|
+
throw Error(`getCollectionBySlug: Collection paths must have an odd number of segments: ${slugOrPath}`);
|
|
985
|
+
}
|
|
986
|
+
const subpathCombinations = getCollectionPathsCombinations(subpaths);
|
|
987
|
+
let result;
|
|
988
|
+
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
989
|
+
const subpathCombination = subpathCombinations[i];
|
|
990
|
+
const navigationEntry = collections && collections.sort((a, b) => (a.slug ?? "").localeCompare(b.slug ?? "")).find((entry) => entry.slug === subpathCombination);
|
|
991
|
+
if (navigationEntry) {
|
|
992
|
+
if (subpathCombination === slugOrPath) {
|
|
993
|
+
result = navigationEntry;
|
|
994
|
+
} else if (navigationEntry.subcollections) {
|
|
995
|
+
const newPath = slugOrPath.replace(subpathCombination, "").split("/").slice(2).join("/");
|
|
996
|
+
if (newPath.length > 0) result = getCollectionBySlugWithin(newPath, getSubcollections(navigationEntry));
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (result) break;
|
|
1000
|
+
}
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
1003
|
+
function getCollectionPathsCombinations(subpaths) {
|
|
1004
|
+
const entries = subpaths.length > 0 && subpaths.length % 2 === 0 ? subpaths.splice(0, subpaths.length - 1) : subpaths;
|
|
1005
|
+
const length = entries.length;
|
|
1006
|
+
const result = [];
|
|
1007
|
+
for (let i = length; i > 0; i = i - 2) {
|
|
1008
|
+
result.push(entries.slice(0, i).join("/"));
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
function navigateToEntity({
|
|
1013
|
+
openEntityMode,
|
|
1014
|
+
collection,
|
|
1015
|
+
entityId,
|
|
1016
|
+
copy,
|
|
1017
|
+
path,
|
|
1018
|
+
selectedTab,
|
|
1019
|
+
sideEntityController,
|
|
1020
|
+
onClose,
|
|
1021
|
+
navigation
|
|
1022
|
+
}) {
|
|
1023
|
+
if (openEntityMode === "side_panel") {
|
|
1024
|
+
sideEntityController.open({
|
|
1025
|
+
entityId,
|
|
1026
|
+
path,
|
|
1027
|
+
copy,
|
|
1028
|
+
selectedTab,
|
|
1029
|
+
collection,
|
|
1030
|
+
updateUrl: true,
|
|
1031
|
+
onClose
|
|
1032
|
+
});
|
|
1033
|
+
} else {
|
|
1034
|
+
let to = navigation.buildUrlCollectionPath(entityId ? `${path ?? path}/${entityId}` : path ?? path);
|
|
1035
|
+
if (entityId && selectedTab) {
|
|
1036
|
+
to += `/${selectedTab}`;
|
|
1037
|
+
}
|
|
1038
|
+
if (!entityId) {
|
|
1039
|
+
to += "#new";
|
|
1040
|
+
}
|
|
1041
|
+
if (copy) {
|
|
1042
|
+
to += "#copy";
|
|
1043
|
+
}
|
|
1044
|
+
navigation.navigate(to);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
const reservedKeys = ["edit", "copy", "delete"];
|
|
1048
|
+
function mergeEntityActions(currentActions, newActions) {
|
|
1049
|
+
const updatedActions = [];
|
|
1050
|
+
currentActions.forEach((action) => {
|
|
1051
|
+
const newAction = newActions.find((a) => a.key === action.key);
|
|
1052
|
+
if (newAction) {
|
|
1053
|
+
const mergedAction = {
|
|
1054
|
+
...action,
|
|
1055
|
+
...newAction
|
|
1056
|
+
};
|
|
1057
|
+
updatedActions.push(mergedAction);
|
|
1058
|
+
} else {
|
|
1059
|
+
updatedActions.push(action);
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
newActions.forEach((action) => {
|
|
1063
|
+
if (!currentActions.find((a) => a.key === action.key) && (!action.key || !reservedKeys.includes(action.key))) {
|
|
1064
|
+
updatedActions.push(action);
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
return updatedActions;
|
|
1068
|
+
}
|
|
1069
|
+
function evaluateAST(sqlString, auth, entity) {
|
|
1070
|
+
if (!entity) return true;
|
|
1071
|
+
let cleanedSQL = sqlString.trim();
|
|
1072
|
+
while (cleanedSQL.startsWith("(") && cleanedSQL.endsWith(")")) {
|
|
1073
|
+
let openCount = 0;
|
|
1074
|
+
let isEnclosing = true;
|
|
1075
|
+
for (let i = 0; i < cleanedSQL.length - 1; i++) {
|
|
1076
|
+
if (cleanedSQL[i] === "(") openCount++;
|
|
1077
|
+
else if (cleanedSQL[i] === ")") openCount--;
|
|
1078
|
+
if (openCount === 0) {
|
|
1079
|
+
isEnclosing = false;
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (isEnclosing) {
|
|
1084
|
+
cleanedSQL = cleanedSQL.substring(1, cleanedSQL.length - 1).trim();
|
|
1085
|
+
} else {
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
const splitByTopLevel = (str, delimiter) => {
|
|
1090
|
+
const parts = [];
|
|
1091
|
+
let current = "";
|
|
1092
|
+
let openCount = 0;
|
|
1093
|
+
let i = 0;
|
|
1094
|
+
while (i < str.length) {
|
|
1095
|
+
if (str[i] === "(") openCount++;
|
|
1096
|
+
else if (str[i] === ")") openCount--;
|
|
1097
|
+
if (openCount === 0 && str.substring(i).toUpperCase().startsWith(delimiter)) {
|
|
1098
|
+
parts.push(current);
|
|
1099
|
+
current = "";
|
|
1100
|
+
i += delimiter.length;
|
|
1101
|
+
} else {
|
|
1102
|
+
current += str[i];
|
|
1103
|
+
i++;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
parts.push(current);
|
|
1107
|
+
return parts;
|
|
1108
|
+
};
|
|
1109
|
+
const orParts = splitByTopLevel(cleanedSQL, " OR ");
|
|
1110
|
+
if (orParts.length > 1) {
|
|
1111
|
+
return orParts.some((part) => evaluateAST(part, auth, entity));
|
|
1112
|
+
}
|
|
1113
|
+
const andParts = splitByTopLevel(cleanedSQL, " AND ");
|
|
1114
|
+
if (andParts.length > 1) {
|
|
1115
|
+
return andParts.every((part) => evaluateAST(part, auth, entity));
|
|
1116
|
+
}
|
|
1117
|
+
const upperSQL = cleanedSQL.toUpperCase();
|
|
1118
|
+
if (upperSQL.includes(" IN ") || upperSQL.includes(" EXISTS ")) {
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
const roleIntersectMatch = cleanedSQL.match(/string_to_array\s*\(\s*auth\.roles\(\)\s*,\s*','\s*\)\s*&&\s*ARRAY\[(.*?)\]/i);
|
|
1122
|
+
if (roleIntersectMatch && roleIntersectMatch[1]) {
|
|
1123
|
+
const requiredRoles = roleIntersectMatch[1].split(",").map((r) => r.trim().replace(/'/g, ""));
|
|
1124
|
+
const userRoles = auth.user?.roles || [];
|
|
1125
|
+
return requiredRoles.some((r) => userRoles.includes(r));
|
|
1126
|
+
}
|
|
1127
|
+
const roleContainMatch = cleanedSQL.match(/string_to_array\s*\(\s*auth\.roles\(\)\s*,\s*','\s*\)\s*@>\s*ARRAY\[(.*?)\]/i);
|
|
1128
|
+
if (roleContainMatch && roleContainMatch[1]) {
|
|
1129
|
+
const requiredRoles = roleContainMatch[1].split(",").map((r) => r.trim().replace(/'/g, ""));
|
|
1130
|
+
const userRoles = auth.user?.roles || [];
|
|
1131
|
+
return requiredRoles.every((r) => userRoles.includes(r));
|
|
1132
|
+
}
|
|
1133
|
+
const pattern1 = new RegExp(`^\\{?([a-zA-Z0-9_]+)\\}?\\s*=\\s*(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))`);
|
|
1134
|
+
const pattern2 = new RegExp(`^(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))\\s*=\\s*\\{?([a-zA-Z0-9_]+)\\}?`);
|
|
1135
|
+
const match1 = cleanedSQL.match(pattern1);
|
|
1136
|
+
if (match1 && match1[1]) {
|
|
1137
|
+
return entity.values[match1[1]] === auth.user?.uid;
|
|
1138
|
+
}
|
|
1139
|
+
const match2 = cleanedSQL.match(pattern2);
|
|
1140
|
+
if (match2 && match2[1]) {
|
|
1141
|
+
return entity.values[match2[1]] === auth.user?.uid;
|
|
1142
|
+
}
|
|
1143
|
+
const simpleEqualityMatch = cleanedSQL.match(/^\{?([\w_]+)\}?\s*(=|!=)\s*'([^']+)'$/i);
|
|
1144
|
+
if (simpleEqualityMatch) {
|
|
1145
|
+
const field = simpleEqualityMatch[1];
|
|
1146
|
+
const operator = simpleEqualityMatch[2];
|
|
1147
|
+
const value = simpleEqualityMatch[3];
|
|
1148
|
+
const entityValue = entity.values[field];
|
|
1149
|
+
if (operator === "=") return entityValue === value;
|
|
1150
|
+
if (operator === "!=") return entityValue !== value;
|
|
1151
|
+
}
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
function evaluateRule(rule, auth, entity) {
|
|
1155
|
+
if (rule.access === "public") return true;
|
|
1156
|
+
if (rule.ownerField) {
|
|
1157
|
+
if (!entity) ;
|
|
1158
|
+
else {
|
|
1159
|
+
if (entity.values[rule.ownerField] !== auth.user?.uid) return false;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (rule.using && !evaluateAST(rule.using, auth, entity)) return false;
|
|
1163
|
+
if (rule.withCheck && !evaluateAST(rule.withCheck, auth, entity)) return false;
|
|
1164
|
+
return true;
|
|
1165
|
+
}
|
|
1166
|
+
function checkOperation(collection, authController, entity, targetOperation) {
|
|
1167
|
+
if (!collection.securityRules || collection.securityRules.length === 0) {
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
const applicableRules = collection.securityRules.filter((r) => r.operation === targetOperation || r.operation === "all" || r.operations?.includes(targetOperation) || r.operations?.includes("all"));
|
|
1171
|
+
if (applicableRules.length === 0) return false;
|
|
1172
|
+
const userRoleIds = authController.user?.roles ?? [];
|
|
1173
|
+
const userRoles = [...userRoleIds, "public"];
|
|
1174
|
+
const roleApplicableRules = applicableRules.filter((rule) => {
|
|
1175
|
+
if (!rule.roles || rule.roles.length === 0) return true;
|
|
1176
|
+
return rule.roles.some((r) => userRoles.includes(r));
|
|
1177
|
+
});
|
|
1178
|
+
if (roleApplicableRules.length === 0) return false;
|
|
1179
|
+
let grantedByPermissive = false;
|
|
1180
|
+
let deniedByRestrictive = false;
|
|
1181
|
+
for (const rule of roleApplicableRules) {
|
|
1182
|
+
const mode = rule.mode || "permissive";
|
|
1183
|
+
const passed = evaluateRule(rule, authController, entity);
|
|
1184
|
+
if (mode === "restrictive" && !passed) {
|
|
1185
|
+
deniedByRestrictive = true;
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
if (mode === "permissive" && passed) {
|
|
1189
|
+
grantedByPermissive = true;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (deniedByRestrictive) return false;
|
|
1193
|
+
const hasPermissive = roleApplicableRules.some((r) => (r.mode || "permissive") === "permissive");
|
|
1194
|
+
if (hasPermissive) {
|
|
1195
|
+
return grantedByPermissive;
|
|
1196
|
+
} else {
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function canReadCollection(collection, authController) {
|
|
1201
|
+
return checkOperation(collection, authController, null, "select");
|
|
1202
|
+
}
|
|
1203
|
+
function canEditEntity(collection, authController, path, entity) {
|
|
1204
|
+
return checkOperation(collection, authController, entity, "update");
|
|
1205
|
+
}
|
|
1206
|
+
function canCreateEntity(collection, authController, path, entity) {
|
|
1207
|
+
if (collection.collectionGroup) return false;
|
|
1208
|
+
return checkOperation(collection, authController, entity, "insert");
|
|
1209
|
+
}
|
|
1210
|
+
function canDeleteEntity(collection, authController, path, entity) {
|
|
1211
|
+
return checkOperation(collection, authController, entity, "delete");
|
|
1212
|
+
}
|
|
1213
|
+
function plural(word, amount) {
|
|
1214
|
+
if (amount !== void 0 && amount === 1) {
|
|
1215
|
+
return word;
|
|
1216
|
+
}
|
|
1217
|
+
const plurals = {
|
|
1218
|
+
"(quiz)$": "$1zes",
|
|
1219
|
+
"^(ox)$": "$1en",
|
|
1220
|
+
"([m|l])ouse$": "$1ice",
|
|
1221
|
+
"(matr|vert|ind)ix|ex$": "$1ices",
|
|
1222
|
+
"(x|ch|ss|sh)$": "$1es",
|
|
1223
|
+
"([^aeiouy]|qu)y$": "$1ies",
|
|
1224
|
+
"(hive)$": "$1s",
|
|
1225
|
+
"(?:([^f])fe|([lr])f)$": "$1$2ves",
|
|
1226
|
+
"(shea|lea|loa|thie)f$": "$1ves",
|
|
1227
|
+
sis$: "ses",
|
|
1228
|
+
"([ti])um$": "$1a",
|
|
1229
|
+
"(tomat|potat|ech|her|vet)o$": "$1oes",
|
|
1230
|
+
"(bu)s$": "$1ses",
|
|
1231
|
+
"(alias)$": "$1es",
|
|
1232
|
+
"(octop)us$": "$1i",
|
|
1233
|
+
"(ax|test)is$": "$1es",
|
|
1234
|
+
"(us)$": "$1es",
|
|
1235
|
+
"([^s]+)$": "$1s"
|
|
1236
|
+
};
|
|
1237
|
+
const irregular = {
|
|
1238
|
+
move: "moves",
|
|
1239
|
+
foot: "feet",
|
|
1240
|
+
goose: "geese",
|
|
1241
|
+
sex: "sexes",
|
|
1242
|
+
child: "children",
|
|
1243
|
+
man: "men",
|
|
1244
|
+
tooth: "teeth",
|
|
1245
|
+
person: "people"
|
|
1246
|
+
};
|
|
1247
|
+
const uncountable = ["sheep", "fish", "deer", "moose", "series", "species", "money", "rice", "information", "equipment", "bison", "cod", "offspring", "pike", "salmon", "shrimp", "swine", "trout", "aircraft", "hovercraft", "spacecraft", "sugar", "tuna", "you", "wood"];
|
|
1248
|
+
if (uncountable.indexOf(word.toLowerCase()) >= 0) {
|
|
1249
|
+
return word;
|
|
1250
|
+
}
|
|
1251
|
+
for (const w in irregular) {
|
|
1252
|
+
const pattern = new RegExp(`${w}$`, "i");
|
|
1253
|
+
const replace = irregular[w];
|
|
1254
|
+
if (pattern.test(word)) {
|
|
1255
|
+
return word.replace(pattern, replace);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
for (const reg in plurals) {
|
|
1259
|
+
const pattern = new RegExp(reg, "i");
|
|
1260
|
+
if (pattern.test(word)) {
|
|
1261
|
+
return word.replace(pattern, plurals[reg]);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return word;
|
|
1265
|
+
}
|
|
1266
|
+
function singular(word, amount) {
|
|
1267
|
+
if (amount !== void 0 && amount !== 1) {
|
|
1268
|
+
return word;
|
|
1269
|
+
}
|
|
1270
|
+
const singulars = {
|
|
1271
|
+
"(quiz)zes$": "$1",
|
|
1272
|
+
"(matr)ices$": "$1ix",
|
|
1273
|
+
"(vert|ind)ices$": "$1ex",
|
|
1274
|
+
"^(ox)en$": "$1",
|
|
1275
|
+
"(alias)es$": "$1",
|
|
1276
|
+
"(octop|vir)i$": "$1us",
|
|
1277
|
+
"(cris|ax|test)es$": "$1is",
|
|
1278
|
+
"(shoe)s$": "$1",
|
|
1279
|
+
"(o)es$": "$1",
|
|
1280
|
+
"(bus)es$": "$1",
|
|
1281
|
+
"([m|l])ice$": "$1ouse",
|
|
1282
|
+
"(x|ch|ss|sh)es$": "$1",
|
|
1283
|
+
"(m)ovies$": "$1ovie",
|
|
1284
|
+
"(s)eries$": "$1eries",
|
|
1285
|
+
"([^aeiouy]|qu)ies$": "$1y",
|
|
1286
|
+
"([lr])ves$": "$1f",
|
|
1287
|
+
"(tive)s$": "$1",
|
|
1288
|
+
"(hive)s$": "$1",
|
|
1289
|
+
"(li|wi|kni)ves$": "$1fe",
|
|
1290
|
+
"(shea|loa|lea|thie)ves$": "$1f",
|
|
1291
|
+
"(^analy)ses$": "$1sis",
|
|
1292
|
+
"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$": "$1$2sis",
|
|
1293
|
+
"([ti])a$": "$1um",
|
|
1294
|
+
"(n)ews$": "$1ews",
|
|
1295
|
+
"(h|bl)ouses$": "$1ouse",
|
|
1296
|
+
"(corpse)s$": "$1",
|
|
1297
|
+
"(us)es$": "$1",
|
|
1298
|
+
s$: ""
|
|
1299
|
+
};
|
|
1300
|
+
const irregular = {
|
|
1301
|
+
move: "moves",
|
|
1302
|
+
foot: "feet",
|
|
1303
|
+
goose: "geese",
|
|
1304
|
+
sex: "sexes",
|
|
1305
|
+
child: "children",
|
|
1306
|
+
man: "men",
|
|
1307
|
+
tooth: "teeth",
|
|
1308
|
+
person: "people"
|
|
1309
|
+
};
|
|
1310
|
+
const uncountable = ["sheep", "fish", "deer", "moose", "series", "species", "money", "rice", "information", "equipment", "bison", "cod", "offspring", "pike", "salmon", "shrimp", "swine", "trout", "aircraft", "hovercraft", "spacecraft", "sugar", "tuna", "you", "wood"];
|
|
1311
|
+
if (uncountable.indexOf(word.toLowerCase()) >= 0) {
|
|
1312
|
+
return word;
|
|
1313
|
+
}
|
|
1314
|
+
for (const w in irregular) {
|
|
1315
|
+
const pattern = new RegExp(`${irregular[w]}$`, "i");
|
|
1316
|
+
if (pattern.test(word)) {
|
|
1317
|
+
return word.replace(pattern, w);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
for (const reg in singulars) {
|
|
1321
|
+
const pattern = new RegExp(reg, "i");
|
|
1322
|
+
if (pattern.test(word)) {
|
|
1323
|
+
return word.replace(pattern, singulars[reg]);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return word;
|
|
1327
|
+
}
|
|
1328
|
+
function getEntityImagePreviewPropertyKey(collection) {
|
|
1329
|
+
for (const key in collection.properties) {
|
|
1330
|
+
const property = collection.properties[key];
|
|
1331
|
+
if (property.type === "string" && property.storage?.acceptedFiles?.includes("image/*")) {
|
|
1332
|
+
return key;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
for (const key in collection.properties) {
|
|
1336
|
+
const property = collection.properties[key];
|
|
1337
|
+
if (property.type === "array" && !Array.isArray(property.of) && property.of?.type === "string" && property.of.storage?.acceptedFiles?.includes("image/*")) {
|
|
1338
|
+
return key;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
for (const key in collection.properties) {
|
|
1342
|
+
const property = collection.properties[key];
|
|
1343
|
+
if (property.type === "string" && property.url === "image") {
|
|
1344
|
+
return key;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
for (const key in collection.properties) {
|
|
1348
|
+
const property = collection.properties[key];
|
|
1349
|
+
if (property.type === "array" && property.of?.type === "string" && property.of.url === "image") {
|
|
1350
|
+
return key;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return void 0;
|
|
1354
|
+
}
|
|
1355
|
+
function flattenObject(obj, parentKey = "") {
|
|
1356
|
+
if (!obj) return obj;
|
|
1357
|
+
return Object.keys(obj).reduce((flatObj, key) => {
|
|
1358
|
+
const newKey = parentKey ? `${parentKey}.${key}` : key;
|
|
1359
|
+
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
1360
|
+
if (Array.isArray(obj[key])) {
|
|
1361
|
+
obj[key].forEach((item, index) => {
|
|
1362
|
+
Object.assign(flatObj, flattenObject(item, `${newKey}[${index}]`));
|
|
1363
|
+
});
|
|
1364
|
+
} else {
|
|
1365
|
+
Object.assign(flatObj, flattenObject(obj[key], newKey));
|
|
1366
|
+
}
|
|
1367
|
+
} else {
|
|
1368
|
+
flatObj[newKey] = obj[key];
|
|
1369
|
+
}
|
|
1370
|
+
return flatObj;
|
|
1371
|
+
}, {});
|
|
1372
|
+
}
|
|
1373
|
+
function getArrayValuesCount(array) {
|
|
1374
|
+
return array.reduce((acc, obj) => {
|
|
1375
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
1376
|
+
if (Array.isArray(value)) {
|
|
1377
|
+
acc[key] = Math.max(acc[key] || 0, value.length);
|
|
1378
|
+
}
|
|
1379
|
+
if (typeof value === "object" && value !== null) {
|
|
1380
|
+
const nested = getArrayValuesCount([value]);
|
|
1381
|
+
Object.entries(nested).forEach(([nestedKey, nestedCount]) => {
|
|
1382
|
+
const compoundKey = `${key}.${nestedKey}`;
|
|
1383
|
+
acc[compoundKey] = Math.max(acc[compoundKey] || 0, nestedCount);
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
return acc;
|
|
1388
|
+
}, {});
|
|
1389
|
+
}
|
|
1390
|
+
function getNavigationEntriesFromPath(props) {
|
|
1391
|
+
const {
|
|
1392
|
+
path,
|
|
1393
|
+
collections = [],
|
|
1394
|
+
currentFullPath
|
|
1395
|
+
} = props;
|
|
1396
|
+
const subpaths = removeInitialAndTrailingSlashes(path).split("/");
|
|
1397
|
+
const subpathCombinations = getCollectionPathsCombinations(subpaths);
|
|
1398
|
+
const result = [];
|
|
1399
|
+
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
1400
|
+
const subpathCombination = subpathCombinations[i];
|
|
1401
|
+
const collection = collections && collections.find((entry) => entry.slug === subpathCombination);
|
|
1402
|
+
if (collection) {
|
|
1403
|
+
const collectionPath = currentFullPath && currentFullPath.length > 0 ? currentFullPath + "/" + collection.slug : collection.slug;
|
|
1404
|
+
result.push({
|
|
1405
|
+
type: "collection",
|
|
1406
|
+
id: collection.slug,
|
|
1407
|
+
slug: collectionPath,
|
|
1408
|
+
path: collectionPath,
|
|
1409
|
+
collection
|
|
1410
|
+
});
|
|
1411
|
+
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
1412
|
+
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
1413
|
+
if (nextSegments.length > 0) {
|
|
1414
|
+
const entityId = nextSegments[0];
|
|
1415
|
+
const path2 = collectionPath + "/" + entityId;
|
|
1416
|
+
result.push({
|
|
1417
|
+
type: "entity",
|
|
1418
|
+
entityId,
|
|
1419
|
+
slug: collectionPath,
|
|
1420
|
+
path: path2,
|
|
1421
|
+
parentCollection: collection
|
|
1422
|
+
});
|
|
1423
|
+
if (nextSegments.length > 1) {
|
|
1424
|
+
const newPath = nextSegments.slice(1).join("/");
|
|
1425
|
+
if (!collection) {
|
|
1426
|
+
throw Error("collection not found resolving path: " + collection);
|
|
1427
|
+
}
|
|
1428
|
+
const entityViews = collection.entityViews;
|
|
1429
|
+
const customView = entityViews && entityViews.map((entry) => resolveEntityView(entry, props.contextEntityViews)).filter(Boolean).find((entry) => entry.key === newPath);
|
|
1430
|
+
const subcollections = getSubcollections(collection);
|
|
1431
|
+
if (customView) {
|
|
1432
|
+
result.push({
|
|
1433
|
+
type: "custom_view",
|
|
1434
|
+
slug: collectionPath,
|
|
1435
|
+
entityId,
|
|
1436
|
+
path: path2 + "/" + customView.key,
|
|
1437
|
+
view: customView
|
|
1438
|
+
});
|
|
1439
|
+
} else if (subcollections) {
|
|
1440
|
+
result.push(...getNavigationEntriesFromPath({
|
|
1441
|
+
path: newPath,
|
|
1442
|
+
collections: subcollections,
|
|
1443
|
+
currentFullPath: path2,
|
|
1444
|
+
contextEntityViews: props.contextEntityViews
|
|
1445
|
+
}));
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
break;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return result;
|
|
1453
|
+
}
|
|
1454
|
+
function resolveEntityView(entityView, contextEntityViews) {
|
|
1455
|
+
if (typeof entityView === "string") {
|
|
1456
|
+
return contextEntityViews?.find((entry) => entry.key === entityView);
|
|
1457
|
+
} else {
|
|
1458
|
+
return entityView;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function getParentReferencesFromPath(props) {
|
|
1462
|
+
const {
|
|
1463
|
+
path,
|
|
1464
|
+
collections = [],
|
|
1465
|
+
currentFullPath
|
|
1466
|
+
} = props;
|
|
1467
|
+
const subpaths = removeInitialAndTrailingSlashes(path).split("/");
|
|
1468
|
+
const subpathCombinations = getCollectionPathsCombinations(subpaths);
|
|
1469
|
+
const result = [];
|
|
1470
|
+
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
1471
|
+
const subpathCombination = subpathCombinations[i];
|
|
1472
|
+
const collection = collections && collections.find((entry) => entry.slug === subpathCombination);
|
|
1473
|
+
if (collection) {
|
|
1474
|
+
const collectionPath = currentFullPath && currentFullPath.length > 0 ? currentFullPath + "/" + collection.slug : collection.slug;
|
|
1475
|
+
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
1476
|
+
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
1477
|
+
if (nextSegments.length > 0) {
|
|
1478
|
+
const entityId = nextSegments[0];
|
|
1479
|
+
const path2 = collectionPath + "/" + entityId;
|
|
1480
|
+
result.push(new EntityReference({
|
|
1481
|
+
id: entityId,
|
|
1482
|
+
path: collectionPath
|
|
1483
|
+
}));
|
|
1484
|
+
if (nextSegments.length > 1) {
|
|
1485
|
+
const newPath = nextSegments.slice(1).join("/");
|
|
1486
|
+
if (!collection) {
|
|
1487
|
+
throw Error("collection not found resolving path: " + collection);
|
|
1488
|
+
}
|
|
1489
|
+
if (collection.subcollections) {
|
|
1490
|
+
result.push(...getParentReferencesFromPath({
|
|
1491
|
+
path: newPath,
|
|
1492
|
+
collections: getSubcollections(collection),
|
|
1493
|
+
currentFullPath: path2
|
|
1494
|
+
}));
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return result;
|
|
1502
|
+
}
|
|
1503
|
+
function buildCollection(collection) {
|
|
1504
|
+
return collection;
|
|
1505
|
+
}
|
|
1506
|
+
function buildProperty(property) {
|
|
1507
|
+
return property;
|
|
1508
|
+
}
|
|
1509
|
+
function buildProperties(properties) {
|
|
1510
|
+
return properties;
|
|
1511
|
+
}
|
|
1512
|
+
function buildPropertiesOrBuilder(propertiesOrBuilder) {
|
|
1513
|
+
return propertiesOrBuilder;
|
|
1514
|
+
}
|
|
1515
|
+
function buildEnum(enumValues) {
|
|
1516
|
+
return enumValues;
|
|
1517
|
+
}
|
|
1518
|
+
function buildEnumValueConfig(enumValueConfig) {
|
|
1519
|
+
return enumValueConfig;
|
|
1520
|
+
}
|
|
1521
|
+
function buildEntityCallbacks(callbacks) {
|
|
1522
|
+
return callbacks;
|
|
1523
|
+
}
|
|
1524
|
+
function buildAdditionalFieldDelegate(additionalFieldDelegate) {
|
|
1525
|
+
return additionalFieldDelegate;
|
|
1526
|
+
}
|
|
1527
|
+
function buildFieldConfig(propertyConfig) {
|
|
1528
|
+
return propertyConfig;
|
|
1529
|
+
}
|
|
1530
|
+
async function resolveStorageFilenameString({
|
|
1531
|
+
input,
|
|
1532
|
+
storage,
|
|
1533
|
+
values,
|
|
1534
|
+
entityId,
|
|
1535
|
+
path,
|
|
1536
|
+
property,
|
|
1537
|
+
file,
|
|
1538
|
+
propertyKey
|
|
1539
|
+
}) {
|
|
1540
|
+
let result;
|
|
1541
|
+
if (typeof input === "function") {
|
|
1542
|
+
result = await input({
|
|
1543
|
+
path,
|
|
1544
|
+
entityId,
|
|
1545
|
+
values,
|
|
1546
|
+
property,
|
|
1547
|
+
file,
|
|
1548
|
+
storage,
|
|
1549
|
+
propertyKey
|
|
1550
|
+
});
|
|
1551
|
+
if (!result) console.warn("Storage callback returned empty result. Using default name value");
|
|
1552
|
+
} else {
|
|
1553
|
+
result = replacePlaceholders({
|
|
1554
|
+
file,
|
|
1555
|
+
input,
|
|
1556
|
+
entityId,
|
|
1557
|
+
propertyKey,
|
|
1558
|
+
path
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
if (!result) result = randomString() + "_" + file.name;
|
|
1562
|
+
return result;
|
|
1563
|
+
}
|
|
1564
|
+
function resolveStoragePathString({
|
|
1565
|
+
input,
|
|
1566
|
+
storage,
|
|
1567
|
+
values,
|
|
1568
|
+
entityId,
|
|
1569
|
+
path,
|
|
1570
|
+
property,
|
|
1571
|
+
file,
|
|
1572
|
+
propertyKey
|
|
1573
|
+
}) {
|
|
1574
|
+
let result;
|
|
1575
|
+
if (typeof input === "function") {
|
|
1576
|
+
result = input({
|
|
1577
|
+
path,
|
|
1578
|
+
entityId,
|
|
1579
|
+
values,
|
|
1580
|
+
property,
|
|
1581
|
+
file,
|
|
1582
|
+
storage,
|
|
1583
|
+
propertyKey
|
|
1584
|
+
});
|
|
1585
|
+
if (!result) console.warn("Storage callback returned empty result. Using default name value");
|
|
1586
|
+
} else {
|
|
1587
|
+
result = replacePlaceholders({
|
|
1588
|
+
file,
|
|
1589
|
+
input,
|
|
1590
|
+
entityId,
|
|
1591
|
+
propertyKey,
|
|
1592
|
+
path
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
if (!result) result = randomString() + "_" + file.name;
|
|
1596
|
+
return result;
|
|
1597
|
+
}
|
|
1598
|
+
function replacePlaceholders({
|
|
1599
|
+
file,
|
|
1600
|
+
input,
|
|
1601
|
+
entityId,
|
|
1602
|
+
propertyKey,
|
|
1603
|
+
path
|
|
1604
|
+
}) {
|
|
1605
|
+
const ext = file.name.split(".").pop();
|
|
1606
|
+
let result = input.replace("{propertyKey}", propertyKey).replace("{rand}", randomString()).replace("{file}", file.name).replace("{file.type}", file.type);
|
|
1607
|
+
if (entityId) {
|
|
1608
|
+
result = result.replace("{entityId}", String(entityId));
|
|
1609
|
+
}
|
|
1610
|
+
if (path) {
|
|
1611
|
+
result = result.replace("{path}", path);
|
|
1612
|
+
}
|
|
1613
|
+
if (ext) {
|
|
1614
|
+
result = result.replace("{file.ext}", ext);
|
|
1615
|
+
const name = file.name.replace(`.${ext}`, "");
|
|
1616
|
+
result = result.replace("{file.name}", name);
|
|
1617
|
+
}
|
|
1618
|
+
if (!result) result = randomString() + "_" + file.name;
|
|
1619
|
+
return result;
|
|
1620
|
+
}
|
|
1621
|
+
function toArray(input) {
|
|
1622
|
+
return Array.isArray(input) ? input : input ? [input] : [];
|
|
1623
|
+
}
|
|
1624
|
+
function hasPropertyCallbacks(properties, callbackName) {
|
|
1625
|
+
if (!properties) return false;
|
|
1626
|
+
for (const property of Object.values(properties)) {
|
|
1627
|
+
if (property.callbacks?.[callbackName]) return true;
|
|
1628
|
+
if (property.type === "map" && property.properties) {
|
|
1629
|
+
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
1630
|
+
} else if (property.type === "array" && property.of) {
|
|
1631
|
+
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
1632
|
+
for (const of of ofs) {
|
|
1633
|
+
if (of.callbacks?.[callbackName]) return true;
|
|
1634
|
+
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
async function processProperties(properties, values, previousValues, propsContext, callbackName) {
|
|
1641
|
+
if (!values || typeof values !== "object") return values;
|
|
1642
|
+
let result = {
|
|
1643
|
+
...values
|
|
1644
|
+
};
|
|
1645
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
1646
|
+
if (result[key] === void 0) continue;
|
|
1647
|
+
let currentValue = result[key];
|
|
1648
|
+
let previousValue = previousValues?.[key];
|
|
1649
|
+
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
1650
|
+
if (property.of && !Array.isArray(property.of)) {
|
|
1651
|
+
currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
1652
|
+
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
1653
|
+
const singlePropData = {
|
|
1654
|
+
"_tmp": property.of
|
|
1655
|
+
};
|
|
1656
|
+
const res = await processProperties(singlePropData, {
|
|
1657
|
+
"_tmp": item
|
|
1658
|
+
}, {
|
|
1659
|
+
"_tmp": prevItem
|
|
1660
|
+
}, propsContext, callbackName);
|
|
1661
|
+
return res["_tmp"];
|
|
1662
|
+
}));
|
|
1663
|
+
}
|
|
1664
|
+
} else if (property.type === "map" && property.properties && typeof currentValue === "object") {
|
|
1665
|
+
currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
|
|
1666
|
+
}
|
|
1667
|
+
if (property.callbacks?.[callbackName]) {
|
|
1668
|
+
const cbRes = await Promise.resolve(property.callbacks[callbackName]({
|
|
1669
|
+
...propsContext,
|
|
1670
|
+
value: currentValue,
|
|
1671
|
+
previousValue
|
|
1672
|
+
}));
|
|
1673
|
+
if (cbRes !== void 0) {
|
|
1674
|
+
currentValue = cbRes;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
result[key] = currentValue;
|
|
1678
|
+
}
|
|
1679
|
+
return result;
|
|
1680
|
+
}
|
|
1681
|
+
const buildPropertyCallbacks = (properties) => {
|
|
1682
|
+
if (!properties) return void 0;
|
|
1683
|
+
const propertyCallbacks = {};
|
|
1684
|
+
if (hasPropertyCallbacks(properties, "afterRead")) {
|
|
1685
|
+
propertyCallbacks.afterRead = async (props) => {
|
|
1686
|
+
const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
|
|
1687
|
+
return {
|
|
1688
|
+
...props.entity,
|
|
1689
|
+
values: processedValues
|
|
1690
|
+
};
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
if (hasPropertyCallbacks(properties, "beforeSave")) {
|
|
1694
|
+
propertyCallbacks.beforeSave = async (props) => {
|
|
1695
|
+
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1699
|
+
};
|
|
1700
|
+
function hashString(str) {
|
|
1701
|
+
let hash2 = 0;
|
|
1702
|
+
let i;
|
|
1703
|
+
let chr;
|
|
1704
|
+
for (i = 0; i < str.length; i++) {
|
|
1705
|
+
chr = str.charCodeAt(i);
|
|
1706
|
+
hash2 = (hash2 << 5) - hash2 + chr;
|
|
1707
|
+
hash2 |= 0;
|
|
1708
|
+
}
|
|
1709
|
+
return Math.abs(hash2);
|
|
1710
|
+
}
|
|
1711
|
+
function generateForeignKeyName(name) {
|
|
1712
|
+
const snakeCaseName = toSnakeCase(name);
|
|
1713
|
+
const singularName = snakeCaseName.endsWith("s") ? snakeCaseName.slice(0, -1) : snakeCaseName;
|
|
1714
|
+
return `${singularName}_id`;
|
|
1715
|
+
}
|
|
1716
|
+
function sanitizeRelation(relation, sourceCollection) {
|
|
1717
|
+
if (!relation.target) {
|
|
1718
|
+
throw new Error("Relation is missing a `target` collection.");
|
|
1719
|
+
}
|
|
1720
|
+
const targetCollection = relation.target();
|
|
1721
|
+
const newRelation = {
|
|
1722
|
+
...relation
|
|
1723
|
+
};
|
|
1724
|
+
if (!newRelation.relationName) {
|
|
1725
|
+
newRelation.relationName = toSnakeCase(targetCollection.slug ?? targetCollection.dbPath);
|
|
1726
|
+
}
|
|
1727
|
+
if (!newRelation.direction) {
|
|
1728
|
+
if (newRelation.foreignKeyOnTarget) newRelation.direction = "inverse";
|
|
1729
|
+
else if (newRelation.through) newRelation.direction = "owning";
|
|
1730
|
+
else if (newRelation.cardinality === "many") newRelation.direction = "inverse";
|
|
1731
|
+
else newRelation.direction = "owning";
|
|
1732
|
+
}
|
|
1733
|
+
if (!newRelation.joinPath) {
|
|
1734
|
+
const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
|
|
1735
|
+
if (newRelation.cardinality === "one" && newRelation.direction === "owning") {
|
|
1736
|
+
if (!newRelation.localKey) {
|
|
1737
|
+
newRelation.localKey = generateForeignKeyName(newRelation.relationName);
|
|
1738
|
+
}
|
|
1739
|
+
} else if (newRelation.cardinality === "one" && newRelation.direction === "inverse") {
|
|
1740
|
+
if (!newRelation.foreignKeyOnTarget) {
|
|
1741
|
+
let foundForeignKey = false;
|
|
1742
|
+
try {
|
|
1743
|
+
const targetRelations = targetCollection.relations || [];
|
|
1744
|
+
for (const targetRel of targetRelations) {
|
|
1745
|
+
if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) {
|
|
1746
|
+
try {
|
|
1747
|
+
const targetRelTarget = targetRel.target();
|
|
1748
|
+
if (targetRelTarget.slug === sourceCollection.slug || targetRelTarget.dbPath === sourceCollection.dbPath) {
|
|
1749
|
+
newRelation.foreignKeyOnTarget = targetRel.localKey;
|
|
1750
|
+
foundForeignKey = true;
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1753
|
+
} catch (e) {
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
} catch (e) {
|
|
1759
|
+
}
|
|
1760
|
+
if (!foundForeignKey) {
|
|
1761
|
+
const keyPrefix = newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName;
|
|
1762
|
+
newRelation.foreignKeyOnTarget = generateForeignKeyName(keyPrefix);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
} else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
|
|
1766
|
+
let isManyToManyInverse = false;
|
|
1767
|
+
if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) {
|
|
1768
|
+
try {
|
|
1769
|
+
const targetRelations = targetCollection.relations || [];
|
|
1770
|
+
for (const targetRel of targetRelations) {
|
|
1771
|
+
if (targetRel.cardinality === "many" && targetRel.direction === "owning" && targetRel.through && targetRel.relationName === newRelation.inverseRelationName) {
|
|
1772
|
+
isManyToManyInverse = true;
|
|
1773
|
+
break;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
} catch (e) {
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
if (!isManyToManyInverse && !newRelation.foreignKeyOnTarget) {
|
|
1780
|
+
newRelation.foreignKeyOnTarget = generateForeignKeyName(sourceName);
|
|
1781
|
+
}
|
|
1782
|
+
} else if (newRelation.cardinality === "many" && newRelation.direction === "owning") {
|
|
1783
|
+
const sourceTableName = getTableName(sourceCollection);
|
|
1784
|
+
const targetTableName = getTableName(targetCollection);
|
|
1785
|
+
newRelation.through = {
|
|
1786
|
+
table: newRelation.through?.table ?? [sourceTableName, targetTableName].sort().join("_"),
|
|
1787
|
+
sourceColumn: newRelation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
|
|
1788
|
+
targetColumn: newRelation.through?.targetColumn ?? generateForeignKeyName(newRelation.relationName)
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
if (newRelation.cardinality === "one" && newRelation.direction === "owning" && !newRelation.localKey && !newRelation.joinPath) {
|
|
1793
|
+
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}'`);
|
|
1794
|
+
}
|
|
1795
|
+
if (newRelation.cardinality === "one" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) {
|
|
1796
|
+
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}'`);
|
|
1797
|
+
}
|
|
1798
|
+
if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) {
|
|
1799
|
+
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}'`);
|
|
1800
|
+
}
|
|
1801
|
+
return newRelation;
|
|
1802
|
+
}
|
|
1803
|
+
function resolveCollectionRelations(collection) {
|
|
1804
|
+
const relations = {};
|
|
1805
|
+
if (collection.relations) {
|
|
1806
|
+
collection.relations.forEach((relation) => {
|
|
1807
|
+
const normalizedRelation = sanitizeRelation(relation, collection);
|
|
1808
|
+
const relationKey = normalizedRelation.relationName;
|
|
1809
|
+
if (relationKey) {
|
|
1810
|
+
relations[relationKey] = normalizedRelation;
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
if (collection.properties) {
|
|
1815
|
+
Object.entries(collection.properties).forEach(([propKey, prop]) => {
|
|
1816
|
+
const relation = resolvePropertyRelation({
|
|
1817
|
+
propertyKey: propKey,
|
|
1818
|
+
property: prop,
|
|
1819
|
+
sourceCollection: collection
|
|
1820
|
+
});
|
|
1821
|
+
if (relation) {
|
|
1822
|
+
if (!relations[propKey]) {
|
|
1823
|
+
if (!relation.relationName) {
|
|
1824
|
+
relation.relationName = propKey;
|
|
1825
|
+
}
|
|
1826
|
+
relations[propKey] = sanitizeRelation(relation, collection);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
return relations;
|
|
1832
|
+
}
|
|
1833
|
+
function resolvePropertyRelation({
|
|
1834
|
+
propertyKey,
|
|
1835
|
+
property,
|
|
1836
|
+
sourceCollection
|
|
1837
|
+
}) {
|
|
1838
|
+
if (property.type !== "relation") return void 0;
|
|
1839
|
+
const relation = sourceCollection.relations?.find((rel) => rel.relationName === property.relationName);
|
|
1840
|
+
if (!relation) {
|
|
1841
|
+
console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug || sourceCollection.dbPath}'`);
|
|
1842
|
+
return void 0;
|
|
1843
|
+
}
|
|
1844
|
+
return relation;
|
|
1845
|
+
}
|
|
1846
|
+
function getTableName(collection) {
|
|
1847
|
+
return collection.dbPath ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
|
|
1848
|
+
}
|
|
1849
|
+
function getTableVarName(tableName) {
|
|
1850
|
+
return tableName.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
|
|
1851
|
+
}
|
|
1852
|
+
function getEnumVarName(tableName, propName) {
|
|
1853
|
+
const tableVar = getTableVarName(tableName);
|
|
1854
|
+
const propVar = propName.charAt(0).toUpperCase() + propName.slice(1);
|
|
1855
|
+
return `${tableVar}${propVar}`;
|
|
1856
|
+
}
|
|
1857
|
+
function getColumnName(fullColumn) {
|
|
1858
|
+
return fullColumn.includes(".") ? fullColumn.split(".").pop() : fullColumn;
|
|
1859
|
+
}
|
|
1860
|
+
function getIn(obj, path) {
|
|
1861
|
+
if (!obj || !path) return void 0;
|
|
1862
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
1863
|
+
}
|
|
1864
|
+
let operationsRegistered = false;
|
|
1865
|
+
function registerConditionOperations() {
|
|
1866
|
+
if (operationsRegistered) return;
|
|
1867
|
+
jsonLogic.add_operation("hasRole", function(roleId) {
|
|
1868
|
+
return this?.user?.roles?.includes(roleId) ?? false;
|
|
1869
|
+
});
|
|
1870
|
+
jsonLogic.add_operation("hasAnyRole", function(roleIds) {
|
|
1871
|
+
if (!this?.user?.roles || !Array.isArray(roleIds)) return false;
|
|
1872
|
+
return roleIds.some((role) => this.user.roles.includes(role));
|
|
1873
|
+
});
|
|
1874
|
+
jsonLogic.add_operation("isToday", (timestamp) => {
|
|
1875
|
+
if (!timestamp) return false;
|
|
1876
|
+
const date = new Date(timestamp);
|
|
1877
|
+
const today = /* @__PURE__ */ new Date();
|
|
1878
|
+
return date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
|
1879
|
+
});
|
|
1880
|
+
jsonLogic.add_operation("isPast", (timestamp) => {
|
|
1881
|
+
if (!timestamp) return false;
|
|
1882
|
+
return timestamp < Date.now();
|
|
1883
|
+
});
|
|
1884
|
+
jsonLogic.add_operation("isFuture", (timestamp) => {
|
|
1885
|
+
if (!timestamp) return false;
|
|
1886
|
+
return timestamp > Date.now();
|
|
1887
|
+
});
|
|
1888
|
+
operationsRegistered = true;
|
|
1889
|
+
}
|
|
1890
|
+
function evaluateCondition(rule, context) {
|
|
1891
|
+
registerConditionOperations();
|
|
1892
|
+
return jsonLogic.apply(rule, context);
|
|
1893
|
+
}
|
|
1894
|
+
function serializeValueForConditions(value) {
|
|
1895
|
+
if (value === null || value === void 0) {
|
|
1896
|
+
return value;
|
|
1897
|
+
}
|
|
1898
|
+
if (value instanceof Date) {
|
|
1899
|
+
return value.getTime();
|
|
1900
|
+
}
|
|
1901
|
+
if (typeof value?.toMillis === "function") {
|
|
1902
|
+
return value.toMillis();
|
|
1903
|
+
}
|
|
1904
|
+
if (typeof value?.toDate === "function") {
|
|
1905
|
+
return value.toDate().getTime();
|
|
1906
|
+
}
|
|
1907
|
+
if (Array.isArray(value)) {
|
|
1908
|
+
return value.map(serializeValueForConditions);
|
|
1909
|
+
}
|
|
1910
|
+
if (typeof value === "object") {
|
|
1911
|
+
const result = {};
|
|
1912
|
+
for (const key of Object.keys(value)) {
|
|
1913
|
+
result[key] = serializeValueForConditions(value[key]);
|
|
1914
|
+
}
|
|
1915
|
+
return result;
|
|
1916
|
+
}
|
|
1917
|
+
return value;
|
|
1918
|
+
}
|
|
1919
|
+
function buildConditionContext(params) {
|
|
1920
|
+
const {
|
|
1921
|
+
propertyKey,
|
|
1922
|
+
values,
|
|
1923
|
+
previousValues,
|
|
1924
|
+
path,
|
|
1925
|
+
entityId,
|
|
1926
|
+
index,
|
|
1927
|
+
authController
|
|
1928
|
+
} = params;
|
|
1929
|
+
const user = authController.user;
|
|
1930
|
+
const serializedValues = serializeValueForConditions(values ?? {});
|
|
1931
|
+
const serializedPreviousValues = serializeValueForConditions(previousValues ?? values ?? {});
|
|
1932
|
+
return {
|
|
1933
|
+
values: serializedValues,
|
|
1934
|
+
previousValues: serializedPreviousValues,
|
|
1935
|
+
propertyValue: propertyKey ? getIn(serializedValues, propertyKey) : void 0,
|
|
1936
|
+
path,
|
|
1937
|
+
entityId,
|
|
1938
|
+
isNew: !entityId,
|
|
1939
|
+
index,
|
|
1940
|
+
user: {
|
|
1941
|
+
uid: user?.uid ?? "",
|
|
1942
|
+
email: user?.email ?? null,
|
|
1943
|
+
displayName: user?.displayName ?? null,
|
|
1944
|
+
photoURL: user?.photoURL ?? null,
|
|
1945
|
+
roles: user?.roles ?? []
|
|
1946
|
+
},
|
|
1947
|
+
now: Date.now()
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
function applyPropertyConditions(property, context) {
|
|
1951
|
+
const {
|
|
1952
|
+
conditions
|
|
1953
|
+
} = property;
|
|
1954
|
+
if (!conditions) return property;
|
|
1955
|
+
let result = {
|
|
1956
|
+
...property
|
|
1957
|
+
};
|
|
1958
|
+
if (conditions.disabled) {
|
|
1959
|
+
const isDisabled = evaluateCondition(conditions.disabled, context);
|
|
1960
|
+
if (isDisabled) {
|
|
1961
|
+
result.disabled = {
|
|
1962
|
+
clearOnDisabled: conditions.clearOnDisabled ?? false,
|
|
1963
|
+
disabledMessage: conditions.disabledMessage,
|
|
1964
|
+
hidden: false
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
if (conditions.hidden) {
|
|
1969
|
+
const isHidden2 = evaluateCondition(conditions.hidden, context);
|
|
1970
|
+
if (isHidden2) {
|
|
1971
|
+
result.disabled = {
|
|
1972
|
+
...typeof result.disabled === "object" ? result.disabled : {},
|
|
1973
|
+
hidden: true,
|
|
1974
|
+
clearOnDisabled: conditions.clearOnDisabled ?? false
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (conditions.readOnly) {
|
|
1979
|
+
const isReadOnly2 = evaluateCondition(conditions.readOnly, context);
|
|
1980
|
+
if (isReadOnly2) {
|
|
1981
|
+
result.readOnly = true;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
if (conditions.required !== void 0) {
|
|
1985
|
+
const isRequired = evaluateCondition(conditions.required, context);
|
|
1986
|
+
result.validation = {
|
|
1987
|
+
...result.validation,
|
|
1988
|
+
required: isRequired,
|
|
1989
|
+
requiredMessage: conditions.requiredMessage
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
if (context.isNew && conditions.defaultValue !== void 0) {
|
|
1993
|
+
result.defaultValue = evaluateCondition(conditions.defaultValue, context);
|
|
1994
|
+
}
|
|
1995
|
+
if ("enumValues" in result && result.enumValues && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) {
|
|
1996
|
+
result.enumValues = applyEnumConditions(result.enumValues, conditions, context);
|
|
1997
|
+
}
|
|
1998
|
+
if (result.type === "reference") {
|
|
1999
|
+
if (conditions.referencePath) {
|
|
2000
|
+
result.path = evaluateCondition(conditions.referencePath, context);
|
|
2001
|
+
}
|
|
2002
|
+
if (conditions.referenceFilter) {
|
|
2003
|
+
result.forceFilter = evaluateCondition(conditions.referenceFilter, context);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
if (result.type === "array") {
|
|
2007
|
+
if (conditions.canAddElements !== void 0) {
|
|
2008
|
+
result.canAddElements = evaluateCondition(conditions.canAddElements, context);
|
|
2009
|
+
}
|
|
2010
|
+
if (conditions.sortable !== void 0) {
|
|
2011
|
+
result.sortable = evaluateCondition(conditions.sortable, context);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
return result;
|
|
2015
|
+
}
|
|
2016
|
+
function objectToArray(obj) {
|
|
2017
|
+
if (Array.isArray(obj)) return obj.map(String);
|
|
2018
|
+
if (obj && typeof obj === "object") {
|
|
2019
|
+
const keys = Object.keys(obj);
|
|
2020
|
+
if (keys.length > 0 && keys.every((k) => !isNaN(Number(k)))) {
|
|
2021
|
+
return keys.sort((a, b) => Number(a) - Number(b)).map((k) => obj[k]).filter((v) => typeof v === "string" || typeof v === "number").map(String);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return [];
|
|
2025
|
+
}
|
|
2026
|
+
function applyEnumConditions(enumValues, conditions, context) {
|
|
2027
|
+
let result = [...enumValues];
|
|
2028
|
+
if (conditions.allowedEnumValues) {
|
|
2029
|
+
const allowed = evaluateCondition(conditions.allowedEnumValues, context);
|
|
2030
|
+
const allowedArray = objectToArray(allowed);
|
|
2031
|
+
if (allowedArray.length > 0) {
|
|
2032
|
+
result = result.filter((ev) => allowedArray.includes(String(ev.id)));
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
if (conditions.excludedEnumValues) {
|
|
2036
|
+
const excluded = evaluateCondition(conditions.excludedEnumValues, context);
|
|
2037
|
+
const excludedArray = objectToArray(excluded);
|
|
2038
|
+
if (excludedArray.length > 0) {
|
|
2039
|
+
result = result.filter((ev) => !excludedArray.includes(String(ev.id)));
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
if (conditions.enumConditions) {
|
|
2043
|
+
result = result.map((ev) => {
|
|
2044
|
+
const evConditions = conditions.enumConditions?.[ev.id];
|
|
2045
|
+
if (!evConditions) return ev;
|
|
2046
|
+
if (evConditions.hidden && evaluateCondition(evConditions.hidden, context)) {
|
|
2047
|
+
return null;
|
|
2048
|
+
}
|
|
2049
|
+
if (evConditions.disabled && evaluateCondition(evConditions.disabled, context)) {
|
|
2050
|
+
return {
|
|
2051
|
+
...ev,
|
|
2052
|
+
disabled: true
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
return ev;
|
|
2056
|
+
}).filter((ev) => ev !== null);
|
|
2057
|
+
}
|
|
2058
|
+
return result;
|
|
2059
|
+
}
|
|
2060
|
+
class CollectionRegistry {
|
|
2061
|
+
// Normalized runtime layer (used by Data Grid / UI)
|
|
2062
|
+
collectionsByDbPath = /* @__PURE__ */ new Map();
|
|
2063
|
+
collectionsBySlug = /* @__PURE__ */ new Map();
|
|
2064
|
+
rootCollections = [];
|
|
2065
|
+
// Raw configuration layer (used by Collection Editor AST generator)
|
|
2066
|
+
rawCollectionsByDbPath = /* @__PURE__ */ new Map();
|
|
2067
|
+
rawCollectionsBySlug = /* @__PURE__ */ new Map();
|
|
2068
|
+
rawRootCollections = [];
|
|
2069
|
+
// Snapshot of raw input for idempotency check — compared BEFORE normalization
|
|
2070
|
+
// to avoid the issue where normalization creates new objects that always fail equality.
|
|
2071
|
+
lastRawInputSnapshot = null;
|
|
2072
|
+
constructor(collections) {
|
|
2073
|
+
if (collections) {
|
|
2074
|
+
this.registerMultiple(collections);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
reset() {
|
|
2078
|
+
this.collectionsByDbPath.clear();
|
|
2079
|
+
this.collectionsBySlug.clear();
|
|
2080
|
+
this.rootCollections = [];
|
|
2081
|
+
this.rawCollectionsByDbPath.clear();
|
|
2082
|
+
this.rawCollectionsBySlug.clear();
|
|
2083
|
+
this.rawRootCollections = [];
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Registers a collection and its subcollections recursively.
|
|
2087
|
+
* Returns true if the collections have changed, false otherwise.
|
|
2088
|
+
*
|
|
2089
|
+
* Idempotent: compares the raw input (before normalization) against a stored
|
|
2090
|
+
* snapshot. Only re-normalizes and re-registers when the raw input actually changed.
|
|
2091
|
+
* @param collections
|
|
2092
|
+
*/
|
|
2093
|
+
registerMultiple(collections) {
|
|
2094
|
+
const rawSnapshot = collections.map((c) => removeFunctions(c));
|
|
2095
|
+
if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
this.reset();
|
|
2099
|
+
const normalizedCollections = collections.map((c) => this.normalizeCollection({
|
|
2100
|
+
...c
|
|
2101
|
+
}));
|
|
2102
|
+
normalizedCollections.forEach((c, index) => this.register(c, collections[index]));
|
|
2103
|
+
this.lastRawInputSnapshot = rawSnapshot;
|
|
2104
|
+
return true;
|
|
2105
|
+
}
|
|
2106
|
+
register(collection, rawCollection) {
|
|
2107
|
+
const raw = rawCollection ? cloneDeep(rawCollection) : cloneDeep(collection);
|
|
2108
|
+
this.rootCollections.push(collection);
|
|
2109
|
+
this.rawRootCollections.push(raw);
|
|
2110
|
+
this._registerRecursively(collection, raw);
|
|
2111
|
+
}
|
|
2112
|
+
_registerRecursively(collection, rawCollection) {
|
|
2113
|
+
if (this.collectionsByDbPath.has(collection.dbPath)) {
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const normalizedCollection = this.normalizeCollection(collection);
|
|
2117
|
+
this.collectionsByDbPath.set(normalizedCollection.dbPath, normalizedCollection);
|
|
2118
|
+
this.rawCollectionsByDbPath.set(rawCollection.dbPath, rawCollection);
|
|
2119
|
+
if (normalizedCollection.slug) {
|
|
2120
|
+
this.collectionsBySlug.set(normalizedCollection.slug, normalizedCollection);
|
|
2121
|
+
}
|
|
2122
|
+
if (rawCollection.slug) {
|
|
2123
|
+
this.rawCollectionsBySlug.set(rawCollection.slug, rawCollection);
|
|
2124
|
+
}
|
|
2125
|
+
const subcollections = getSubcollections(collection);
|
|
2126
|
+
const rawSubcollections = getSubcollections(rawCollection);
|
|
2127
|
+
if (subcollections && rawSubcollections) {
|
|
2128
|
+
subcollections.forEach((subCollection, index) => {
|
|
2129
|
+
this._registerRecursively(this.normalizeCollection(subCollection), cloneDeep(rawSubcollections[index]));
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
normalizeCollection(collection) {
|
|
2134
|
+
const properties = this.normalizeProperties(collection.properties, collection.relations ?? []);
|
|
2135
|
+
collection.properties = properties;
|
|
2136
|
+
return collection;
|
|
2137
|
+
}
|
|
2138
|
+
normalizeProperties(properties, relations) {
|
|
2139
|
+
const newProperties = {};
|
|
2140
|
+
for (const key in properties) {
|
|
2141
|
+
newProperties[key] = this.normalizeProperty(properties[key], relations);
|
|
2142
|
+
}
|
|
2143
|
+
return newProperties;
|
|
2144
|
+
}
|
|
2145
|
+
normalizeProperty(property, relations) {
|
|
2146
|
+
const newProperty = {
|
|
2147
|
+
...property
|
|
2148
|
+
};
|
|
2149
|
+
if (newProperty.type === "map" && newProperty.properties) {
|
|
2150
|
+
newProperty.properties = this.normalizeProperties(newProperty.properties, relations);
|
|
2151
|
+
} else if (newProperty.type === "array") {
|
|
2152
|
+
if (newProperty.of) {
|
|
2153
|
+
newProperty.of = this.normalizeProperty(newProperty.of, relations);
|
|
2154
|
+
} else if (newProperty.oneOf && newProperty.oneOf.properties) {
|
|
2155
|
+
newProperty.oneOf.properties = this.normalizeProperties(newProperty.oneOf.properties, relations);
|
|
2156
|
+
}
|
|
2157
|
+
} else if ((newProperty.type === "string" || newProperty.type === "number") && newProperty.enum) {
|
|
2158
|
+
const stringOrNumberProperty = newProperty;
|
|
2159
|
+
if (typeof stringOrNumberProperty.enum === "object" && !Array.isArray(stringOrNumberProperty.enum)) {
|
|
2160
|
+
stringOrNumberProperty.enum = enumToObjectEntries(stringOrNumberProperty.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? [];
|
|
2161
|
+
}
|
|
2162
|
+
} else if (newProperty.type === "relation") {
|
|
2163
|
+
const relationProperty = newProperty;
|
|
2164
|
+
const relation = relations.find((r) => r.relationName === relationProperty.relationName);
|
|
2165
|
+
if (relation) {
|
|
2166
|
+
relationProperty.relation = relation;
|
|
2167
|
+
} else {
|
|
2168
|
+
console.warn(`Could not find relation for property with relationName: ${relationProperty.relationName}`);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return newProperty;
|
|
2172
|
+
}
|
|
2173
|
+
get(path) {
|
|
2174
|
+
const bySlug = this.collectionsBySlug.get(path);
|
|
2175
|
+
if (bySlug) return bySlug;
|
|
2176
|
+
return this.collectionsByDbPath.get(path);
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Gets the pristine, un-normalized collection exactly as it was provided.
|
|
2180
|
+
* Useful for the AST editor so it doesn't accidentally serialize injected metadata back to disk.
|
|
2181
|
+
*/
|
|
2182
|
+
getRaw(path) {
|
|
2183
|
+
const bySlug = this.rawCollectionsBySlug.get(path);
|
|
2184
|
+
if (bySlug) return bySlug;
|
|
2185
|
+
return this.rawCollectionsByDbPath.get(path);
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Get collection by resolving multi-segment paths through relations
|
|
2189
|
+
* e.g., "authors/70/posts" resolves to the posts collection
|
|
2190
|
+
*/
|
|
2191
|
+
getCollectionByPath(collectionPath) {
|
|
2192
|
+
if (!collectionPath.includes("/")) {
|
|
2193
|
+
return this.get(collectionPath);
|
|
2194
|
+
}
|
|
2195
|
+
const pathSegments = collectionPath.split("/").filter((p) => p);
|
|
2196
|
+
if (pathSegments.length < 3 || pathSegments.length % 2 === 0) {
|
|
2197
|
+
throw new Error(`Invalid relation path: ${collectionPath}. Expected format: collection/id/relation or collection/id/relation/id/relation`);
|
|
2198
|
+
}
|
|
2199
|
+
const rootCollectionPath = pathSegments[0];
|
|
2200
|
+
let currentCollection = this.get(rootCollectionPath);
|
|
2201
|
+
if (!currentCollection) {
|
|
2202
|
+
throw new Error(`Root collection not found: ${rootCollectionPath}`);
|
|
2203
|
+
}
|
|
2204
|
+
for (let i = 2; i < pathSegments.length; i += 2) {
|
|
2205
|
+
const relationKey = pathSegments[i];
|
|
2206
|
+
const resolvedRelations = resolveCollectionRelations(currentCollection);
|
|
2207
|
+
const relation = resolvedRelations[relationKey];
|
|
2208
|
+
if (!relation) {
|
|
2209
|
+
throw new Error(`Relation '${relationKey}' not found in collection '${currentCollection.slug || currentCollection.dbPath}'`);
|
|
2210
|
+
}
|
|
2211
|
+
currentCollection = relation.target();
|
|
2212
|
+
if (i + 1 < pathSegments.length) ;
|
|
2213
|
+
}
|
|
2214
|
+
return currentCollection;
|
|
2215
|
+
}
|
|
2216
|
+
getCollections() {
|
|
2217
|
+
return Array.from(this.collectionsByDbPath.values());
|
|
2218
|
+
}
|
|
2219
|
+
getRawCollections() {
|
|
2220
|
+
return Array.from(this.rawCollectionsByDbPath.values());
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Resolves a multi-segment path like "products/123/locales" and returns
|
|
2224
|
+
* information about the collections and entity IDs along the path
|
|
2225
|
+
*/
|
|
2226
|
+
resolvePathToCollections(path) {
|
|
2227
|
+
const pathSegments = path.split("/").filter((p) => p);
|
|
2228
|
+
if (pathSegments.length === 0) {
|
|
2229
|
+
throw new Error(`Invalid path: ${path}`);
|
|
2230
|
+
}
|
|
2231
|
+
if (pathSegments.length % 2 !== 1) {
|
|
2232
|
+
throw new Error(`Invalid collection path: ${path}. It must have an odd number of segments.`);
|
|
2233
|
+
}
|
|
2234
|
+
const collections = [];
|
|
2235
|
+
const entityIds = [];
|
|
2236
|
+
let currentCollection = this.get(pathSegments[0]);
|
|
2237
|
+
if (!currentCollection) {
|
|
2238
|
+
throw new Error(`Unknown collection path or slug: ${pathSegments[0]}`);
|
|
2239
|
+
}
|
|
2240
|
+
collections.push(currentCollection);
|
|
2241
|
+
for (let i = 1; i < pathSegments.length; i += 2) {
|
|
2242
|
+
const entityId = pathSegments[i];
|
|
2243
|
+
entityIds.push(entityId);
|
|
2244
|
+
if (i + 1 < pathSegments.length) {
|
|
2245
|
+
const subcollectionSlug = pathSegments[i + 1];
|
|
2246
|
+
const subcollections = currentCollection.subcollections?.();
|
|
2247
|
+
if (!subcollections) {
|
|
2248
|
+
throw new Error(`No subcollections found for ${currentCollection.slug || currentCollection.dbPath} in path: ${path}`);
|
|
2249
|
+
}
|
|
2250
|
+
const subcollection = subcollections.find((c) => c.slug === subcollectionSlug);
|
|
2251
|
+
if (!subcollection) {
|
|
2252
|
+
throw new Error(`Subcollection '${subcollectionSlug}' not found in ${currentCollection.slug || currentCollection.dbPath}`);
|
|
2253
|
+
}
|
|
2254
|
+
currentCollection = subcollection;
|
|
2255
|
+
collections.push(currentCollection);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return {
|
|
2259
|
+
collections,
|
|
2260
|
+
entityIds,
|
|
2261
|
+
finalCollection: currentCollection
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
export {
|
|
2266
|
+
COLLECTION_PATH_SEPARATOR,
|
|
2267
|
+
CollectionRegistry,
|
|
2268
|
+
DEFAULT_ONE_OF_TYPE,
|
|
2269
|
+
DEFAULT_ONE_OF_VALUE,
|
|
2270
|
+
addInitialSlash,
|
|
2271
|
+
applyPropertyConditions,
|
|
2272
|
+
buildAdditionalFieldDelegate,
|
|
2273
|
+
buildCollection,
|
|
2274
|
+
buildConditionContext,
|
|
2275
|
+
buildEntityCallbacks,
|
|
2276
|
+
buildEnum,
|
|
2277
|
+
buildEnumValueConfig,
|
|
2278
|
+
buildFieldConfig,
|
|
2279
|
+
buildProperties,
|
|
2280
|
+
buildPropertiesOrBuilder,
|
|
2281
|
+
buildProperty,
|
|
2282
|
+
buildPropertyCallbacks,
|
|
2283
|
+
canCreateEntity,
|
|
2284
|
+
canDeleteEntity,
|
|
2285
|
+
canEditEntity,
|
|
2286
|
+
canReadCollection,
|
|
2287
|
+
clone,
|
|
2288
|
+
defaultDateFormat,
|
|
2289
|
+
enumToObjectEntries,
|
|
2290
|
+
evaluateCondition,
|
|
2291
|
+
flattenObject,
|
|
2292
|
+
fullPathToCollectionSegments,
|
|
2293
|
+
getArrayResolvedProperties,
|
|
2294
|
+
getArrayValuesCount,
|
|
2295
|
+
getCollectionBySlugWithin,
|
|
2296
|
+
getCollectionPathsCombinations,
|
|
2297
|
+
getColumnName,
|
|
2298
|
+
getDefaultValueFor,
|
|
2299
|
+
getDefaultValueFortype,
|
|
2300
|
+
getDefaultValuesFor,
|
|
2301
|
+
getEntityImagePreviewPropertyKey,
|
|
2302
|
+
getEnumVarName,
|
|
2303
|
+
getHashValue,
|
|
2304
|
+
getIn$1 as getIn,
|
|
2305
|
+
getLabelOrConfigFrom,
|
|
2306
|
+
getLastSegment,
|
|
2307
|
+
getLocalChangesBackup,
|
|
2308
|
+
getNavigationEntriesFromPath,
|
|
2309
|
+
getParentReferencesFromPath,
|
|
2310
|
+
getPrimaryKeys,
|
|
2311
|
+
getReferenceFrom,
|
|
2312
|
+
getRelationFrom,
|
|
2313
|
+
getSubcollections,
|
|
2314
|
+
getTableName,
|
|
2315
|
+
getTableVarName,
|
|
2316
|
+
getValueInPath,
|
|
2317
|
+
hashString,
|
|
2318
|
+
hydrateRegExp,
|
|
2319
|
+
isDefaultFieldConfigId,
|
|
2320
|
+
isEmptyArray,
|
|
2321
|
+
isEmptyObject,
|
|
2322
|
+
isFunction,
|
|
2323
|
+
isHidden,
|
|
2324
|
+
isInteger,
|
|
2325
|
+
isNaN$1 as isNaN,
|
|
2326
|
+
isObject,
|
|
2327
|
+
isPlainObject,
|
|
2328
|
+
isPropertyBuilder,
|
|
2329
|
+
isReadOnly,
|
|
2330
|
+
isValidRegExp,
|
|
2331
|
+
mergeDeep,
|
|
2332
|
+
mergeEntityActions,
|
|
2333
|
+
navigateToEntity,
|
|
2334
|
+
pick,
|
|
2335
|
+
plural,
|
|
2336
|
+
prettifyIdentifier,
|
|
2337
|
+
randomColor,
|
|
2338
|
+
randomString,
|
|
2339
|
+
registerConditionOperations,
|
|
2340
|
+
removeFunctions,
|
|
2341
|
+
removeInPath,
|
|
2342
|
+
removeInitialAndTrailingSlashes,
|
|
2343
|
+
removeInitialSlash,
|
|
2344
|
+
removeNulls,
|
|
2345
|
+
removePropsIfExisting,
|
|
2346
|
+
removeTrailingSlash,
|
|
2347
|
+
removeUndefined,
|
|
2348
|
+
resolveArrayProperties,
|
|
2349
|
+
resolveCollectionPathIds,
|
|
2350
|
+
resolveCollectionRelations,
|
|
2351
|
+
resolveDefaultSelectedView,
|
|
2352
|
+
resolveEntityAction,
|
|
2353
|
+
resolveEntityView$1 as resolveEntityView,
|
|
2354
|
+
resolveEnumValues,
|
|
2355
|
+
resolveProperties,
|
|
2356
|
+
resolveProperty,
|
|
2357
|
+
resolvePropertyEnum,
|
|
2358
|
+
resolvePropertyRelation,
|
|
2359
|
+
resolveRelationProperty,
|
|
2360
|
+
resolveStorageFilenameString,
|
|
2361
|
+
resolveStoragePathString,
|
|
2362
|
+
resolvedSelectedEntityView,
|
|
2363
|
+
sanitizeData,
|
|
2364
|
+
sanitizeRelation,
|
|
2365
|
+
segmentsToStrippedPath,
|
|
2366
|
+
serializeRegExp,
|
|
2367
|
+
setIn,
|
|
2368
|
+
singular,
|
|
2369
|
+
slugify,
|
|
2370
|
+
sortProperties,
|
|
2371
|
+
stripCollectionPath,
|
|
2372
|
+
toArray,
|
|
2373
|
+
toKebabCase,
|
|
2374
|
+
toSnakeCase,
|
|
2375
|
+
traverseValueProperty,
|
|
2376
|
+
traverseValuesProperties,
|
|
2377
|
+
unslugify,
|
|
2378
|
+
updateDateAutoValues
|
|
2379
|
+
};
|
|
2380
|
+
//# sourceMappingURL=index.es.js.map
|