@rebasepro/common 0.0.1-canary.0 → 0.0.1-canary.0e2f892

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.
Files changed (63) hide show
  1. package/README.md +3 -3
  2. package/dist/collections/CollectionRegistry.d.ts +10 -2
  3. package/dist/data/buildRebaseData.d.ts +14 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.es.js +635 -853
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +653 -871
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/util/builders.d.ts +6 -13
  10. package/dist/util/collections.d.ts +2 -2
  11. package/dist/util/entities.d.ts +38 -8
  12. package/dist/util/index.d.ts +1 -11
  13. package/dist/util/navigation_from_path.d.ts +10 -5
  14. package/dist/util/navigation_utils.d.ts +1 -12
  15. package/dist/util/permissions.d.ts +4 -4
  16. package/dist/util/references.d.ts +1 -1
  17. package/dist/util/relations.d.ts +10 -0
  18. package/dist/util/resolutions.d.ts +11 -13
  19. package/dist/util/storage.d.ts +4 -4
  20. package/package.json +106 -113
  21. package/src/collections/CollectionRegistry.ts +220 -48
  22. package/src/data/buildRebaseData.ts +285 -0
  23. package/src/index.ts +1 -0
  24. package/src/types/json-logic-js.d.ts +8 -0
  25. package/src/util/builders.ts +9 -19
  26. package/src/util/callbacks.ts +6 -6
  27. package/src/util/collections.ts +11 -8
  28. package/src/util/conditions.ts +6 -6
  29. package/src/util/entities.ts +74 -15
  30. package/src/util/index.ts +1 -11
  31. package/src/util/navigation_from_path.ts +9 -8
  32. package/src/util/navigation_utils.ts +4 -56
  33. package/src/util/parent_references_from_path.ts +4 -3
  34. package/src/util/permissions.test.ts +233 -115
  35. package/src/util/permissions.ts +26 -26
  36. package/src/util/references.ts +16 -2
  37. package/src/util/relations.ts +109 -29
  38. package/src/util/resolutions.ts +83 -83
  39. package/src/util/storage.ts +5 -5
  40. package/dist/util/arrays.d.ts +0 -1
  41. package/dist/util/dates.d.ts +0 -1
  42. package/dist/util/entity_actions.d.ts +0 -2
  43. package/dist/util/fields.d.ts +0 -2
  44. package/dist/util/flatten_object.d.ts +0 -5
  45. package/dist/util/hash.d.ts +0 -1
  46. package/dist/util/names.d.ts +0 -22
  47. package/dist/util/objects.d.ts +0 -26
  48. package/dist/util/os.d.ts +0 -2
  49. package/dist/util/plurals.d.ts +0 -16
  50. package/dist/util/regexp.d.ts +0 -7
  51. package/dist/util/strings.d.ts +0 -7
  52. package/src/util/arrays.ts +0 -3
  53. package/src/util/dates.ts +0 -1
  54. package/src/util/entity_actions.ts +0 -28
  55. package/src/util/fields.ts +0 -28
  56. package/src/util/flatten_object.ts +0 -45
  57. package/src/util/hash.ts +0 -11
  58. package/src/util/names.ts +0 -30
  59. package/src/util/objects.ts +0 -376
  60. package/src/util/os.ts +0 -13
  61. package/src/util/plurals.ts +0 -188
  62. package/src/util/regexp.ts +0 -32
  63. package/src/util/strings.ts +0 -84
package/dist/index.es.js CHANGED
@@ -1,299 +1,17 @@
1
- import { GeoPoint, EntityReference, EntityRelation } from "@rebasepro/types";
2
- import hash from "object-hash";
1
+ import { EntityReference, EntityRelation, getDataSourceCapabilities } from "@rebasepro/types";
2
+ import { mergeDeep, getIn as getIn$1, isDefaultFieldConfigId, randomString, toSnakeCase, generateForeignKeyName, removeFunctions } from "@rebasepro/utils";
3
3
  import jsonLogic from "json-logic-js";
4
4
  import { deepEqual } from "fast-equals";
5
5
  import cloneDeep from "lodash/cloneDeep.js";
6
6
  const DEFAULT_ONE_OF_TYPE = "type";
7
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
8
  function isReadOnly(property) {
291
9
  if (property.readOnly) return true;
292
10
  if (property.type === "date") {
293
11
  if (property.autoValue) return true;
294
12
  }
295
13
  if (property.type === "reference") {
296
- return !property.path && !property.Field;
14
+ return !property.path && !("Field" in property && property.Field);
297
15
  }
298
16
  return false;
299
17
  }
@@ -379,12 +97,20 @@ function getReferenceFrom(entity) {
379
97
  return new EntityReference({
380
98
  id: entity.id,
381
99
  path: entity.path,
382
- datasource: entity.datasource,
100
+ driver: entity.driver,
383
101
  databaseId: entity.databaseId
384
102
  });
385
103
  }
386
104
  function getRelationFrom(entity) {
387
- return new EntityRelation(entity.id, entity.path);
105
+ return new EntityRelation(entity.id, entity.path, entity);
106
+ }
107
+ function normalizeToEntityRelation(value) {
108
+ if (value instanceof EntityRelation) return value;
109
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
110
+ const obj = value;
111
+ const isRelationLike = obj.__type === "relation" || obj.__type === "reference" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation() || typeof obj.isEntityReference === "function" && obj.isEntityReference();
112
+ if (!isRelationLike) return null;
113
+ return new EntityRelation(obj.id, obj.path, obj.data);
388
114
  }
389
115
  function traverseValuesProperties(inputValues, properties, operation) {
390
116
  const safeInputValues = inputValues ?? {};
@@ -440,6 +166,21 @@ function traverseValueProperty(inputValue, property, operation) {
440
166
  }
441
167
  return value;
442
168
  }
169
+ function createRelationRef(id, path) {
170
+ return {
171
+ id,
172
+ path,
173
+ __type: "relation"
174
+ };
175
+ }
176
+ function createRelationRefWithData(id, path, data) {
177
+ return {
178
+ id,
179
+ path,
180
+ __type: "relation",
181
+ data
182
+ };
183
+ }
443
184
  function sortProperties(properties, propertiesOrder) {
444
185
  try {
445
186
  const propertiesKeys = Object.keys(properties);
@@ -538,58 +279,6 @@ function getPrimaryKeys(collection) {
538
279
  }
539
280
  return ["id"];
540
281
  }
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
282
  function enumToObjectEntries(enumValues) {
594
283
  if (Array.isArray(enumValues)) {
595
284
  return enumValues;
@@ -624,28 +313,6 @@ function segmentsToStrippedPath(paths) {
624
313
  function fullPathToCollectionSegments(path) {
625
314
  return path.split("/").filter((e, i) => i % 2 === 0);
626
315
  }
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
316
  function resolveProperty(props) {
650
317
  const {
651
318
  property,
@@ -655,22 +322,24 @@ function resolveProperty(props) {
655
322
  let resultProperty;
656
323
  if (isPropertyBuilder(property)) {
657
324
  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 ?? {});
325
+ if (!path) {
326
+ resultProperty = property;
327
+ } else {
328
+ const usedPropertyValue = rest.propertyKey ? getIn$1(rest.values, rest.propertyKey) : void 0;
329
+ const dynamicProps = property.dynamicProps?.({
330
+ ...rest,
331
+ path,
332
+ propertyValue: usedPropertyValue,
333
+ values: rest.values ?? {},
334
+ previousValues: rest.previousValues ?? rest.values ?? {}
335
+ });
336
+ resultProperty = mergeDeep(property, dynamicProps ?? {});
337
+ }
668
338
  } else {
669
339
  resultProperty = property;
670
340
  }
671
- if (resultProperty.dynamicProps) {
341
+ if (resultProperty?.dynamicProps && rest.path) {
672
342
  const path = rest.path;
673
- if (!path) throw Error("Trying to resolve dynamicProps without specifying the entity path");
674
343
  const usedPropertyValue = rest.propertyKey ? getIn$1(rest.values, rest.propertyKey) : void 0;
675
344
  const dynamicPropsResult = resultProperty.dynamicProps({
676
345
  ...rest,
@@ -712,10 +381,10 @@ function resolveProperty(props) {
712
381
  return resolvedProperty;
713
382
  }
714
383
  if (customField.property) {
715
- const {
716
- propertyConfig: _unused,
717
- ...restConfigProperty
718
- } = customField.property;
384
+ const restConfigProperty = {
385
+ ...customField.property
386
+ };
387
+ delete restConfigProperty.propertyConfig;
719
388
  const customFieldProperty = resolveProperty({
720
389
  property: {
721
390
  name: "",
@@ -731,10 +400,14 @@ function resolveProperty(props) {
731
400
  }
732
401
  return resolvedProperty;
733
402
  }
734
- function resolveRelationProperty(property, relations) {
735
- const relation = relations.find((rel) => rel.relationName === property.relationName);
403
+ function resolveRelationProperty(property, relations, propertyKey) {
404
+ if (property.relation) {
405
+ return property;
406
+ }
407
+ const name = property.relationName || propertyKey;
408
+ const relation = name ? relations.find((rel) => rel.relationName === name) : void 0;
736
409
  if (!relation) {
737
- throw Error(`Relation ${property.relationName} not found`);
410
+ throw Error(`Relation ${name ?? "(unnamed)"} not found`);
738
411
  }
739
412
  return {
740
413
  ...property,
@@ -827,7 +500,7 @@ function resolveArrayProperties({
827
500
  });
828
501
  }).filter((e) => Boolean(e)) : [];
829
502
  return resolvedProperties;
830
- } else if (!property.Field) {
503
+ } else if (!("Field" in property && property.Field)) {
831
504
  throw Error(`The array property (${propertyKey}) needs to declare an 'of' or a 'oneOf' property, or provide a custom \`Field\` component`);
832
505
  } else {
833
506
  return [];
@@ -862,209 +535,41 @@ function resolveEnumValues(input) {
862
535
  return void 0;
863
536
  }
864
537
  }
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
538
  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;
539
+ if (collection.childCollections) {
540
+ return collection.childCollections() ?? [];
541
+ }
542
+ if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
543
+ return collection.subcollections() ?? [];
544
+ }
545
+ if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
546
+ const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
547
+ return manyRelations.map((r) => {
548
+ const target = r.target();
549
+ if (!target) return void 0;
550
+ const relationKey = r.relationName || target.slug;
551
+ let customName;
552
+ if (collection.properties) {
553
+ const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
554
+ if (prop && prop[1].name) {
555
+ customName = prop[1].name;
556
+ }
970
557
  }
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));
558
+ const baseOverrides = {
559
+ slug: relationKey
560
+ };
561
+ if (customName) {
562
+ baseOverrides.name = customName;
563
+ baseOverrides.singularName = customName;
997
564
  }
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
565
+ const targetWithOverrides = {
566
+ ...target,
567
+ ...baseOverrides
1056
568
  };
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;
569
+ return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
570
+ }).filter((c) => Boolean(c));
571
+ }
572
+ return [];
1068
573
  }
1069
574
  function evaluateAST(sqlString, auth, entity) {
1070
575
  if (!entity) return true;
@@ -1130,8 +635,8 @@ function evaluateAST(sqlString, auth, entity) {
1130
635
  const userRoles = auth.user?.roles || [];
1131
636
  return requiredRoles.every((r) => userRoles.includes(r));
1132
637
  }
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_]+)\\}?`);
638
+ const pattern1 = new RegExp("^\\{?([a-zA-Z0-9_]+)\\}?\\s*=\\s*(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))");
639
+ const pattern2 = new RegExp("^(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))\\s*=\\s*\\{?([a-zA-Z0-9_]+)\\}?");
1135
640
  const match1 = cleanedSQL.match(pattern1);
1136
641
  if (match1 && match1[1]) {
1137
642
  return entity.values[match1[1]] === auth.user?.uid;
@@ -1164,10 +669,11 @@ function evaluateRule(rule, auth, entity) {
1164
669
  return true;
1165
670
  }
1166
671
  function checkOperation(collection, authController, entity, targetOperation) {
1167
- if (!collection.securityRules || collection.securityRules.length === 0) {
672
+ const securityRules = getDataSourceCapabilities(collection.driver).supportsRLS ? collection.securityRules : void 0;
673
+ if (!securityRules || securityRules.length === 0) {
1168
674
  return true;
1169
675
  }
1170
- const applicableRules = collection.securityRules.filter((r) => r.operation === targetOperation || r.operation === "all" || r.operations?.includes(targetOperation) || r.operations?.includes("all"));
676
+ const applicableRules = securityRules.filter((r) => r.operation === targetOperation || r.operation === "all" || r.operations?.includes(targetOperation) || r.operations?.includes("all"));
1171
677
  if (applicableRules.length === 0) return false;
1172
678
  const userRoleIds = authController.user?.roles ?? [];
1173
679
  const userRoles = [...userRoleIds, "public"];
@@ -1204,127 +710,11 @@ function canEditEntity(collection, authController, path, entity) {
1204
710
  return checkOperation(collection, authController, entity, "update");
1205
711
  }
1206
712
  function canCreateEntity(collection, authController, path, entity) {
1207
- if (collection.collectionGroup) return false;
1208
713
  return checkOperation(collection, authController, entity, "insert");
1209
714
  }
1210
715
  function canDeleteEntity(collection, authController, path, entity) {
1211
716
  return checkOperation(collection, authController, entity, "delete");
1212
717
  }
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
718
  function getEntityImagePreviewPropertyKey(collection) {
1329
719
  for (const key in collection.properties) {
1330
720
  const property = collection.properties[key];
@@ -1346,46 +736,136 @@ function getEntityImagePreviewPropertyKey(collection) {
1346
736
  }
1347
737
  for (const key in collection.properties) {
1348
738
  const property = collection.properties[key];
1349
- if (property.type === "array" && property.of?.type === "string" && property.of.url === "image") {
739
+ if (property.type === "array" && property.of && !Array.isArray(property.of) && property.of.type === "string" && property.of.url === "image") {
740
+ return key;
741
+ }
742
+ }
743
+ for (const key in collection.properties) {
744
+ const property = collection.properties[key];
745
+ if (property.type === "string" && property.storage && !property.storage.acceptedFiles) {
746
+ return key;
747
+ }
748
+ }
749
+ for (const key in collection.properties) {
750
+ const property = collection.properties[key];
751
+ if (property.type === "array" && !Array.isArray(property.of) && property.of?.type === "string" && property.of.storage && !property.of.storage.acceptedFiles) {
1350
752
  return key;
1351
753
  }
1352
754
  }
1353
755
  return void 0;
1354
756
  }
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
- });
757
+ function removeInitialAndTrailingSlashes(s) {
758
+ return removeInitialSlash(removeTrailingSlash(s));
759
+ }
760
+ function removeInitialSlash(s) {
761
+ if (s.startsWith("/")) return s.slice(1);
762
+ else return s;
763
+ }
764
+ function removeTrailingSlash(s) {
765
+ if (s.endsWith("/")) return s.slice(0, -1);
766
+ else return s;
767
+ }
768
+ function addInitialSlash(s) {
769
+ if (s.startsWith("/")) return s;
770
+ else return `/${s}`;
771
+ }
772
+ function getLastSegment(path) {
773
+ const cleanPath = removeInitialAndTrailingSlashes(path);
774
+ if (cleanPath.includes("/")) {
775
+ const segments = cleanPath.split("/");
776
+ return segments[segments.length - 1];
777
+ }
778
+ return cleanPath;
779
+ }
780
+ function resolveCollectionPathIds(path, allCollections) {
781
+ let remainingPath = removeInitialAndTrailingSlashes(path);
782
+ if (!remainingPath) {
783
+ return "";
784
+ }
785
+ let currentCollections = allCollections;
786
+ const resolvedPathParts = [];
787
+ while (remainingPath.length > 0) {
788
+ if (!currentCollections || currentCollections.length === 0) {
789
+ console.warn(`resolveCollectionPathIds: Path structure implies subcollections, but none found before segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
790
+ resolvedPathParts.push(remainingPath);
791
+ remainingPath = "";
792
+ break;
793
+ }
794
+ let foundMatch = false;
795
+ const potentialMatches = currentCollections.flatMap((col) => [{
796
+ col,
797
+ match: col.slug
798
+ }]).filter((p) => p.match && remainingPath.startsWith(p.match)).sort((a, b) => b.match.length - a.match.length);
799
+ if (potentialMatches.length > 0) {
800
+ const {
801
+ col: foundCollection,
802
+ match: matchString
803
+ } = potentialMatches[0];
804
+ resolvedPathParts.push(foundCollection.slug);
805
+ remainingPath = removeInitialSlash(remainingPath.substring(matchString.length));
806
+ if (remainingPath.length === 0) {
807
+ foundMatch = true;
808
+ break;
809
+ }
810
+ const idSeparatorIndex = remainingPath.indexOf("/");
811
+ let entityId;
812
+ if (idSeparatorIndex > -1) {
813
+ entityId = remainingPath.substring(0, idSeparatorIndex);
814
+ remainingPath = remainingPath.substring(idSeparatorIndex + 1);
1364
815
  } else {
1365
- Object.assign(flatObj, flattenObject(obj[key], newKey));
816
+ entityId = remainingPath;
817
+ remainingPath = "";
818
+ 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.`);
819
+ }
820
+ resolvedPathParts.push(entityId);
821
+ currentCollections = getSubcollections(foundCollection);
822
+ foundMatch = true;
823
+ if (!currentCollections && remainingPath.length > 0) {
824
+ 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.`);
825
+ resolvedPathParts.push(remainingPath);
826
+ remainingPath = "";
827
+ break;
1366
828
  }
1367
- } else {
1368
- flatObj[newKey] = obj[key];
1369
829
  }
1370
- return flatObj;
1371
- }, {});
830
+ if (!foundMatch) {
831
+ console.warn(`resolveCollectionPathIds: Collection definition not found for segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
832
+ resolvedPathParts.push(remainingPath);
833
+ remainingPath = "";
834
+ break;
835
+ }
836
+ }
837
+ return resolvedPathParts.join("/");
1372
838
  }
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
- });
839
+ function getCollectionBySlugWithin(slugOrPath, collections) {
840
+ const subpaths = removeInitialAndTrailingSlashes(slugOrPath).split("/");
841
+ if (subpaths.length % 2 === 0) {
842
+ throw Error(`getCollectionBySlug: Collection paths must have an odd number of segments: ${slugOrPath}`);
843
+ }
844
+ const subpathCombinations = getCollectionPathsCombinations(subpaths);
845
+ let result;
846
+ for (let i = 0; i < subpathCombinations.length; i++) {
847
+ const subpathCombination = subpathCombinations[i];
848
+ const navigationEntry = collections && collections.sort((a, b) => (a.slug ?? "").localeCompare(b.slug ?? "")).find((entry) => entry.slug === subpathCombination);
849
+ if (navigationEntry) {
850
+ if (subpathCombination === slugOrPath) {
851
+ result = navigationEntry;
852
+ } else if (getSubcollections(navigationEntry).length > 0) {
853
+ const newPath = slugOrPath.replace(subpathCombination, "").split("/").slice(2).join("/");
854
+ if (newPath.length > 0) result = getCollectionBySlugWithin(newPath, getSubcollections(navigationEntry));
1385
855
  }
1386
- });
1387
- return acc;
1388
- }, {});
856
+ }
857
+ if (result) break;
858
+ }
859
+ return result;
860
+ }
861
+ function getCollectionPathsCombinations(subpaths) {
862
+ const entries = subpaths.length > 0 && subpaths.length % 2 === 0 ? subpaths.splice(0, subpaths.length - 1) : subpaths;
863
+ const length = entries.length;
864
+ const result = [];
865
+ for (let i = length; i > 0; i = i - 2) {
866
+ result.push(entries.slice(0, i).join("/"));
867
+ }
868
+ return result;
1389
869
  }
1390
870
  function getNavigationEntriesFromPath(props) {
1391
871
  const {
@@ -1426,7 +906,7 @@ function getNavigationEntriesFromPath(props) {
1426
906
  throw Error("collection not found resolving path: " + collection);
1427
907
  }
1428
908
  const entityViews = collection.entityViews;
1429
- const customView = entityViews && entityViews.map((entry) => resolveEntityView(entry, props.contextEntityViews)).filter(Boolean).find((entry) => entry.key === newPath);
909
+ const customView = entityViews && entityViews.map((entry) => resolveEntityView(entry, props.contextEntityViews)).filter((v) => v != null).find((entry) => entry.key === newPath);
1430
910
  const subcollections = getSubcollections(collection);
1431
911
  if (customView) {
1432
912
  result.push({
@@ -1486,7 +966,7 @@ function getParentReferencesFromPath(props) {
1486
966
  if (!collection) {
1487
967
  throw Error("collection not found resolving path: " + collection);
1488
968
  }
1489
- if (collection.subcollections) {
969
+ if (getSubcollections(collection).length > 0) {
1490
970
  result.push(...getParentReferencesFromPath({
1491
971
  path: newPath,
1492
972
  collections: getSubcollections(collection),
@@ -1524,9 +1004,6 @@ function buildEntityCallbacks(callbacks) {
1524
1004
  function buildAdditionalFieldDelegate(additionalFieldDelegate) {
1525
1005
  return additionalFieldDelegate;
1526
1006
  }
1527
- function buildFieldConfig(propertyConfig) {
1528
- return propertyConfig;
1529
- }
1530
1007
  async function resolveStorageFilenameString({
1531
1008
  input,
1532
1009
  storage,
@@ -1618,9 +1095,6 @@ function replacePlaceholders({
1618
1095
  if (!result) result = randomString() + "_" + file.name;
1619
1096
  return result;
1620
1097
  }
1621
- function toArray(input) {
1622
- return Array.isArray(input) ? input : input ? [input] : [];
1623
- }
1624
1098
  function hasPropertyCallbacks(properties, callbackName) {
1625
1099
  if (!properties) return false;
1626
1100
  for (const property of Object.values(properties)) {
@@ -1639,13 +1113,13 @@ function hasPropertyCallbacks(properties, callbackName) {
1639
1113
  }
1640
1114
  async function processProperties(properties, values, previousValues, propsContext, callbackName) {
1641
1115
  if (!values || typeof values !== "object") return values;
1642
- let result = {
1116
+ const result = {
1643
1117
  ...values
1644
1118
  };
1645
1119
  for (const [key, property] of Object.entries(properties)) {
1646
1120
  if (result[key] === void 0) continue;
1647
1121
  let currentValue = result[key];
1648
- let previousValue = previousValues?.[key];
1122
+ const previousValue = previousValues?.[key];
1649
1123
  if (property.type === "array" && Array.isArray(currentValue)) {
1650
1124
  if (property.of && !Array.isArray(property.of)) {
1651
1125
  currentValue = await Promise.all(currentValue.map(async (item, index) => {
@@ -1697,22 +1171,6 @@ const buildPropertyCallbacks = (properties) => {
1697
1171
  }
1698
1172
  return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1699
1173
  };
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
1174
  function sanitizeRelation(relation, sourceCollection) {
1717
1175
  if (!relation.target) {
1718
1176
  throw new Error("Relation is missing a `target` collection.");
@@ -1722,7 +1180,7 @@ function sanitizeRelation(relation, sourceCollection) {
1722
1180
  ...relation
1723
1181
  };
1724
1182
  if (!newRelation.relationName) {
1725
- newRelation.relationName = toSnakeCase(targetCollection.slug ?? targetCollection.dbPath);
1183
+ newRelation.relationName = toSnakeCase(targetCollection.slug);
1726
1184
  }
1727
1185
  if (!newRelation.direction) {
1728
1186
  if (newRelation.foreignKeyOnTarget) newRelation.direction = "inverse";
@@ -1740,12 +1198,12 @@ function sanitizeRelation(relation, sourceCollection) {
1740
1198
  if (!newRelation.foreignKeyOnTarget) {
1741
1199
  let foundForeignKey = false;
1742
1200
  try {
1743
- const targetRelations = targetCollection.relations || [];
1201
+ const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
1744
1202
  for (const targetRel of targetRelations) {
1745
1203
  if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) {
1746
1204
  try {
1747
1205
  const targetRelTarget = targetRel.target();
1748
- if (targetRelTarget.slug === sourceCollection.slug || targetRelTarget.dbPath === sourceCollection.dbPath) {
1206
+ if (targetRelTarget.slug === sourceCollection.slug) {
1749
1207
  newRelation.foreignKeyOnTarget = targetRel.localKey;
1750
1208
  foundForeignKey = true;
1751
1209
  break;
@@ -1766,9 +1224,9 @@ function sanitizeRelation(relation, sourceCollection) {
1766
1224
  let isManyToManyInverse = false;
1767
1225
  if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) {
1768
1226
  try {
1769
- const targetRelations = targetCollection.relations || [];
1227
+ const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
1770
1228
  for (const targetRel of targetRelations) {
1771
- if (targetRel.cardinality === "many" && targetRel.direction === "owning" && targetRel.through && targetRel.relationName === newRelation.inverseRelationName) {
1229
+ if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
1772
1230
  isManyToManyInverse = true;
1773
1231
  break;
1774
1232
  }
@@ -1795,19 +1253,26 @@ function sanitizeRelation(relation, sourceCollection) {
1795
1253
  if (newRelation.cardinality === "one" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) {
1796
1254
  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
1255
  }
1798
- if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) {
1256
+ if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath && !newRelation.inverseRelationName) {
1799
1257
  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
1258
  }
1801
1259
  return newRelation;
1802
1260
  }
1261
+ const _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
1803
1262
  function resolveCollectionRelations(collection) {
1263
+ const cached = _resolvedRelationsCache.get(collection);
1264
+ if (cached) return cached;
1265
+ if (!getDataSourceCapabilities(collection.driver).supportsRelations) return {};
1266
+ const relCollection = collection;
1804
1267
  const relations = {};
1805
- if (collection.relations) {
1806
- collection.relations.forEach((relation) => {
1268
+ const registeredRelationNames = /* @__PURE__ */ new Set();
1269
+ if (relCollection.relations) {
1270
+ relCollection.relations.forEach((relation) => {
1807
1271
  const normalizedRelation = sanitizeRelation(relation, collection);
1808
1272
  const relationKey = normalizedRelation.relationName;
1809
1273
  if (relationKey) {
1810
1274
  relations[relationKey] = normalizedRelation;
1275
+ registeredRelationNames.add(relationKey);
1811
1276
  }
1812
1277
  });
1813
1278
  }
@@ -1819,15 +1284,17 @@ function resolveCollectionRelations(collection) {
1819
1284
  sourceCollection: collection
1820
1285
  });
1821
1286
  if (relation) {
1822
- if (!relations[propKey]) {
1823
- if (!relation.relationName) {
1824
- relation.relationName = propKey;
1825
- }
1826
- relations[propKey] = sanitizeRelation(relation, collection);
1287
+ if (relations[propKey]) return;
1288
+ if (!relation.relationName) {
1289
+ relation.relationName = propKey;
1827
1290
  }
1291
+ const normalizedRelation = sanitizeRelation(relation, collection);
1292
+ relations[propKey] = normalizedRelation;
1293
+ registeredRelationNames.add(normalizedRelation.relationName ?? propKey);
1828
1294
  }
1829
1295
  });
1830
1296
  }
1297
+ _resolvedRelationsCache.set(collection, relations);
1831
1298
  return relations;
1832
1299
  }
1833
1300
  function resolvePropertyRelation({
@@ -1836,15 +1303,35 @@ function resolvePropertyRelation({
1836
1303
  sourceCollection
1837
1304
  }) {
1838
1305
  if (property.type !== "relation") return void 0;
1839
- const relation = sourceCollection.relations?.find((rel) => rel.relationName === property.relationName);
1306
+ const relProp = property;
1307
+ if (relProp.target) {
1308
+ return {
1309
+ relationName: relProp.relationName || propertyKey,
1310
+ target: relProp.target,
1311
+ cardinality: relProp.cardinality || "one",
1312
+ direction: relProp.direction || "owning",
1313
+ inverseRelationName: relProp.inverseRelationName,
1314
+ localKey: relProp.localKey,
1315
+ foreignKeyOnTarget: relProp.foreignKeyOnTarget,
1316
+ through: relProp.through,
1317
+ joinPath: relProp.joinPath,
1318
+ onUpdate: relProp.onUpdate,
1319
+ onDelete: relProp.onDelete,
1320
+ overrides: relProp.overrides
1321
+ };
1322
+ }
1323
+ const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === relProp.relationName);
1840
1324
  if (!relation) {
1841
- console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug || sourceCollection.dbPath}'`);
1325
+ console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1842
1326
  return void 0;
1843
1327
  }
1844
1328
  return relation;
1845
1329
  }
1846
1330
  function getTableName(collection) {
1847
- return collection.dbPath ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1331
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
1332
+ return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1333
+ }
1334
+ return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1848
1335
  }
1849
1336
  function getTableVarName(tableName) {
1850
1337
  return tableName.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
@@ -1857,6 +1344,14 @@ function getEnumVarName(tableName, propName) {
1857
1344
  function getColumnName(fullColumn) {
1858
1345
  return fullColumn.includes(".") ? fullColumn.split(".").pop() : fullColumn;
1859
1346
  }
1347
+ function findRelation(resolvedRelations, key) {
1348
+ if (resolvedRelations[key]) return resolvedRelations[key];
1349
+ const slugKey = key.replace(/_/g, "-");
1350
+ if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];
1351
+ const snakeKey = key.replace(/-/g, "_");
1352
+ if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
1353
+ return void 0;
1354
+ }
1860
1355
  function getIn(obj, path) {
1861
1356
  if (!obj || !path) return void 0;
1862
1357
  return path.split(".").reduce((acc, part) => acc && acc[part], obj);
@@ -1942,7 +1437,7 @@ function buildConditionContext(params) {
1942
1437
  email: user?.email ?? null,
1943
1438
  displayName: user?.displayName ?? null,
1944
1439
  photoURL: user?.photoURL ?? null,
1945
- roles: user?.roles ?? []
1440
+ roles: (user?.roles ?? []).map((r) => typeof r === "string" ? r : r.id)
1946
1441
  },
1947
1442
  now: Date.now()
1948
1443
  };
@@ -1952,7 +1447,7 @@ function applyPropertyConditions(property, context) {
1952
1447
  conditions
1953
1448
  } = property;
1954
1449
  if (!conditions) return property;
1955
- let result = {
1450
+ const result = {
1956
1451
  ...property
1957
1452
  };
1958
1453
  if (conditions.disabled) {
@@ -1992,8 +1487,8 @@ function applyPropertyConditions(property, context) {
1992
1487
  if (context.isNew && conditions.defaultValue !== void 0) {
1993
1488
  result.defaultValue = evaluateCondition(conditions.defaultValue, context);
1994
1489
  }
1995
- if ("enumValues" in result && result.enumValues && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) {
1996
- result.enumValues = applyEnumConditions(result.enumValues, conditions, context);
1490
+ if ("enum" in result && result.enum && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) {
1491
+ result.enum = applyEnumConditions(result.enum, conditions, context);
1997
1492
  }
1998
1493
  if (result.type === "reference") {
1999
1494
  if (conditions.referencePath) {
@@ -2059,13 +1554,15 @@ function applyEnumConditions(enumValues, conditions, context) {
2059
1554
  }
2060
1555
  class CollectionRegistry {
2061
1556
  // Normalized runtime layer (used by Data Grid / UI)
2062
- collectionsByDbPath = /* @__PURE__ */ new Map();
1557
+ collectionsByTableName = /* @__PURE__ */ new Map();
2063
1558
  collectionsBySlug = /* @__PURE__ */ new Map();
2064
1559
  rootCollections = [];
1560
+ cachedCollectionsList = null;
2065
1561
  // Raw configuration layer (used by Collection Editor AST generator)
2066
- rawCollectionsByDbPath = /* @__PURE__ */ new Map();
1562
+ rawCollectionsByTableName = /* @__PURE__ */ new Map();
2067
1563
  rawCollectionsBySlug = /* @__PURE__ */ new Map();
2068
1564
  rawRootCollections = [];
1565
+ cachedRawCollectionsList = null;
2069
1566
  // Snapshot of raw input for idempotency check — compared BEFORE normalization
2070
1567
  // to avoid the issue where normalization creates new objects that always fail equality.
2071
1568
  lastRawInputSnapshot = null;
@@ -2075,12 +1572,14 @@ class CollectionRegistry {
2075
1572
  }
2076
1573
  }
2077
1574
  reset() {
2078
- this.collectionsByDbPath.clear();
1575
+ this.collectionsByTableName.clear();
2079
1576
  this.collectionsBySlug.clear();
2080
1577
  this.rootCollections = [];
2081
- this.rawCollectionsByDbPath.clear();
1578
+ this.cachedCollectionsList = null;
1579
+ this.rawCollectionsByTableName.clear();
2082
1580
  this.rawCollectionsBySlug.clear();
2083
1581
  this.rawRootCollections = [];
1582
+ this.cachedRawCollectionsList = null;
2084
1583
  }
2085
1584
  /**
2086
1585
  * Registers a collection and its subcollections recursively.
@@ -2099,7 +1598,31 @@ class CollectionRegistry {
2099
1598
  const normalizedCollections = collections.map((c) => this.normalizeCollection({
2100
1599
  ...c
2101
1600
  }));
2102
- normalizedCollections.forEach((c, index) => this.register(c, collections[index]));
1601
+ normalizedCollections.forEach((c, index) => {
1602
+ const raw = cloneDeep(collections[index]);
1603
+ this.rootCollections.push(c);
1604
+ this.rawRootCollections.push(raw);
1605
+ const normalized = this.normalizeCollection(c);
1606
+ this.collectionsByTableName.set(getTableName(normalized), normalized);
1607
+ this.rawCollectionsByTableName.set(getTableName(raw), raw);
1608
+ if (normalized.slug) {
1609
+ this.collectionsBySlug.set(normalized.slug, normalized);
1610
+ }
1611
+ if (raw.slug) {
1612
+ this.rawCollectionsBySlug.set(raw.slug, raw);
1613
+ }
1614
+ });
1615
+ normalizedCollections.forEach((c) => {
1616
+ const subcollections = getSubcollections(c);
1617
+ if (subcollections && subcollections.length > 0) {
1618
+ subcollections.forEach((subCollection) => {
1619
+ if (!subCollection) return;
1620
+ this._registerRecursively(this.normalizeCollection({
1621
+ ...subCollection
1622
+ }), cloneDeep(subCollection));
1623
+ });
1624
+ }
1625
+ });
2103
1626
  this.lastRawInputSnapshot = rawSnapshot;
2104
1627
  return true;
2105
1628
  }
@@ -2110,49 +1633,127 @@ class CollectionRegistry {
2110
1633
  this._registerRecursively(collection, raw);
2111
1634
  }
2112
1635
  _registerRecursively(collection, rawCollection) {
2113
- if (this.collectionsByDbPath.has(collection.dbPath)) {
1636
+ if (this.collectionsByTableName.has(getTableName(collection))) {
2114
1637
  return;
2115
1638
  }
2116
1639
  const normalizedCollection = this.normalizeCollection(collection);
2117
- this.collectionsByDbPath.set(normalizedCollection.dbPath, normalizedCollection);
2118
- this.rawCollectionsByDbPath.set(rawCollection.dbPath, rawCollection);
1640
+ this.collectionsByTableName.set(getTableName(normalizedCollection), normalizedCollection);
1641
+ this.rawCollectionsByTableName.set(getTableName(rawCollection), rawCollection);
2119
1642
  if (normalizedCollection.slug) {
2120
1643
  this.collectionsBySlug.set(normalizedCollection.slug, normalizedCollection);
2121
1644
  }
2122
1645
  if (rawCollection.slug) {
2123
1646
  this.rawCollectionsBySlug.set(rawCollection.slug, rawCollection);
2124
1647
  }
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]));
1648
+ const subcollections = getSubcollections(normalizedCollection);
1649
+ if (subcollections && subcollections.length > 0) {
1650
+ subcollections.forEach((subCollection) => {
1651
+ if (!subCollection) return;
1652
+ this._registerRecursively(this.normalizeCollection({
1653
+ ...subCollection
1654
+ }), cloneDeep(subCollection));
2130
1655
  });
2131
1656
  }
2132
1657
  }
2133
1658
  normalizeCollection(collection) {
2134
- const properties = this.normalizeProperties(collection.properties, collection.relations ?? []);
2135
- collection.properties = properties;
2136
- return collection;
1659
+ const result = {
1660
+ ...collection
1661
+ };
1662
+ const extractedRelations = this.extractRelationsFromProperties(result.properties);
1663
+ const relResult = result;
1664
+ const manualRelations = getDataSourceCapabilities(result.driver).supportsRelations ? relResult.relations ?? [] : [];
1665
+ const mergedRelationsRaw = [...extractedRelations];
1666
+ for (const manual of manualRelations) {
1667
+ const name = manual.relationName;
1668
+ if (!name || !mergedRelationsRaw.find((r) => r.relationName === name)) {
1669
+ mergedRelationsRaw.push(manual);
1670
+ }
1671
+ }
1672
+ let mergedRelations = mergedRelationsRaw;
1673
+ if (getDataSourceCapabilities(result.driver).supportsRelations) {
1674
+ mergedRelations = mergedRelationsRaw.map((r) => {
1675
+ try {
1676
+ return sanitizeRelation(r, result);
1677
+ } catch {
1678
+ return r;
1679
+ }
1680
+ });
1681
+ relResult.relations = mergedRelations;
1682
+ }
1683
+ const properties = this.normalizeProperties(result.properties, mergedRelations);
1684
+ result.properties = properties;
1685
+ if (!result.childCollections) {
1686
+ if (getDataSourceCapabilities(result.driver).supportsSubcollections && result.subcollections) {
1687
+ result.childCollections = result.subcollections;
1688
+ } else if (getDataSourceCapabilities(result.driver).supportsRelations && relResult.relations) {
1689
+ const manyRelations = relResult.relations.filter((r) => r.cardinality === "many");
1690
+ if (manyRelations.length > 0) {
1691
+ result.childCollections = () => manyRelations.map((r) => {
1692
+ const target = r.target();
1693
+ return r.overrides ? mergeDeep(target, r.overrides) : target;
1694
+ });
1695
+ }
1696
+ }
1697
+ }
1698
+ return result;
1699
+ }
1700
+ /**
1701
+ * Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
1702
+ * This allows developers to define relations directly on properties without a separate
1703
+ * `relations[]` entry on the collection.
1704
+ */
1705
+ extractRelationsFromProperties(properties) {
1706
+ const relations = [];
1707
+ for (const [key, property] of Object.entries(properties)) {
1708
+ if (property.type === "relation") {
1709
+ const relProp = property;
1710
+ const target = relProp.target ?? relProp.relation?.target;
1711
+ if (target) {
1712
+ const relationName = relProp.relationName ?? relProp.relation?.relationName ?? key;
1713
+ relations.push({
1714
+ relationName,
1715
+ target,
1716
+ cardinality: relProp.cardinality ?? relProp.relation?.cardinality ?? "one",
1717
+ direction: relProp.direction ?? relProp.relation?.direction ?? "owning",
1718
+ inverseRelationName: relProp.inverseRelationName ?? relProp.relation?.inverseRelationName,
1719
+ localKey: relProp.localKey ?? relProp.relation?.localKey,
1720
+ foreignKeyOnTarget: relProp.foreignKeyOnTarget ?? relProp.relation?.foreignKeyOnTarget,
1721
+ through: relProp.through ?? relProp.relation?.through,
1722
+ joinPath: relProp.joinPath ?? relProp.relation?.joinPath,
1723
+ onUpdate: relProp.onUpdate ?? relProp.relation?.onUpdate,
1724
+ onDelete: relProp.onDelete ?? relProp.relation?.onDelete,
1725
+ overrides: relProp.overrides ?? relProp.relation?.overrides
1726
+ });
1727
+ }
1728
+ } else if (property.type === "map" && property.properties) {
1729
+ relations.push(...this.extractRelationsFromProperties(property.properties));
1730
+ }
1731
+ }
1732
+ return relations;
2137
1733
  }
2138
1734
  normalizeProperties(properties, relations) {
2139
1735
  const newProperties = {};
2140
1736
  for (const key in properties) {
2141
- newProperties[key] = this.normalizeProperty(properties[key], relations);
1737
+ newProperties[key] = this.normalizeProperty(key, properties[key], relations);
2142
1738
  }
2143
1739
  return newProperties;
2144
1740
  }
2145
- normalizeProperty(property, relations) {
1741
+ normalizeProperty(key, property, relations) {
2146
1742
  const newProperty = {
2147
1743
  ...property
2148
1744
  };
2149
1745
  if (newProperty.type === "map" && newProperty.properties) {
2150
1746
  newProperty.properties = this.normalizeProperties(newProperty.properties, relations);
2151
1747
  } 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);
1748
+ const arrayProp = newProperty;
1749
+ if (arrayProp.of) {
1750
+ if (Array.isArray(arrayProp.of)) {
1751
+ arrayProp.of = arrayProp.of.map((p, i) => this.normalizeProperty(`${key}[${i}]`, p, relations));
1752
+ } else {
1753
+ arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of, relations);
1754
+ }
1755
+ } else if (arrayProp.oneOf && arrayProp.oneOf.properties) {
1756
+ arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties, relations);
2156
1757
  }
2157
1758
  } else if ((newProperty.type === "string" || newProperty.type === "number") && newProperty.enum) {
2158
1759
  const stringOrNumberProperty = newProperty;
@@ -2161,11 +1762,12 @@ class CollectionRegistry {
2161
1762
  }
2162
1763
  } else if (newProperty.type === "relation") {
2163
1764
  const relationProperty = newProperty;
2164
- const relation = relations.find((r) => r.relationName === relationProperty.relationName);
1765
+ const name = relationProperty.relationName || key;
1766
+ const relation = relations.find((r) => r.relationName === name);
2165
1767
  if (relation) {
2166
1768
  relationProperty.relation = relation;
2167
1769
  } else {
2168
- console.warn(`Could not find relation for property with relationName: ${relationProperty.relationName}`);
1770
+ console.warn(`Could not find relation for property '${key}' with relationName: ${name}`);
2169
1771
  }
2170
1772
  }
2171
1773
  return newProperty;
@@ -2173,7 +1775,12 @@ class CollectionRegistry {
2173
1775
  get(path) {
2174
1776
  const bySlug = this.collectionsBySlug.get(path);
2175
1777
  if (bySlug) return bySlug;
2176
- return this.collectionsByDbPath.get(path);
1778
+ if (path.includes("-")) {
1779
+ const normalized = path.replace(/-/g, "_");
1780
+ const byNormalized = this.collectionsBySlug.get(normalized);
1781
+ if (byNormalized) return byNormalized;
1782
+ }
1783
+ return this.collectionsByTableName.get(path);
2177
1784
  }
2178
1785
  /**
2179
1786
  * Gets the pristine, un-normalized collection exactly as it was provided.
@@ -2182,7 +1789,12 @@ class CollectionRegistry {
2182
1789
  getRaw(path) {
2183
1790
  const bySlug = this.rawCollectionsBySlug.get(path);
2184
1791
  if (bySlug) return bySlug;
2185
- return this.rawCollectionsByDbPath.get(path);
1792
+ if (path.includes("-")) {
1793
+ const normalized = path.replace(/-/g, "_");
1794
+ const byNormalized = this.rawCollectionsBySlug.get(normalized);
1795
+ if (byNormalized) return byNormalized;
1796
+ }
1797
+ return this.rawCollectionsByTableName.get(path);
2186
1798
  }
2187
1799
  /**
2188
1800
  * Get collection by resolving multi-segment paths through relations
@@ -2203,10 +1815,13 @@ class CollectionRegistry {
2203
1815
  }
2204
1816
  for (let i = 2; i < pathSegments.length; i += 2) {
2205
1817
  const relationKey = pathSegments[i];
1818
+ if (!getDataSourceCapabilities(currentCollection.driver).supportsRelations) {
1819
+ throw new Error(`Relation path navigation requires a collection that supports relations, but '${currentCollection.slug}' uses driver '${currentCollection.driver}'`);
1820
+ }
2206
1821
  const resolvedRelations = resolveCollectionRelations(currentCollection);
2207
- const relation = resolvedRelations[relationKey];
1822
+ const relation = findRelation(resolvedRelations, relationKey);
2208
1823
  if (!relation) {
2209
- throw new Error(`Relation '${relationKey}' not found in collection '${currentCollection.slug || currentCollection.dbPath}'`);
1824
+ throw new Error(`Relation '${relationKey}' not found in collection '${currentCollection.slug}'`);
2210
1825
  }
2211
1826
  currentCollection = relation.target();
2212
1827
  if (i + 1 < pathSegments.length) ;
@@ -2214,10 +1829,16 @@ class CollectionRegistry {
2214
1829
  return currentCollection;
2215
1830
  }
2216
1831
  getCollections() {
2217
- return Array.from(this.collectionsByDbPath.values());
1832
+ if (!this.cachedCollectionsList) {
1833
+ this.cachedCollectionsList = Array.from(this.collectionsByTableName.values());
1834
+ }
1835
+ return this.cachedCollectionsList;
2218
1836
  }
2219
1837
  getRawCollections() {
2220
- return Array.from(this.rawCollectionsByDbPath.values());
1838
+ if (!this.cachedRawCollectionsList) {
1839
+ this.cachedRawCollectionsList = Array.from(this.rawCollectionsByTableName.values());
1840
+ }
1841
+ return this.cachedRawCollectionsList;
2221
1842
  }
2222
1843
  /**
2223
1844
  * Resolves a multi-segment path like "products/123/locales" and returns
@@ -2243,13 +1864,13 @@ class CollectionRegistry {
2243
1864
  entityIds.push(entityId);
2244
1865
  if (i + 1 < pathSegments.length) {
2245
1866
  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}`);
1867
+ const subcollections = getSubcollections(currentCollection);
1868
+ if (!subcollections || subcollections.length === 0) {
1869
+ throw new Error(`No subcollections found for ${currentCollection.slug} in path: ${path}`);
2249
1870
  }
2250
1871
  const subcollection = subcollections.find((c) => c.slug === subcollectionSlug);
2251
1872
  if (!subcollection) {
2252
- throw new Error(`Subcollection '${subcollectionSlug}' not found in ${currentCollection.slug || currentCollection.dbPath}`);
1873
+ throw new Error(`Subcollection '${subcollectionSlug}' not found in ${currentCollection.slug}`);
2253
1874
  }
2254
1875
  currentCollection = subcollection;
2255
1876
  collections.push(currentCollection);
@@ -2262,6 +1883,205 @@ class CollectionRegistry {
2262
1883
  };
2263
1884
  }
2264
1885
  }
1886
+ function convertWhereToFilter(where) {
1887
+ if (!where) return void 0;
1888
+ const operatorMap = {
1889
+ "eq": "==",
1890
+ "neq": "!=",
1891
+ "gt": ">",
1892
+ "gte": ">=",
1893
+ "lt": "<",
1894
+ "lte": "<=",
1895
+ "in": "in",
1896
+ "nin": "not-in",
1897
+ "not-in": "not-in",
1898
+ "cs": "array-contains",
1899
+ "csa": "array-contains-any",
1900
+ "==": "==",
1901
+ "!=": "!=",
1902
+ ">": ">",
1903
+ ">=": ">=",
1904
+ "<": "<",
1905
+ "<=": "<=",
1906
+ "array-contains": "array-contains",
1907
+ "array-contains-any": "array-contains-any"
1908
+ };
1909
+ const filter = {};
1910
+ for (const [field, rawValue] of Object.entries(where)) {
1911
+ if (rawValue === null) {
1912
+ filter[field] = ["==", null];
1913
+ continue;
1914
+ }
1915
+ if (typeof rawValue === "boolean") {
1916
+ filter[field] = ["==", rawValue];
1917
+ continue;
1918
+ }
1919
+ if (typeof rawValue === "number") {
1920
+ filter[field] = ["==", rawValue];
1921
+ continue;
1922
+ }
1923
+ if (Array.isArray(rawValue) && rawValue.length === 2) {
1924
+ const [rawOp, val] = rawValue;
1925
+ const mappedOp = operatorMap[rawOp] ?? "==";
1926
+ filter[field] = [mappedOp, val];
1927
+ continue;
1928
+ }
1929
+ if (typeof rawValue === "string") {
1930
+ const dotIndex = rawValue.indexOf(".");
1931
+ if (dotIndex === -1) {
1932
+ filter[field] = ["==", rawValue];
1933
+ continue;
1934
+ }
1935
+ const op = rawValue.substring(0, dotIndex);
1936
+ let value = rawValue.substring(dotIndex + 1);
1937
+ if (typeof value === "string" && value.startsWith("(") && value.endsWith(")")) {
1938
+ value = value.slice(1, -1).split(",").map((v) => v.trim());
1939
+ }
1940
+ if (value === "null") {
1941
+ value = null;
1942
+ } else if (value === "true") {
1943
+ value = true;
1944
+ } else if (value === "false") {
1945
+ value = false;
1946
+ } else if (typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "") {
1947
+ value = Number(value);
1948
+ }
1949
+ const mappedOp = operatorMap[op];
1950
+ if (mappedOp) {
1951
+ filter[field] = [mappedOp, value];
1952
+ }
1953
+ }
1954
+ }
1955
+ return Object.keys(filter).length > 0 ? filter : void 0;
1956
+ }
1957
+ function parseOrderBy(orderBy) {
1958
+ if (!orderBy) return void 0;
1959
+ const parts = orderBy.split(":");
1960
+ const field = parts[0];
1961
+ const direction = parts[1] || "asc";
1962
+ return [field, direction];
1963
+ }
1964
+ function createDriverAccessor(driver, slug) {
1965
+ return {
1966
+ async find(params) {
1967
+ const orderParsed = parseOrderBy(params?.orderBy);
1968
+ const entities = await driver.fetchCollection({
1969
+ path: slug,
1970
+ limit: params?.limit,
1971
+ offset: params?.offset,
1972
+ filter: convertWhereToFilter(params?.where),
1973
+ orderBy: orderParsed?.[0],
1974
+ order: orderParsed?.[1],
1975
+ searchString: params?.searchString
1976
+ });
1977
+ const limit = params?.limit ?? 20;
1978
+ const offset = params?.offset ?? 0;
1979
+ return {
1980
+ data: entities,
1981
+ meta: {
1982
+ total: entities.length,
1983
+ limit,
1984
+ offset,
1985
+ hasMore: entities.length >= limit
1986
+ }
1987
+ };
1988
+ },
1989
+ async findById(id) {
1990
+ return driver.fetchEntity({
1991
+ path: slug,
1992
+ entityId: id
1993
+ });
1994
+ },
1995
+ async create(data, id) {
1996
+ return driver.saveEntity({
1997
+ path: slug,
1998
+ values: data,
1999
+ entityId: id,
2000
+ status: "new"
2001
+ });
2002
+ },
2003
+ async update(id, data) {
2004
+ return driver.saveEntity({
2005
+ path: slug,
2006
+ values: data,
2007
+ entityId: id,
2008
+ status: "existing"
2009
+ });
2010
+ },
2011
+ async delete(id) {
2012
+ return driver.deleteEntity({
2013
+ entity: {
2014
+ id,
2015
+ path: slug,
2016
+ values: {}
2017
+ }
2018
+ });
2019
+ },
2020
+ count: driver.countEntities ? async (params) => {
2021
+ return driver.countEntities({
2022
+ path: slug,
2023
+ filter: convertWhereToFilter(params?.where)
2024
+ });
2025
+ } : void 0,
2026
+ listen: driver.listenCollection ? (params, onUpdate, onError) => {
2027
+ const orderParsed = parseOrderBy(params?.orderBy);
2028
+ const limit = params?.limit ?? 20;
2029
+ const offset = params?.offset ?? 0;
2030
+ return driver.listenCollection({
2031
+ path: slug,
2032
+ limit: params?.limit,
2033
+ offset: params?.offset,
2034
+ filter: convertWhereToFilter(params?.where),
2035
+ orderBy: orderParsed?.[0],
2036
+ order: orderParsed?.[1],
2037
+ searchString: params?.searchString,
2038
+ onUpdate: (entities) => {
2039
+ onUpdate({
2040
+ data: entities,
2041
+ meta: {
2042
+ total: entities.length,
2043
+ limit,
2044
+ offset,
2045
+ hasMore: entities.length >= limit
2046
+ }
2047
+ });
2048
+ },
2049
+ onError
2050
+ });
2051
+ } : void 0,
2052
+ listenById: driver.listenEntity ? (id, onUpdate, onError) => {
2053
+ return driver.listenEntity({
2054
+ path: slug,
2055
+ entityId: id,
2056
+ onUpdate: (entity) => onUpdate(entity ?? void 0),
2057
+ onError
2058
+ });
2059
+ } : void 0
2060
+ };
2061
+ }
2062
+ function buildRebaseData(driver) {
2063
+ const cache = /* @__PURE__ */ new Map();
2064
+ function getAccessor(slug) {
2065
+ let accessor = cache.get(slug);
2066
+ if (!accessor) {
2067
+ accessor = createDriverAccessor(driver, slug);
2068
+ cache.set(slug, accessor);
2069
+ }
2070
+ return accessor;
2071
+ }
2072
+ const target = {
2073
+ collection: getAccessor
2074
+ };
2075
+ return new Proxy(target, {
2076
+ get(_target, prop) {
2077
+ if (prop === "collection") return getAccessor;
2078
+ if (typeof prop === "symbol") return void 0;
2079
+ if (prop === "then" || prop === "toJSON" || prop === "$$typeof") return void 0;
2080
+ const slug = toSnakeCase(prop);
2081
+ return getAccessor(slug);
2082
+ }
2083
+ });
2084
+ }
2265
2085
  export {
2266
2086
  COLLECTION_PATH_SEPARATOR,
2267
2087
  CollectionRegistry,
@@ -2275,23 +2095,22 @@ export {
2275
2095
  buildEntityCallbacks,
2276
2096
  buildEnum,
2277
2097
  buildEnumValueConfig,
2278
- buildFieldConfig,
2279
2098
  buildProperties,
2280
2099
  buildPropertiesOrBuilder,
2281
2100
  buildProperty,
2282
2101
  buildPropertyCallbacks,
2102
+ buildRebaseData,
2283
2103
  canCreateEntity,
2284
2104
  canDeleteEntity,
2285
2105
  canEditEntity,
2286
2106
  canReadCollection,
2287
- clone,
2288
- defaultDateFormat,
2107
+ createRelationRef,
2108
+ createRelationRefWithData,
2289
2109
  enumToObjectEntries,
2290
2110
  evaluateCondition,
2291
- flattenObject,
2111
+ findRelation,
2292
2112
  fullPathToCollectionSegments,
2293
2113
  getArrayResolvedProperties,
2294
- getArrayValuesCount,
2295
2114
  getCollectionBySlugWithin,
2296
2115
  getCollectionPathsCombinations,
2297
2116
  getColumnName,
@@ -2300,8 +2119,6 @@ export {
2300
2119
  getDefaultValuesFor,
2301
2120
  getEntityImagePreviewPropertyKey,
2302
2121
  getEnumVarName,
2303
- getHashValue,
2304
- getIn$1 as getIn,
2305
2122
  getLabelOrConfigFrom,
2306
2123
  getLastSegment,
2307
2124
  getLocalChangesBackup,
@@ -2313,44 +2130,18 @@ export {
2313
2130
  getSubcollections,
2314
2131
  getTableName,
2315
2132
  getTableVarName,
2316
- getValueInPath,
2317
- hashString,
2318
- hydrateRegExp,
2319
- isDefaultFieldConfigId,
2320
- isEmptyArray,
2321
- isEmptyObject,
2322
- isFunction,
2323
2133
  isHidden,
2324
- isInteger,
2325
- isNaN$1 as isNaN,
2326
- isObject,
2327
- isPlainObject,
2328
2134
  isPropertyBuilder,
2329
2135
  isReadOnly,
2330
- isValidRegExp,
2331
- mergeDeep,
2332
- mergeEntityActions,
2333
- navigateToEntity,
2334
- pick,
2335
- plural,
2336
- prettifyIdentifier,
2337
- randomColor,
2338
- randomString,
2136
+ normalizeToEntityRelation,
2339
2137
  registerConditionOperations,
2340
- removeFunctions,
2341
- removeInPath,
2342
2138
  removeInitialAndTrailingSlashes,
2343
2139
  removeInitialSlash,
2344
- removeNulls,
2345
- removePropsIfExisting,
2346
2140
  removeTrailingSlash,
2347
- removeUndefined,
2348
2141
  resolveArrayProperties,
2349
2142
  resolveCollectionPathIds,
2350
2143
  resolveCollectionRelations,
2351
2144
  resolveDefaultSelectedView,
2352
- resolveEntityAction,
2353
- resolveEntityView$1 as resolveEntityView,
2354
2145
  resolveEnumValues,
2355
2146
  resolveProperties,
2356
2147
  resolveProperty,
@@ -2359,22 +2150,13 @@ export {
2359
2150
  resolveRelationProperty,
2360
2151
  resolveStorageFilenameString,
2361
2152
  resolveStoragePathString,
2362
- resolvedSelectedEntityView,
2363
2153
  sanitizeData,
2364
2154
  sanitizeRelation,
2365
2155
  segmentsToStrippedPath,
2366
- serializeRegExp,
2367
- setIn,
2368
- singular,
2369
- slugify,
2370
2156
  sortProperties,
2371
2157
  stripCollectionPath,
2372
- toArray,
2373
- toKebabCase,
2374
- toSnakeCase,
2375
2158
  traverseValueProperty,
2376
2159
  traverseValuesProperties,
2377
- unslugify,
2378
2160
  updateDateAutoValues
2379
2161
  };
2380
2162
  //# sourceMappingURL=index.es.js.map