@rebasepro/admin 0.2.1 → 0.2.3

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 (35) hide show
  1. package/dist/{CollectionEditorDialog-BXIh2AXg.js → CollectionEditorDialog-CmGXXSY9.js} +6 -182
  2. package/dist/CollectionEditorDialog-CmGXXSY9.js.map +1 -0
  3. package/dist/{CollectionsStudioView-jR8iz_ja.js → CollectionsStudioView-DcLHT5bU.js} +4 -4
  4. package/dist/{CollectionsStudioView-jR8iz_ja.js.map → CollectionsStudioView-DcLHT5bU.js.map} +1 -1
  5. package/dist/{ContentHomePage-BQZWuOFb.js → ContentHomePage-C7vFqKSe.js} +2 -2
  6. package/dist/{ContentHomePage-BQZWuOFb.js.map → ContentHomePage-C7vFqKSe.js.map} +1 -1
  7. package/dist/{ExportCollectionAction-CMdiiv1L.js → ExportCollectionAction-BfN34eWX.js} +2 -2
  8. package/dist/{ExportCollectionAction-CMdiiv1L.js.map → ExportCollectionAction-BfN34eWX.js.map} +1 -1
  9. package/dist/{ImportCollectionAction-C05lE0IW.js → ImportCollectionAction-SZrInjhx.js} +2 -2
  10. package/dist/{ImportCollectionAction-C05lE0IW.js.map → ImportCollectionAction-SZrInjhx.js.map} +1 -1
  11. package/dist/{PropertyEditView-BB5xjnhZ.js → PropertyEditView-Cvryrb3B.js} +304 -326
  12. package/dist/PropertyEditView-Cvryrb3B.js.map +1 -0
  13. package/dist/{RolesView-CULIHWZ9.js → RolesView-BCb7qwWs.js} +2 -2
  14. package/dist/{RolesView-CULIHWZ9.js.map → RolesView-BCb7qwWs.js.map} +1 -1
  15. package/dist/{UsersView-D7_AtJ44.js → UsersView-Cex24r8H.js} +2 -2
  16. package/dist/{UsersView-D7_AtJ44.js.map → UsersView-Cex24r8H.js.map} +1 -1
  17. package/dist/collection_editor/ui/collection_editor/properties/RelationPropertyField.d.ts +1 -7
  18. package/dist/collection_editor_ui.js +3 -3
  19. package/dist/{index-D5OQhv-T.js → index-DjduZG1T.js} +3 -3
  20. package/dist/index-DjduZG1T.js.map +1 -0
  21. package/dist/{index-CoSNm3e3.js → index-MKPc70-v.js} +3 -3
  22. package/dist/index-MKPc70-v.js.map +1 -0
  23. package/dist/{index-BAM9KCmM.js → index-PLIQXpTt.js} +2 -2
  24. package/dist/{index-BAM9KCmM.js.map → index-PLIQXpTt.js.map} +1 -1
  25. package/dist/index.js +4 -4
  26. package/dist/{util-DtbWD7LF.js → util-DbWax_sV.js} +95 -15
  27. package/dist/{util-DtbWD7LF.js.map → util-DbWax_sV.js.map} +1 -1
  28. package/package.json +10 -9
  29. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +1 -10
  30. package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +37 -57
  31. package/src/collection_editor/validateCollectionJson.ts +88 -1
  32. package/dist/CollectionEditorDialog-BXIh2AXg.js.map +0 -1
  33. package/dist/PropertyEditView-BB5xjnhZ.js.map +0 -1
  34. package/dist/index-CoSNm3e3.js.map +0 -1
  35. package/dist/index-D5OQhv-T.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/admin",
3
3
  "type": "module",
4
- "version": "0.2.1",
4
+ "version": "0.2.3",
5
5
  "description": "Rebase CMS — content management views, forms, and routing",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -57,6 +57,7 @@
57
57
  "@floating-ui/dom": "^1.7.6",
58
58
  "cmdk": "^1.1.1",
59
59
  "date-fns": "^3.6.0",
60
+ "exceljs": "^4.4.0",
60
61
  "fast-equals": "6.0.0",
61
62
  "fractional-indexing": "3.2.0",
62
63
  "fuse.js": "^7.3.0",
@@ -79,17 +80,17 @@
79
80
  "prosemirror-tables": "^1.8.5",
80
81
  "prosemirror-transform": "^1.12.0",
81
82
  "prosemirror-view": "^1.41.8",
83
+ "react-compiler-runtime": "1.0.0",
82
84
  "react-dropzone": "^14.4.1",
83
85
  "react-use-measure": "^2.1.7",
84
- "exceljs": "^4.4.0",
85
86
  "zod": "^3.25.76",
86
- "@rebasepro/common": "0.2.1",
87
- "@rebasepro/core": "0.2.1",
88
- "@rebasepro/formex": "0.2.1",
89
- "@rebasepro/ui": "0.2.1",
90
- "@rebasepro/types": "0.2.1",
91
- "@rebasepro/schema-inference": "0.2.1",
92
- "@rebasepro/utils": "0.2.1"
87
+ "@rebasepro/common": "0.2.3",
88
+ "@rebasepro/formex": "0.2.3",
89
+ "@rebasepro/core": "0.2.3",
90
+ "@rebasepro/schema-inference": "0.2.3",
91
+ "@rebasepro/ui": "0.2.3",
92
+ "@rebasepro/utils": "0.2.3",
93
+ "@rebasepro/types": "0.2.3"
93
94
  },
94
95
  "peerDependencies": {
95
96
  "react": ">=19.0.0",
@@ -29,7 +29,6 @@ import { CollectionEditorSchema } from "./CollectionYupValidation";
29
29
  import { GeneralSettingsForm } from "./GeneralSettingsForm";
30
30
  import { DisplaySettingsForm } from "./DisplaySettingsForm";
31
31
  import { CollectionPropertiesEditorForm } from "./CollectionPropertiesEditorForm";
32
- import { CollectionRelationsTab } from "./CollectionRelationsTab";
33
32
  import { CollectionsConfigController } from "../../types/config_controller";
34
33
  import { CollectionEditorWelcomeView } from "./CollectionEditorWelcomeView";
35
34
  import { CollectionInference } from "../../types/collection_inference";
@@ -163,7 +162,6 @@ type EditorView = "welcome"
163
162
  | "properties"
164
163
  | "loading"
165
164
  | "extra_view"
166
- | "relations"
167
165
  | "rls";
168
166
 
169
167
  export function CollectionEditor(props: CollectionEditorDialogProps & {
@@ -490,7 +488,7 @@ function CollectionEditorInternal<M extends Record<string, unknown>>({
490
488
  const validation = (col: EntityCollection<M>) => {
491
489
 
492
490
  let errors: Record<string, string> = {};
493
- const schema = (currentView === "properties" || currentView === "relations" || currentView === "general") && CollectionEditorSchema;
491
+ const schema = (currentView === "properties" || currentView === "general") && CollectionEditorSchema;
494
492
  if (schema) {
495
493
  const result = schema.safeParse(col);
496
494
  if (!result.success) {
@@ -644,9 +642,6 @@ function CollectionEditorInternal<M extends Record<string, unknown>>({
644
642
  {getDataSourceCapabilities(values.driver).supportsRLS && <Tab value={"rls"}>
645
643
  RLS
646
644
  </Tab>}
647
- {getDataSourceCapabilities(values.driver).supportsRelations && <Tab value={"relations"}>
648
- Relations
649
- </Tab>}
650
645
  </Tabs>
651
646
  </div>
652
647
  <div className="flex items-center gap-4">
@@ -752,10 +747,6 @@ function CollectionEditorInternal<M extends Record<string, unknown>>({
752
747
  <DisplaySettingsForm expandKanban={expandKanban}/>
753
748
  }
754
749
 
755
- {currentView === "relations" && getDataSourceCapabilities(values.driver).supportsRelations &&
756
- <CollectionRelationsTab/>
757
- }
758
-
759
750
  {currentView === "rls" && getDataSourceCapabilities(values.driver).supportsRLS &&
760
751
  <CollectionRLSTab/>
761
752
  }
@@ -14,16 +14,25 @@ import { CollectionsSelect } from "./ReferencePropertyField";
14
14
 
15
15
  const ON_ACTION_OPTIONS: OnAction[] = ["cascade", "restrict", "no action", "set null", "set default"];
16
16
 
17
+ function getTargetSlug(target?: string | (() => string | { slug: string } | Record<string, unknown>)): string {
18
+ if (!target) return "";
19
+ if (typeof target === "string") return target;
20
+ try {
21
+ const resolved = target();
22
+ if (typeof resolved === "string") return resolved;
23
+ if (resolved && typeof resolved === "object" && "slug" in resolved && typeof resolved.slug === "string") {
24
+ return resolved.slug;
25
+ }
26
+ return "";
27
+ } catch {
28
+ return "";
29
+ }
30
+ }
31
+
17
32
  /**
18
33
  * Property editor form for `type: "relation"` properties.
19
34
  *
20
- * This component edits both:
21
- * 1. The `RelationProperty` fields on the property itself (relationName, etc.)
22
- * 2. The matching `Relation` entry in `collection.relations[]`
23
- *
24
- * When a user configures a relation property, we sync the relation config
25
- * back to the parent collection's `relations` array so saving the collection
26
- * persists everything in one go.
35
+ * This component edits the `RelationProperty` fields on the property itself (target, relationName, etc.)
27
36
  */
28
37
  export function RelationPropertyField({
29
38
  disabled,
@@ -36,37 +45,22 @@ export function RelationPropertyField({
36
45
  values,
37
46
  errors,
38
47
  setFieldValue
39
- } = useFormex<RelationProperty & { id?: string; _relationConfig?: Record<string, unknown> }>();
48
+ } = useFormex<RelationProperty & { id?: string }>();
40
49
 
41
50
  const collectionRegistry = useCollectionRegistryController();
42
51
 
43
- // ─── Read the parent collection form to sync `relations[]` ───
44
- // The PropertyForm is nested inside a parent Formex for the whole collection.
45
- // We reach it via a second useFormex keyed to the parent context.
46
- // However, the PropertyForm uses its own isolated Formex, so we can't
47
- // directly access the parent. Instead, we store the relation config
48
- // on the property itself and let the save logic merge it.
49
- //
50
- // We store the full relation config on a transient `_relationConfig` key
51
- // so the consumer (CollectionPropertiesEditorForm / save logic) can
52
- // extract it and place it in `collection.relations[]`.
53
-
54
52
  const relationName = values.relationName ?? "";
55
- // Transient config object stored on the property for editor use.
56
- // Contains standard Relation fields plus `_targetSlug` for the UI dropdown.
57
- const relationConfig: Record<string, unknown> = (values._relationConfig as Record<string, unknown>) ?? {};
58
-
59
- const targetSlug = (relationConfig._targetSlug as string) ?? "";
60
- const cardinality = (relationConfig.cardinality as string) ?? "one";
61
- const direction = (relationConfig.direction as string) ?? "owning";
62
- const localKey = (relationConfig.localKey as string) ?? "";
63
- const foreignKeyOnTarget = (relationConfig.foreignKeyOnTarget as string) ?? "";
64
- const through = relationConfig.through as Record<string, string> | undefined;
53
+ const targetSlug = getTargetSlug(values.target);
54
+ const cardinality = values.cardinality ?? "one";
55
+ const direction = values.direction ?? "owning";
56
+ const localKey = values.localKey ?? "";
57
+ const foreignKeyOnTarget = values.foreignKeyOnTarget ?? "";
58
+ const through = values.through;
65
59
  const throughTable = through?.table ?? "";
66
60
  const throughSourceColumn = through?.sourceColumn ?? "";
67
61
  const throughTargetColumn = through?.targetColumn ?? "";
68
- const onUpdate = (relationConfig.onUpdate as string) ?? "no action";
69
- const onDelete = (relationConfig.onDelete as string) ?? "no action";
62
+ const onUpdate = values.onUpdate ?? "no action";
63
+ const onDelete = values.onDelete ?? "no action";
70
64
 
71
65
  // Whether to show the junction table section
72
66
  const showThrough = cardinality === "many" && direction === "owning";
@@ -75,26 +69,12 @@ export function RelationPropertyField({
75
69
  // Whether to show the foreign key on target field
76
70
  const showForeignKey = direction === "inverse";
77
71
 
78
- const updateRelationConfig = useCallback(
79
- (patch: Record<string, unknown>) => {
80
- const current = (values._relationConfig as Record<string, unknown>) ?? {};
81
- setFieldValue("_relationConfig" as keyof (RelationProperty & { _relationConfig?: unknown }), { ...current,
82
- ...patch });
83
- },
84
- [values, setFieldValue]
85
- );
86
-
87
72
  const updateThrough = useCallback(
88
73
  (patch: Record<string, unknown>) => {
89
- const current = (values._relationConfig as Record<string, unknown>) ?? {};
90
- const currentThrough = (current.through as Record<string, unknown>) ?? {};
91
- setFieldValue("_relationConfig" as keyof (RelationProperty & { _relationConfig?: unknown }), {
92
- ...current,
93
- through: { ...currentThrough,
94
- ...patch }
95
- });
74
+ const currentThrough = values.through ?? { table: "", sourceColumn: "", targetColumn: "" };
75
+ setFieldValue("through", { ...currentThrough, ...patch });
96
76
  },
97
- [values, setFieldValue]
77
+ [values.through, setFieldValue]
98
78
  );
99
79
 
100
80
  // Auto-generate relationName from target collection slug
@@ -102,7 +82,7 @@ export function RelationPropertyField({
102
82
  if (targetSlug && !relationName) {
103
83
  setFieldValue("relationName", targetSlug);
104
84
  }
105
- }, [targetSlug]);
85
+ }, [targetSlug, relationName, setFieldValue]);
106
86
 
107
87
  const collections: EntityCollection[] = collectionRegistry?.collections ?? [];
108
88
 
@@ -112,10 +92,10 @@ export function RelationPropertyField({
112
92
  <div className={"col-span-12"}>
113
93
  <CollectionsSelect
114
94
  disabled={disabled}
115
- pathPath={"_relationConfig._targetSlug"}
95
+ pathPath={"target"}
116
96
  value={targetSlug}
117
97
  setFieldValue={(_, value) => {
118
- updateRelationConfig({ _targetSlug: value });
98
+ setFieldValue("target", value);
119
99
  // Auto-generate relation name from target
120
100
  if (!relationName || relationName === targetSlug) {
121
101
  setFieldValue("relationName", value);
@@ -152,7 +132,7 @@ export function RelationPropertyField({
152
132
  <div className={"col-span-12 sm:col-span-6"}>
153
133
  <Select
154
134
  value={cardinality}
155
- onValueChange={(v) => updateRelationConfig({ cardinality: v })}
135
+ onValueChange={(v) => setFieldValue("cardinality", v as "one" | "many")}
156
136
  label={"Cardinality"}
157
137
  disabled={disabled}
158
138
  fullWidth
@@ -184,7 +164,7 @@ export function RelationPropertyField({
184
164
  <div className={"col-span-12 sm:col-span-6"}>
185
165
  <Select
186
166
  value={direction}
187
- onValueChange={(v) => updateRelationConfig({ direction: v })}
167
+ onValueChange={(v) => setFieldValue("direction", v as "owning" | "inverse")}
188
168
  label={"Direction"}
189
169
  disabled={disabled}
190
170
  fullWidth
@@ -218,7 +198,7 @@ export function RelationPropertyField({
218
198
  <TextField
219
199
  value={localKey}
220
200
  onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
221
- updateRelationConfig({ localKey: e.target.value })
201
+ setFieldValue("localKey", e.target.value)
222
202
  }
223
203
  label={"Local key (foreign key column on this table)"}
224
204
  disabled={disabled}
@@ -236,7 +216,7 @@ export function RelationPropertyField({
236
216
  <TextField
237
217
  value={foreignKeyOnTarget}
238
218
  onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
239
- updateRelationConfig({ foreignKeyOnTarget: e.target.value })
219
+ setFieldValue("foreignKeyOnTarget", e.target.value)
240
220
  }
241
221
  label={"Foreign key on target table"}
242
222
  disabled={disabled}
@@ -305,7 +285,7 @@ export function RelationPropertyField({
305
285
  <div className={"col-span-12 sm:col-span-6"}>
306
286
  <Select
307
287
  value={onUpdate}
308
- onValueChange={(v) => updateRelationConfig({ onUpdate: v })}
288
+ onValueChange={(v) => setFieldValue("onUpdate", v as OnAction)}
309
289
  label={"On update"}
310
290
  disabled={disabled}
311
291
  fullWidth
@@ -325,7 +305,7 @@ export function RelationPropertyField({
325
305
  <div className={"col-span-12 sm:col-span-6"}>
326
306
  <Select
327
307
  value={onDelete}
328
- onValueChange={(v) => updateRelationConfig({ onDelete: v })}
308
+ onValueChange={(v) => setFieldValue("onDelete", v as OnAction)}
329
309
  label={"On delete"}
330
310
  disabled={disabled}
331
311
  fullWidth
@@ -10,8 +10,11 @@ const VALID_DATA_TYPES = [
10
10
  "date",
11
11
  "geopoint",
12
12
  "reference",
13
+ "relation",
13
14
  "array",
14
- "map"
15
+ "map",
16
+ "vector",
17
+ "binary"
15
18
  ] as const;
16
19
 
17
20
  type DataType = typeof VALID_DATA_TYPES[number];
@@ -109,6 +112,90 @@ function validateProperty(
109
112
  }
110
113
  }
111
114
 
115
+ // Validate relation property
116
+ if (property.dataType === "relation") {
117
+ if (property.target !== undefined && typeof property.target !== "string" && typeof property.target !== "function") {
118
+ errors.push({
119
+ path: `${path}.target`,
120
+ message: "Must be a string (collection slug) or a function"
121
+ });
122
+ }
123
+ if (property.cardinality !== undefined && property.cardinality !== "one" && property.cardinality !== "many") {
124
+ errors.push({
125
+ path: `${path}.cardinality`,
126
+ message: "Must be either 'one' or 'many'"
127
+ });
128
+ }
129
+ if (property.direction !== undefined && property.direction !== "owning" && property.direction !== "inverse") {
130
+ errors.push({
131
+ path: `${path}.direction`,
132
+ message: "Must be either 'owning' or 'inverse'"
133
+ });
134
+ }
135
+ if (property.localKey !== undefined && typeof property.localKey !== "string") {
136
+ errors.push({
137
+ path: `${path}.localKey`,
138
+ message: "Must be a string"
139
+ });
140
+ }
141
+ if (property.foreignKeyOnTarget !== undefined && typeof property.foreignKeyOnTarget !== "string") {
142
+ errors.push({
143
+ path: `${path}.foreignKeyOnTarget`,
144
+ message: "Must be a string"
145
+ });
146
+ }
147
+ if (property.through !== undefined) {
148
+ if (typeof property.through !== "object" || property.through === null) {
149
+ errors.push({
150
+ path: `${path}.through`,
151
+ message: "Must be an object"
152
+ });
153
+ } else {
154
+ const throughObj = property.through as Record<string, unknown>;
155
+ if (throughObj.table !== undefined && typeof throughObj.table !== "string") {
156
+ errors.push({
157
+ path: `${path}.through.table`,
158
+ message: "Must be a string"
159
+ });
160
+ }
161
+ if (throughObj.sourceColumn !== undefined && typeof throughObj.sourceColumn !== "string") {
162
+ errors.push({
163
+ path: `${path}.through.sourceColumn`,
164
+ message: "Must be a string"
165
+ });
166
+ }
167
+ if (throughObj.targetColumn !== undefined && typeof throughObj.targetColumn !== "string") {
168
+ errors.push({
169
+ path: `${path}.through.targetColumn`,
170
+ message: "Must be a string"
171
+ });
172
+ }
173
+ }
174
+ }
175
+ if (property.onUpdate !== undefined && typeof property.onUpdate !== "string") {
176
+ errors.push({
177
+ path: `${path}.onUpdate`,
178
+ message: "Must be a string"
179
+ });
180
+ }
181
+ if (property.onDelete !== undefined && typeof property.onDelete !== "string") {
182
+ errors.push({
183
+ path: `${path}.onDelete`,
184
+ message: "Must be a string"
185
+ });
186
+ }
187
+ }
188
+
189
+ // Validate vector property
190
+ if (property.dataType === "vector") {
191
+ if (property.dimensions !== undefined && (typeof property.dimensions !== "number" || isNaN(property.dimensions) || property.dimensions <= 0)) {
192
+ errors.push({
193
+ path: `${path}.dimensions`,
194
+ message: "Must be a positive number"
195
+ });
196
+ }
197
+ }
198
+
112
199
  // Validate storage config for string
113
200
  if (property.dataType === "string" && property.storage) {
114
201
  if (typeof property.storage !== "object") {