@rebasepro/admin 0.6.1 → 0.8.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.
Files changed (187) hide show
  1. package/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-BO_aQ-Z4.js} +236 -155
  2. package/dist/CollectionEditorDialog-BO_aQ-Z4.js.map +1 -0
  3. package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-DKzOpMjR.js} +190 -50
  4. package/dist/PropertyEditView-DKzOpMjR.js.map +1 -0
  5. package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-BQTIExjA.js} +61 -39
  6. package/dist/RouterCollectionsStudioView-BQTIExjA.js.map +1 -0
  7. package/dist/collection_editor/_cms_internals.d.ts +1 -1
  8. package/dist/collection_editor/api/generateCollectionApi.d.ts +3 -2
  9. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  10. package/dist/collection_editor/index.d.ts +6 -0
  11. package/dist/collection_editor/serializable_types.d.ts +370 -0
  12. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  13. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +3 -3
  14. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  15. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  16. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  17. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  18. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  19. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  20. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  21. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  22. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  23. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  24. package/dist/collection_editor_ui.d.ts +4 -0
  25. package/dist/collection_editor_ui.js +33 -4
  26. package/dist/collection_editor_ui.js.map +1 -0
  27. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  28. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  29. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  30. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  31. package/dist/components/EntityEditView.d.ts +3 -2
  32. package/dist/components/RebaseCMS.d.ts +1 -1
  33. package/dist/components/SelectableTable/SelectionStore.d.ts +12 -21
  34. package/dist/components/field_configs.d.ts +1 -1
  35. package/dist/editor/editor.d.ts +0 -6
  36. package/dist/editor.js +1 -3
  37. package/dist/editor.js.map +1 -1
  38. package/dist/form/EntityForm.d.ts +10 -9
  39. package/dist/form/EntityFormBinding.d.ts +27 -0
  40. package/dist/form/components/StorageUploadProgress.d.ts +6 -1
  41. package/dist/form/form_utils.d.ts +8 -0
  42. package/dist/form/index.d.ts +5 -2
  43. package/dist/{history-BO6VT85a.js → history-B3cqBzEV.js} +2 -2
  44. package/dist/{history-BO6VT85a.js.map → history-B3cqBzEV.js.map} +1 -1
  45. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  46. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  47. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  48. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  49. package/dist/hooks/navigation/utils.d.ts +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +2 -2
  52. package/dist/preview/components/StorageThumbnail.d.ts +3 -1
  53. package/dist/routes/CustomViewRoute.d.ts +2 -1
  54. package/dist/{src-BYniefVu.js → src-cxV1ODRG.js} +1731 -1710
  55. package/dist/src-cxV1ODRG.js.map +1 -0
  56. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  57. package/dist/types/components/EntityFormProps.d.ts +59 -0
  58. package/dist/types/components/PropertyPreviewProps.d.ts +2 -1
  59. package/dist/types/fields.d.ts +10 -7
  60. package/dist/util/dataControllerAdapter.d.ts +30 -0
  61. package/dist/util/navigation_utils.d.ts +1 -1
  62. package/dist/util/propertyConfigMapper.d.ts +17 -0
  63. package/package.json +8 -8
  64. package/src/collection_editor/_cms_internals.ts +1 -1
  65. package/src/collection_editor/api/generateCollectionApi.ts +3 -2
  66. package/src/collection_editor/extensibility_types.ts +168 -0
  67. package/src/collection_editor/index.ts +46 -0
  68. package/src/collection_editor/pgColumnToProperty.ts +2 -2
  69. package/src/collection_editor/serializable_types.ts +474 -0
  70. package/src/collection_editor/serializable_utils.ts +576 -0
  71. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +6 -6
  72. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +170 -89
  73. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  74. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  75. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  76. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  77. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  78. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  79. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +154 -18
  80. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  81. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  82. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  83. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  84. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  85. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +35 -1
  86. package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +1 -1
  87. package/src/collection_editor/ui/collection_editor/utils/supported_fields.tsx +0 -1
  88. package/src/collection_editor/ui/collection_editor/utils/update_property_for_widget.ts +7 -29
  89. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  90. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  91. package/src/collection_editor_ui.ts +6 -0
  92. package/src/components/DefaultDrawer.tsx +14 -4
  93. package/src/components/DrawerNavigationGroup.tsx +2 -2
  94. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +11 -9
  95. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +2 -1
  96. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +2 -1
  97. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +23 -20
  98. package/src/components/EntityCollectionTable/index.tsx +1 -1
  99. package/src/components/EntityCollectionTable/table_bindings.tsx +24 -44
  100. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  101. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +13 -12
  102. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  103. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +66 -184
  104. package/src/components/EntityCollectionView/EntityCollectionView.tsx +8 -25
  105. package/src/components/EntityCollectionView/hooks/useCollectionInlineEditor.ts +1 -1
  106. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  107. package/src/components/EntityCollectionView/useBoardDataController.tsx +12 -40
  108. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +3 -3
  109. package/src/components/EntityDetailView.tsx +2 -2
  110. package/src/components/EntityEditView.tsx +7 -6
  111. package/src/components/EntityEditViewFormActions.tsx +35 -29
  112. package/src/components/HomePage/ContentHomePage.tsx +14 -5
  113. package/src/components/RebaseCMS.tsx +3 -3
  114. package/src/components/RebaseNavigation.tsx +74 -9
  115. package/src/components/RelationSelector.tsx +2 -2
  116. package/src/components/SelectableTable/SelectableTable.tsx +13 -5
  117. package/src/components/SelectableTable/SelectionStore.ts +18 -48
  118. package/src/components/SideDialogs.tsx +0 -7
  119. package/src/components/common/default_entity_actions.tsx +22 -11
  120. package/src/components/field_configs.tsx +0 -14
  121. package/src/contexts/BreacrumbsContext.tsx +15 -6
  122. package/src/editor/editor.tsx +4 -7
  123. package/src/editor/selectors/ai-selector.tsx +0 -1
  124. package/src/form/EntityForm.tsx +191 -428
  125. package/src/form/EntityFormActions.tsx +35 -49
  126. package/src/form/EntityFormBinding.tsx +323 -0
  127. package/src/form/components/FormLayout.tsx +1 -1
  128. package/src/form/components/StorageUploadProgress.tsx +9 -2
  129. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +12 -1
  130. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +4 -4
  131. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  132. package/src/form/field_bindings/RelationFieldBinding.tsx +5 -7
  133. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +19 -15
  134. package/src/form/form_utils.ts +163 -0
  135. package/src/form/index.tsx +14 -6
  136. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  137. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  138. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  139. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  140. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  141. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  142. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  143. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  144. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  145. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  146. package/src/hooks/navigation/utils.ts +7 -4
  147. package/src/index.ts +6 -1
  148. package/src/preview/PropertyPreview.tsx +1 -16
  149. package/src/preview/components/StorageThumbnail.tsx +24 -7
  150. package/src/routes/CustomViewRoute.tsx +14 -6
  151. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  152. package/src/types/components/EntityFormProps.tsx +68 -0
  153. package/src/types/components/PropertyPreviewProps.tsx +1 -1
  154. package/src/types/fields.tsx +9 -8
  155. package/src/util/dataControllerAdapter.ts +93 -0
  156. package/src/util/navigation_utils.ts +1 -1
  157. package/src/util/previews.ts +20 -0
  158. package/src/util/propertyConfigMapper.ts +134 -0
  159. package/src/util/property_utils.tsx +1 -2
  160. package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
  161. package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
  162. package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
  163. package/dist/components/EntityCollectionTable/fields/VirtualTableDateField.d.ts +0 -12
  164. package/dist/components/EntityCollectionTable/fields/VirtualTableInput.d.ts +0 -10
  165. package/dist/components/EntityCollectionTable/fields/VirtualTableNumberInput.d.ts +0 -9
  166. package/dist/components/EntityCollectionTable/fields/VirtualTableSwitch.d.ts +0 -8
  167. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  168. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  169. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  170. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  171. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  172. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +0 -11
  173. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  174. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  175. package/dist/src-BYniefVu.js.map +0 -1
  176. package/src/components/EntityCollectionTable/fields/VirtualTableDateField.tsx +0 -39
  177. package/src/components/EntityCollectionTable/fields/VirtualTableInput.tsx +0 -90
  178. package/src/components/EntityCollectionTable/fields/VirtualTableNumberInput.tsx +0 -83
  179. package/src/components/EntityCollectionTable/fields/VirtualTableSwitch.tsx +0 -32
  180. package/src/components/EntityCollectionView/Board.tsx +0 -475
  181. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  182. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  183. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  184. package/src/components/EntityCollectionView/board_types.ts +0 -113
  185. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +0 -141
  186. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  187. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -0,0 +1,576 @@
1
+ /**
2
+ * Conversion utilities between the full Rebase types and their
3
+ * JSON-serializable counterparts.
4
+ *
5
+ * - `toSerializableCollection` / `toSerializableProperty` — strip non-serializable fields
6
+ * - `fromSerializableCollection` / `fromSerializableProperty` — reconstruct original types
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import type {
12
+ ArrayProperty,
13
+ BaseUIConfig,
14
+ BinaryProperty,
15
+ BooleanProperty,
16
+ DateProperty,
17
+ EntityCollection,
18
+ GeopointProperty,
19
+ MapProperty,
20
+ NumberProperty,
21
+ Properties,
22
+ Property,
23
+ ReferenceProperty,
24
+ RelationProperty,
25
+ StorageConfig,
26
+ StringProperty,
27
+ VectorProperty,
28
+ } from "@rebasepro/types";
29
+
30
+ import type {
31
+ SerializableArrayProperty,
32
+ SerializableBaseProperty,
33
+ SerializableBaseUIConfig,
34
+ SerializableBinaryProperty,
35
+ SerializableBooleanProperty,
36
+ SerializableCollection,
37
+ SerializableDateProperty,
38
+ SerializableDateValidation,
39
+ SerializableGeopointProperty,
40
+ SerializableMapProperty,
41
+ SerializableNumberProperty,
42
+ SerializableProperties,
43
+ SerializableProperty,
44
+ SerializableReferenceProperty,
45
+ SerializableRelationProperty,
46
+ SerializableStorageConfig,
47
+ SerializableStringProperty,
48
+ SerializableStringValidation,
49
+ SerializableVectorProperty,
50
+ } from "./serializable_types";
51
+
52
+ // ═══════════════════════════════════════════════════════════════════════
53
+ // PROPERTY CONVERSION: Original → Serializable
54
+ // ═══════════════════════════════════════════════════════════════════════
55
+
56
+ /**
57
+ * Strip non-serializable fields from a `BaseUIConfig`.
58
+ * Removes `Field` and `Preview` (ComponentRef).
59
+ */
60
+ function toSerializableUIConfig(ui: BaseUIConfig | undefined): SerializableBaseUIConfig | undefined {
61
+ if (!ui) return undefined;
62
+ const { Field, Preview, ...rest } = ui as BaseUIConfig & Record<string, unknown>;
63
+ // Only return if there are remaining fields
64
+ const result: Record<string, unknown> = {};
65
+ for (const [key, value] of Object.entries(rest)) {
66
+ if (key !== "Field" && key !== "Preview" && value !== undefined) {
67
+ result[key] = value;
68
+ }
69
+ }
70
+ return Object.keys(result).length > 0 ? result as SerializableBaseUIConfig : undefined;
71
+ }
72
+
73
+ /**
74
+ * Convert a `StorageConfig` to its serializable form.
75
+ * Drops function variants of `storagePath`, `fileName`, and all function fields.
76
+ */
77
+ function toSerializableStorageConfig(storage: StorageConfig | undefined): SerializableStorageConfig | undefined {
78
+ if (!storage) return undefined;
79
+
80
+ // storagePath must be a string for serialization
81
+ const storagePath = typeof storage.storagePath === "string" ? storage.storagePath : undefined;
82
+ if (!storagePath) return undefined;
83
+
84
+ const result: SerializableStorageConfig = { storagePath };
85
+
86
+ if (storage.acceptedFiles) result.acceptedFiles = storage.acceptedFiles;
87
+ if (storage.imageResize) result.imageResize = storage.imageResize;
88
+ if (storage.metadata) result.metadata = storage.metadata;
89
+ if (typeof storage.fileName === "string") result.fileName = storage.fileName;
90
+ if (storage.includeBucketUrl !== undefined) result.includeBucketUrl = storage.includeBucketUrl;
91
+ if (storage.storeUrl !== undefined) result.storeUrl = storage.storeUrl;
92
+ if (storage.maxSize !== undefined) result.maxSize = storage.maxSize;
93
+ if (storage.storageSource !== undefined) result.storageSource = storage.storageSource;
94
+ // processFile, postProcess, previewUrl are intentionally dropped (functions)
95
+
96
+ return result;
97
+ }
98
+
99
+ /**
100
+ * Convert a RegExp or string `matches` validation to a plain string.
101
+ */
102
+ function serializeMatches(matches: string | RegExp | undefined): string | undefined {
103
+ if (matches === undefined) return undefined;
104
+ if (typeof matches === "string") return matches;
105
+ return matches.source;
106
+ }
107
+
108
+ /**
109
+ * Resolve a relation target to a string slug.
110
+ * Functions are called to extract the target; EntityCollection objects
111
+ * use their slug.
112
+ */
113
+ function resolveRelationTarget(target: RelationProperty["target"]): string | undefined {
114
+ if (!target) return undefined;
115
+ if (typeof target === "string") return target;
116
+ if (typeof target === "function") {
117
+ try {
118
+ const resolved = target();
119
+ if (typeof resolved === "string") return resolved;
120
+ if (resolved && typeof resolved === "object" && "slug" in resolved) {
121
+ return (resolved as EntityCollection).slug;
122
+ }
123
+ } catch {
124
+ // If the lazy resolver throws (e.g., circular dependency not yet ready), return undefined
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+
130
+ /**
131
+ * Strip non-serializable base property fields.
132
+ * Returns a clean object with only the common serializable fields from BaseProperty.
133
+ * Validation is intentionally excluded — each property type handles its own.
134
+ */
135
+ function toSerializableBaseFields(property: Property): Omit<SerializableBaseProperty, "validation"> {
136
+ const result: Partial<Omit<SerializableBaseProperty, "validation">> = {
137
+ name: property.name,
138
+ };
139
+
140
+ if (property.description) result.description = property.description;
141
+ if (property.propertyConfig) result.propertyConfig = property.propertyConfig;
142
+ if (property.columnName) result.columnName = property.columnName;
143
+ if (property.defaultValue !== undefined) result.defaultValue = property.defaultValue;
144
+ if (property.conditions) result.conditions = property.conditions;
145
+ if (property.metadata) result.metadata = property.metadata;
146
+ // dynamicProps and callbacks are intentionally dropped (functions)
147
+
148
+ const ui = toSerializableUIConfig(property.ui);
149
+ if (ui) result.ui = ui;
150
+
151
+ return result as Omit<SerializableBaseProperty, "validation">;
152
+ }
153
+
154
+ /**
155
+ * Convert a single `Property` to its JSON-serializable form.
156
+ * Strips functions, converts RegExp to string, Date to ISO string.
157
+ */
158
+ export function toSerializableProperty(property: Property): SerializableProperty {
159
+ const base = toSerializableBaseFields(property);
160
+
161
+ switch (property.type) {
162
+ case "string": {
163
+ const sp = property as StringProperty;
164
+ const result: SerializableStringProperty = {
165
+ ...base,
166
+ type: "string",
167
+ };
168
+ if (sp.columnType) result.columnType = sp.columnType;
169
+ if (sp.isId !== undefined) result.isId = sp.isId;
170
+ if (sp.enum) result.enum = sp.enum;
171
+ if (sp.userSelect) result.userSelect = sp.userSelect;
172
+ if (sp.email) result.email = sp.email;
173
+
174
+ // Convert validation.matches from RegExp to string
175
+ if (sp.validation) {
176
+ const { matches, ...restValidation } = sp.validation;
177
+ const serializedValidation: SerializableStringValidation = { ...restValidation };
178
+ const matchStr = serializeMatches(matches);
179
+ if (matchStr !== undefined) serializedValidation.matches = matchStr;
180
+ result.validation = serializedValidation;
181
+ }
182
+
183
+ // Storage: convert to serializable form
184
+ const storage = toSerializableStorageConfig(sp.storage);
185
+ if (storage) result.storage = storage;
186
+
187
+ return result;
188
+ }
189
+
190
+ case "number": {
191
+ const np = property as NumberProperty;
192
+ const result: SerializableNumberProperty = {
193
+ ...base,
194
+ type: "number",
195
+ };
196
+ if (np.columnType) result.columnType = np.columnType;
197
+ if (np.validation) result.validation = np.validation;
198
+ if (np.isId !== undefined) result.isId = np.isId;
199
+ if (np.enum) result.enum = np.enum;
200
+ return result;
201
+ }
202
+
203
+ case "boolean": {
204
+ const bp = property as BooleanProperty;
205
+ const result: SerializableBooleanProperty = {
206
+ ...base,
207
+ type: "boolean",
208
+ };
209
+ if (bp.validation) result.validation = bp.validation;
210
+ return result;
211
+ }
212
+
213
+ case "date": {
214
+ const dp = property as DateProperty;
215
+ const result: SerializableDateProperty = {
216
+ ...base,
217
+ type: "date",
218
+ };
219
+ if (dp.columnType) result.columnType = dp.columnType;
220
+ if (dp.mode) result.mode = dp.mode;
221
+ if (dp.timezone) result.timezone = dp.timezone;
222
+ if (dp.autoValue) result.autoValue = dp.autoValue;
223
+
224
+ // Convert Date validation min/max to ISO strings
225
+ if (dp.validation) {
226
+ const serializedValidation: SerializableDateValidation = {};
227
+ if (dp.validation.required !== undefined) serializedValidation.required = dp.validation.required;
228
+ if (dp.validation.requiredMessage) serializedValidation.requiredMessage = dp.validation.requiredMessage;
229
+ if (dp.validation.unique) serializedValidation.unique = dp.validation.unique;
230
+ if (dp.validation.uniqueInArray) serializedValidation.uniqueInArray = dp.validation.uniqueInArray;
231
+ if (dp.validation.min instanceof Date) {
232
+ serializedValidation.min = dp.validation.min.toISOString();
233
+ } else if (typeof dp.validation.min === "string") {
234
+ serializedValidation.min = dp.validation.min;
235
+ }
236
+ if (dp.validation.max instanceof Date) {
237
+ serializedValidation.max = dp.validation.max.toISOString();
238
+ } else if (typeof dp.validation.max === "string") {
239
+ serializedValidation.max = dp.validation.max;
240
+ }
241
+ result.validation = serializedValidation;
242
+ }
243
+ return result;
244
+ }
245
+
246
+ case "geopoint": {
247
+ const gp = property as GeopointProperty;
248
+ const result: SerializableGeopointProperty = {
249
+ ...base,
250
+ type: "geopoint",
251
+ };
252
+ if (gp.validation) result.validation = gp.validation;
253
+ return result;
254
+ }
255
+
256
+ case "reference": {
257
+ const rp = property as ReferenceProperty;
258
+ const result: SerializableReferenceProperty = {
259
+ ...base,
260
+ type: "reference",
261
+ };
262
+ if (rp.isId !== undefined) result.isId = rp.isId;
263
+ if (rp.path) result.path = rp.path;
264
+ if (rp.fixedFilter) result.fixedFilter = rp.fixedFilter;
265
+ if (rp.includeId !== undefined) result.includeId = rp.includeId;
266
+ if (rp.includeEntityLink !== undefined) result.includeEntityLink = rp.includeEntityLink;
267
+ return result;
268
+ }
269
+
270
+ case "relation": {
271
+ const rl = property as RelationProperty;
272
+ const result: SerializableRelationProperty = {
273
+ ...base,
274
+ type: "relation",
275
+ };
276
+ if (rl.isId !== undefined) result.isId = rl.isId;
277
+ // Resolve target to string
278
+ const target = resolveRelationTarget(rl.target);
279
+ if (target) result.target = target;
280
+ if (rl.cardinality) result.cardinality = rl.cardinality;
281
+ if (rl.direction) result.direction = rl.direction;
282
+ if (rl.inverseRelationName) result.inverseRelationName = rl.inverseRelationName;
283
+ if (rl.localKey) result.localKey = rl.localKey;
284
+ if (rl.foreignKeyOnTarget) result.foreignKeyOnTarget = rl.foreignKeyOnTarget;
285
+ if (rl.through) result.through = rl.through;
286
+ if (rl.joinPath) result.joinPath = rl.joinPath;
287
+ if (rl.onUpdate) result.onUpdate = rl.onUpdate;
288
+ if (rl.onDelete) result.onDelete = rl.onDelete;
289
+ if (rl.relationName) result.relationName = rl.relationName;
290
+ if (rl.fixedFilter) result.fixedFilter = rl.fixedFilter;
291
+ if (rl.includeId !== undefined) result.includeId = rl.includeId;
292
+ if (rl.includeEntityLink !== undefined) result.includeEntityLink = rl.includeEntityLink;
293
+ if (rl.widget) result.widget = rl.widget;
294
+ // overrides and relation (resolved) are dropped
295
+ return result;
296
+ }
297
+
298
+ case "array": {
299
+ const ap = property as ArrayProperty;
300
+ const result: SerializableArrayProperty = {
301
+ ...base,
302
+ type: "array",
303
+ };
304
+ if (ap.columnType) result.columnType = ap.columnType;
305
+ if (ap.validation) result.validation = ap.validation;
306
+ if (ap.sortable !== undefined) result.sortable = ap.sortable;
307
+ if (ap.canAddElements !== undefined) result.canAddElements = ap.canAddElements;
308
+
309
+ // Recursively serialize the "of" property
310
+ if (ap.of) {
311
+ if (Array.isArray(ap.of)) {
312
+ result.of = ap.of.map(toSerializableProperty);
313
+ } else {
314
+ result.of = toSerializableProperty(ap.of);
315
+ }
316
+ }
317
+
318
+ // Recursively serialize oneOf properties
319
+ if (ap.oneOf) {
320
+ result.oneOf = {
321
+ properties: toSerializableProperties(ap.oneOf.properties),
322
+ };
323
+ if (ap.oneOf.propertiesOrder) result.oneOf.propertiesOrder = ap.oneOf.propertiesOrder;
324
+ if (ap.oneOf.typeField) result.oneOf.typeField = ap.oneOf.typeField;
325
+ if (ap.oneOf.valueField) result.oneOf.valueField = ap.oneOf.valueField;
326
+ }
327
+ return result;
328
+ }
329
+
330
+ case "map": {
331
+ const mp = property as MapProperty;
332
+ const result: SerializableMapProperty = {
333
+ ...base,
334
+ type: "map",
335
+ };
336
+ if (mp.columnType) result.columnType = mp.columnType;
337
+ if (mp.validation) result.validation = mp.validation;
338
+ if (mp.propertiesOrder) result.propertiesOrder = mp.propertiesOrder;
339
+ if (mp.previewProperties) result.previewProperties = mp.previewProperties;
340
+ if (mp.keyValue) result.keyValue = mp.keyValue;
341
+
342
+ // Recursively serialize nested properties
343
+ if (mp.properties) {
344
+ result.properties = toSerializableProperties(mp.properties);
345
+ }
346
+ return result;
347
+ }
348
+
349
+ case "vector": {
350
+ const vp = property as VectorProperty;
351
+ const result: SerializableVectorProperty = {
352
+ ...base,
353
+ type: "vector",
354
+ dimensions: vp.dimensions,
355
+ };
356
+ if (vp.validation) result.validation = vp.validation;
357
+ return result;
358
+ }
359
+
360
+ case "binary": {
361
+ const bp = property as BinaryProperty;
362
+ const result: SerializableBinaryProperty = {
363
+ ...base,
364
+ type: "binary",
365
+ };
366
+ if (bp.validation) result.validation = bp.validation;
367
+ return result;
368
+ }
369
+
370
+ default:
371
+ // Exhaustive check: if new types are added, this will produce a compile error
372
+ return base as SerializableProperty;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Convert a `Properties` record to its serializable form.
378
+ */
379
+ export function toSerializableProperties(properties: Properties): SerializableProperties {
380
+ const result: SerializableProperties = {};
381
+ for (const [key, property] of Object.entries(properties)) {
382
+ result[key] = toSerializableProperty(property);
383
+ }
384
+ return result;
385
+ }
386
+
387
+ // ═══════════════════════════════════════════════════════════════════════
388
+ // COLLECTION CONVERSION: Original → Serializable
389
+ // ═══════════════════════════════════════════════════════════════════════
390
+
391
+ /**
392
+ * Convert an `EntityCollection` to its JSON-serializable form.
393
+ *
394
+ * Strips all non-serializable fields (functions, React nodes, class instances)
395
+ * while preserving the structural schema that the collection editor works with.
396
+ *
397
+ * The result is safe for `JSON.stringify()` and database storage.
398
+ */
399
+ export function toSerializableCollection(collection: EntityCollection): SerializableCollection {
400
+ const result: SerializableCollection = {
401
+ slug: collection.slug,
402
+ name: collection.name,
403
+ properties: toSerializableProperties(collection.properties),
404
+ };
405
+
406
+ // String fields
407
+ if (collection.singularName) result.singularName = collection.singularName;
408
+ if (collection.description) result.description = collection.description;
409
+ if (collection.group) result.group = collection.group;
410
+ if (collection.engine) result.engine = collection.engine;
411
+ if (collection.dataSource) result.dataSource = collection.dataSource;
412
+ if (collection.databaseId) result.databaseId = collection.databaseId;
413
+ if (collection.titleProperty) result.titleProperty = collection.titleProperty as string;
414
+ if (collection.ownerId) result.ownerId = collection.ownerId;
415
+ if (collection.metadata) result.metadata = collection.metadata;
416
+ if (collection.table) result.table = collection.table;
417
+ if ("schema" in collection && collection.schema) result.schema = collection.schema as string;
418
+ if (collection.orderProperty) result.orderProperty = collection.orderProperty as string;
419
+
420
+ // Icon: only keep string variant
421
+ if (typeof collection.icon === "string") result.icon = collection.icon;
422
+
423
+ // Array fields
424
+ if (collection.propertiesOrder) result.propertiesOrder = collection.propertiesOrder as string[];
425
+ if (collection.previewProperties) result.previewProperties = collection.previewProperties;
426
+ if (collection.listProperties) result.listProperties = collection.listProperties;
427
+ if (collection.enabledViews) result.enabledViews = collection.enabledViews;
428
+ if (collection.disableDefaultActions) result.disableDefaultActions = collection.disableDefaultActions;
429
+ if (collection.securityRules) result.securityRules = collection.securityRules;
430
+
431
+ // Enum-like fields
432
+ if (collection.openEntityMode) result.openEntityMode = collection.openEntityMode;
433
+ if (collection.defaultEntityAction) result.defaultEntityAction = collection.defaultEntityAction;
434
+ if (collection.defaultViewMode) result.defaultViewMode = collection.defaultViewMode;
435
+ if (collection.defaultSize) result.defaultSize = collection.defaultSize;
436
+ if (collection.localChangesBackup !== undefined) result.localChangesBackup = collection.localChangesBackup;
437
+
438
+ // Kanban
439
+ if (collection.kanban) result.kanban = collection.kanban as SerializableCollection["kanban"];
440
+
441
+ // Filters and sorting
442
+ if (collection.fixedFilter) result.fixedFilter = collection.fixedFilter as SerializableCollection["fixedFilter"];
443
+ if (collection.defaultFilter) result.defaultFilter = collection.defaultFilter as SerializableCollection["defaultFilter"];
444
+ if (collection.filterPresets) result.filterPresets = collection.filterPresets as SerializableCollection["filterPresets"];
445
+ if (collection.sort) result.sort = collection.sort as SerializableCollection["sort"];
446
+
447
+ // Numeric / boolean flags
448
+ if (collection.pagination !== undefined) result.pagination = collection.pagination;
449
+ if (collection.selectionEnabled !== undefined) result.selectionEnabled = collection.selectionEnabled;
450
+ if (collection.inlineEditing !== undefined) result.inlineEditing = collection.inlineEditing;
451
+ if (collection.hideFromNavigation !== undefined) result.hideFromNavigation = collection.hideFromNavigation;
452
+ if (collection.hideIdFromForm !== undefined) result.hideIdFromForm = collection.hideIdFromForm;
453
+ if (collection.hideIdFromCollection !== undefined) result.hideIdFromCollection = collection.hideIdFromCollection;
454
+ if (collection.formAutoSave !== undefined) result.formAutoSave = collection.formAutoSave;
455
+ if (collection.alwaysApplyDefaultValues !== undefined) result.alwaysApplyDefaultValues = collection.alwaysApplyDefaultValues;
456
+ if (collection.includeJsonView !== undefined) result.includeJsonView = collection.includeJsonView;
457
+ if (collection.history !== undefined) result.history = collection.history;
458
+ if (collection.sideDialogWidth !== undefined) result.sideDialogWidth = collection.sideDialogWidth;
459
+
460
+ // Exportable: only keep boolean variant
461
+ if (typeof collection.exportable === "boolean") result.exportable = collection.exportable;
462
+
463
+ // Auth: only keep boolean variant
464
+ if (typeof collection.auth === "boolean") result.auth = collection.auth;
465
+
466
+ // defaultSelectedView: only keep string variant
467
+ if (typeof collection.defaultSelectedView === "string") result.defaultSelectedView = collection.defaultSelectedView;
468
+
469
+ return result;
470
+ }
471
+
472
+ // ═══════════════════════════════════════════════════════════════════════
473
+ // PROPERTY CONVERSION: Serializable → Original
474
+ // ═══════════════════════════════════════════════════════════════════════
475
+
476
+ /**
477
+ * Convert a serializable property back to the original `Property` type.
478
+ *
479
+ * This is a mostly pass-through operation since serializable properties
480
+ * are a subset of the original types. The main conversions are:
481
+ * - `validation.matches` (string) stays as string (compatible with Property)
482
+ * - `validation.min/max` on dates: ISO strings → Date objects
483
+ */
484
+ export function fromSerializableProperty(serialized: SerializableProperty): Property {
485
+ switch (serialized.type) {
486
+ case "date": {
487
+ const sp = serialized as SerializableDateProperty;
488
+ const { validation: dateValidation, ...dateRest } = sp;
489
+ const result: Record<string, unknown> = { ...dateRest, type: "date" };
490
+ // Convert ISO string dates back to Date objects
491
+ if (dateValidation) {
492
+ const convertedValidation: Record<string, unknown> = { ...dateValidation };
493
+ if (typeof dateValidation.min === "string") {
494
+ convertedValidation.min = new Date(dateValidation.min);
495
+ }
496
+ if (typeof dateValidation.max === "string") {
497
+ convertedValidation.max = new Date(dateValidation.max);
498
+ }
499
+ result.validation = convertedValidation;
500
+ }
501
+ return result as unknown as DateProperty;
502
+ }
503
+
504
+ case "array": {
505
+ const sp = serialized as SerializableArrayProperty;
506
+ const result = { ...sp } as unknown as ArrayProperty;
507
+ // Recursively convert "of" property
508
+ if (sp.of) {
509
+ if (Array.isArray(sp.of)) {
510
+ result.of = sp.of.map(fromSerializableProperty);
511
+ } else {
512
+ result.of = fromSerializableProperty(sp.of);
513
+ }
514
+ }
515
+ // Recursively convert oneOf properties
516
+ if (sp.oneOf) {
517
+ result.oneOf = {
518
+ ...sp.oneOf,
519
+ properties: fromSerializableProperties(sp.oneOf.properties),
520
+ };
521
+ }
522
+ return result;
523
+ }
524
+
525
+ case "map": {
526
+ const sp = serialized as SerializableMapProperty;
527
+ const result = { ...sp } as unknown as MapProperty;
528
+ // Recursively convert nested properties
529
+ if (sp.properties) {
530
+ result.properties = fromSerializableProperties(sp.properties);
531
+ }
532
+ return result;
533
+ }
534
+
535
+ case "string": {
536
+ const sp = serialized as SerializableStringProperty;
537
+ const result = { ...sp } as unknown as StringProperty;
538
+ return result;
539
+ }
540
+
541
+ default:
542
+ // For all other types, the serializable form is already a valid Property
543
+ return serialized as unknown as Property;
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Convert a `SerializableProperties` record back to `Properties`.
549
+ */
550
+ export function fromSerializableProperties(serialized: SerializableProperties): Properties {
551
+ const result: Properties = {};
552
+ for (const [key, property] of Object.entries(serialized)) {
553
+ result[key] = fromSerializableProperty(property);
554
+ }
555
+ return result;
556
+ }
557
+
558
+ // ═══════════════════════════════════════════════════════════════════════
559
+ // COLLECTION CONVERSION: Serializable → Original
560
+ // ═══════════════════════════════════════════════════════════════════════
561
+
562
+ /**
563
+ * Convert a `SerializableCollection` back to an `EntityCollection`.
564
+ *
565
+ * The result will NOT contain any of the non-serializable fields
566
+ * (callbacks, entityActions, etc.) — those must be re-attached by the
567
+ * consumer if needed.
568
+ */
569
+ export function fromSerializableCollection(serialized: SerializableCollection): EntityCollection {
570
+ const { properties, ...rest } = serialized;
571
+
572
+ return {
573
+ ...rest,
574
+ properties: fromSerializableProperties(properties),
575
+ } as EntityCollection;
576
+ }
@@ -2,7 +2,7 @@
2
2
  import { AIIcon } from "@rebasepro/core";
3
3
  import { useCollectionRegistryController } from "../../_cms_internals";
4
4
  import React, { useState } from "react";
5
- import { useSnackbarController } from "@rebasepro/core";
5
+ import { useSafeSnackbarController } from "../../useSafeSnackbarController";
6
6
  import { EntityCollection } from "@rebasepro/types";
7
7
  import {
8
8
  Button,
@@ -76,7 +76,7 @@ export function AICollectionGeneratorPopover({
76
76
 
77
77
  const collectionRegistry = useCollectionRegistryController();
78
78
  const configController = useCollectionsConfigController();
79
- const snackbarController = useSnackbarController();
79
+ const snackbarController = useSafeSnackbarController();
80
80
 
81
81
  const existingCollections = collectionRegistry.collections ?? [];
82
82
 
@@ -95,7 +95,7 @@ export function AICollectionGeneratorPopover({
95
95
  name: c.name,
96
96
  properties: c.properties,
97
97
  propertiesOrder: c.propertiesOrder
98
- }));
98
+ } as Partial<EntityCollection>));
99
99
 
100
100
  const result = await generateCollection({
101
101
  prompt: prompt.trim(),
@@ -106,7 +106,7 @@ export function AICollectionGeneratorPopover({
106
106
  name: existingCollection.name,
107
107
  properties: existingCollection.properties,
108
108
  propertiesOrder: existingCollection.propertiesOrder
109
- }
109
+ } as Partial<EntityCollection>
110
110
  })
111
111
  });
112
112
 
@@ -117,7 +117,7 @@ export function AICollectionGeneratorPopover({
117
117
  mode,
118
118
  operationsCount: result.operations?.length
119
119
  });
120
- snackbarController.open({
120
+ snackbarController?.open({
121
121
  type: "success",
122
122
  message: existingCollection
123
123
  ? "Collection updated with AI suggestions"
@@ -133,7 +133,7 @@ export function AICollectionGeneratorPopover({
133
133
  mode,
134
134
  error: errorMessage
135
135
  });
136
- snackbarController.open({
136
+ snackbarController?.open({
137
137
  type: "error",
138
138
  message: errorMessage
139
139
  });