@ghentcdh/crouton-api 0.0.1-alpha.78 → 0.0.1-alpha.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -670,7 +670,7 @@ var LayoutControlSchema = import_zod8.z.union([
670
670
  import_zod8.z.object({
671
671
  id: import_zod8.z.string(),
672
672
  colspan: import_zod8.z.number().int().min(1).max(12).optional(),
673
- rowspan: import_zod8.z.number().int().min(1).optional(),
673
+ rowspan: import_zod8.z.number().int().min(1).max(6).optional(),
674
674
  width: import_zod8.z.string().optional(),
675
675
  label: import_zod8.z.string().optional(),
676
676
  hideLabel: import_zod8.z.boolean().optional(),
@@ -690,7 +690,7 @@ var LayoutNodeSchema = import_zod8.z.lazy(() => import_zod8.z.object({
690
690
  title: import_zod8.z.string().optional(),
691
691
  titleKey: import_zod8.z.string().optional(),
692
692
  colspan: import_zod8.z.number().int().min(1).max(12).optional(),
693
- rowspan: import_zod8.z.number().int().min(1).optional(),
693
+ rowspan: import_zod8.z.number().int().min(1).max(6).optional(),
694
694
  label: import_zod8.z.string().optional(),
695
695
  controls: import_zod8.z.array(LayoutControlSchema).optional(),
696
696
  items: import_zod8.z.array(LayoutNodeSchema).optional()
@@ -702,152 +702,526 @@ var LayoutSchema = import_zod8.z.object({
702
702
  });
703
703
 
704
704
  // ../crouton-core/src/lib/resource/ResourceJson.schema.ts
705
- var import_zod30 = require("zod");
705
+ var import_zod31 = require("zod");
706
706
 
707
707
  // ../crouton-core/src/lib/resource/CalculatedColumn.schema.ts
708
- var import_zod20 = require("zod");
708
+ var import_zod21 = require("zod");
709
709
 
710
710
  // ../crouton-core/src/lib/resource/FieldInput.schema.ts
711
- var import_zod19 = require("zod");
711
+ var import_zod20 = require("zod");
712
712
 
713
713
  // ../crouton-core/src/lib/resource/field-input/types/array.options.ts
714
714
  var import_zod10 = require("zod");
715
715
 
716
716
  // ../crouton-core/src/lib/resource/field-input/base.options.ts
717
717
  var import_zod9 = require("zod");
718
+
719
+ // ../crouton-core/src/lib/resource/field-input/option-meta.ts
720
+ var opt = /* @__PURE__ */ __name((schema, meta) => schema.meta(meta), "opt");
721
+
722
+ // ../crouton-core/src/lib/resource/field-input/base.options.ts
718
723
  var BaseOptionsSchema = import_zod9.z.object({
719
- label: import_zod9.z.string().optional().describe("Override the field label"),
720
- hideLabel: import_zod9.z.boolean().optional().describe("Hide the label"),
721
- placeholder: import_zod9.z.string().optional(),
722
- readonly: import_zod9.z.boolean().optional(),
723
- colspan: import_zod9.z.number().optional(),
724
- width: import_zod9.z.string().optional(),
725
- styles: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.unknown()).optional(),
726
- customRender: import_zod9.z.string().optional()
727
- }).catchall(import_zod9.z.unknown());
724
+ label: opt(import_zod9.z.string().optional(), {
725
+ description: "Override the field label",
726
+ examples: [
727
+ "My label"
728
+ ]
729
+ }),
730
+ hideLabel: opt(import_zod9.z.boolean().optional(), {
731
+ description: "Hide the field label"
732
+ }),
733
+ placeholder: opt(import_zod9.z.string().optional(), {
734
+ description: "Placeholder text shown inside the input when empty",
735
+ examples: [
736
+ "Search..."
737
+ ]
738
+ }),
739
+ readonly: opt(import_zod9.z.boolean().optional(), {
740
+ description: "Render the field in read-only mode"
741
+ }),
742
+ colspan: opt(import_zod9.z.number().optional(), {
743
+ description: "Grid column span (1\u201312)",
744
+ examples: [
745
+ 6,
746
+ 12
747
+ ]
748
+ }),
749
+ width: opt(import_zod9.z.string().optional(), {
750
+ description: "Named width (xs, sm, md, lg, xl, full) or any CSS value",
751
+ examples: [
752
+ "md",
753
+ "240px"
754
+ ]
755
+ }),
756
+ styles: opt(import_zod9.z.record(import_zod9.z.string(), import_zod9.z.unknown()).optional(), {
757
+ description: "Inline CSS style object applied to the field wrapper",
758
+ examples: [
759
+ {
760
+ color: "red"
761
+ }
762
+ ]
763
+ }),
764
+ customRender: opt(import_zod9.z.string().optional(), {
765
+ description: "Name of a globally registered Vue component to render instead of the default input",
766
+ examples: [
767
+ "MyCustomInput"
768
+ ]
769
+ })
770
+ }).catchall(import_zod9.z.unknown()).meta({
771
+ title: "Base options",
772
+ description: "Accepted by every field input type"
773
+ });
728
774
 
729
775
  // ../crouton-core/src/lib/resource/field-input/types/array.options.ts
730
776
  var ArrayOptionsSchema = BaseOptionsSchema.extend({
731
- layout: import_zod10.z.enum([
777
+ layout: opt(import_zod10.z.enum([
732
778
  "row",
733
779
  "column"
734
- ]).optional(),
735
- elementLabelProp: import_zod10.z.string().optional(),
736
- hideActions: import_zod10.z.boolean().optional()
780
+ ]).optional(), {
781
+ description: "Direction in which array items are stacked",
782
+ examples: [
783
+ "row",
784
+ "column"
785
+ ]
786
+ }),
787
+ elementLabelProp: opt(import_zod10.z.string().optional(), {
788
+ description: "Property of each array item used as its label in the UI",
789
+ examples: [
790
+ "name",
791
+ "title"
792
+ ]
793
+ }),
794
+ hideActions: opt(import_zod10.z.boolean().optional(), {
795
+ description: "Hide the add/remove action buttons"
796
+ })
797
+ }).meta({
798
+ title: "Array options",
799
+ description: "Options for the detail/array layout control"
737
800
  });
738
801
 
739
802
  // ../crouton-core/src/lib/resource/field-input/types/autocomplete.options.ts
740
803
  var import_zod11 = require("zod");
741
804
  var AutocompleteBaseSchema = BaseOptionsSchema.extend({
742
- labelKey: import_zod11.z.string().optional(),
743
- valueKey: import_zod11.z.string().optional(),
744
- freeText: import_zod11.z.boolean().optional(),
745
- enableCreate: import_zod11.z.boolean().optional(),
746
- storeValue: import_zod11.z.boolean().optional()
805
+ labelKey: opt(import_zod11.z.string().optional(), {
806
+ description: "Object key used as the display label in the dropdown",
807
+ examples: [
808
+ "label",
809
+ "name"
810
+ ]
811
+ }),
812
+ valueKey: opt(import_zod11.z.string().optional(), {
813
+ description: "Object key used as the stored value",
814
+ examples: [
815
+ "value",
816
+ "id"
817
+ ]
818
+ }),
819
+ freeText: opt(import_zod11.z.boolean().optional(), {
820
+ description: "Allow the user to type a value not in the options list"
821
+ }),
822
+ enableCreate: opt(import_zod11.z.boolean().optional(), {
823
+ description: 'Show a "create" action when no matching option is found'
824
+ }),
825
+ storeValue: opt(import_zod11.z.boolean().optional(), {
826
+ description: "Store option[valueKey] instead of the whole option object"
827
+ })
828
+ }).meta({
829
+ title: "Common autocomplete options",
830
+ description: "Shared options available on all autocomplete variants"
747
831
  });
748
832
  var AutocompleteInlineOptionsSchema = AutocompleteBaseSchema.extend({
749
- options: import_zod11.z.array(import_zod11.z.unknown()),
750
- values: import_zod11.z.array(import_zod11.z.unknown()).optional()
833
+ options: opt(import_zod11.z.array(import_zod11.z.unknown()), {
834
+ description: "Inline array of option items",
835
+ examples: [
836
+ [
837
+ {
838
+ label: "Active",
839
+ value: "active"
840
+ }
841
+ ]
842
+ ]
843
+ }),
844
+ values: opt(import_zod11.z.array(import_zod11.z.unknown()).optional(), {
845
+ description: "Alias for options (deprecated \u2014 prefer options)",
846
+ deprecated: true
847
+ })
848
+ }).meta({
849
+ title: "Inline autocomplete",
850
+ description: "Options provided as an inline array"
751
851
  });
752
852
  var AutocompleteRemoteOptionsSchema = AutocompleteBaseSchema.extend({
753
- uri: import_zod11.z.string().describe("Remote endpoint URL"),
754
- dataField: import_zod11.z.string().optional().describe("Field in response containing the data array"),
755
- skipAuth: import_zod11.z.boolean().optional()
853
+ uri: opt(import_zod11.z.string(), {
854
+ description: "Remote endpoint URL that returns option items",
855
+ examples: [
856
+ "/api/authors"
857
+ ]
858
+ }),
859
+ dataField: opt(import_zod11.z.string().optional(), {
860
+ description: "Field in the API response containing the data array",
861
+ examples: [
862
+ "data",
863
+ "results"
864
+ ]
865
+ }),
866
+ skipAuth: opt(import_zod11.z.boolean().optional(), {
867
+ description: "Send the request without authentication headers"
868
+ })
869
+ }).meta({
870
+ title: "Remote autocomplete",
871
+ description: "Options fetched from a remote endpoint"
756
872
  });
757
873
  var AutocompleteResourceOptionsSchema = AutocompleteBaseSchema.extend({
758
- resource: import_zod11.z.string().describe("Relative path to a resource.json"),
759
- skipAuth: import_zod11.z.boolean().optional(),
760
- dataField: import_zod11.z.string().optional()
874
+ resource: opt(import_zod11.z.string(), {
875
+ description: "Relative path to a resource.json used to populate the options",
876
+ examples: [
877
+ "./author.resource.json"
878
+ ]
879
+ }),
880
+ skipAuth: opt(import_zod11.z.boolean().optional(), {
881
+ description: "Send the request without authentication headers"
882
+ }),
883
+ dataField: opt(import_zod11.z.string().optional(), {
884
+ description: "Field in the API response containing the data array",
885
+ examples: [
886
+ "data",
887
+ "results"
888
+ ]
889
+ })
890
+ }).meta({
891
+ title: "Resource autocomplete",
892
+ description: "Options populated from a crouton resource"
761
893
  });
762
894
  var AutocompleteOptionsSchema = import_zod11.z.union([
763
895
  AutocompleteInlineOptionsSchema,
764
896
  AutocompleteRemoteOptionsSchema,
765
897
  AutocompleteResourceOptionsSchema,
766
898
  AutocompleteBaseSchema
767
- ]).describe("Autocomplete options: inline array, remote URI, or resource reference");
899
+ ]).meta({
900
+ title: "Autocomplete options",
901
+ description: "Autocomplete options: inline array, remote URI, or resource reference"
902
+ });
768
903
 
769
904
  // ../crouton-core/src/lib/resource/field-input/types/boolean.options.ts
770
- var BooleanOptionsSchema = BaseOptionsSchema.extend({});
905
+ var BooleanOptionsSchema = BaseOptionsSchema.extend({}).meta({
906
+ title: "Boolean options",
907
+ description: "Checkbox input. All common options apply; no type-specific options."
908
+ });
771
909
 
772
910
  // ../crouton-core/src/lib/resource/field-input/types/custom.options.ts
773
911
  var import_zod12 = require("zod");
774
912
  var CustomOptionsSchema = BaseOptionsSchema.extend({
775
- customRender: import_zod12.z.string().optional()
776
- }).catchall(import_zod12.z.unknown());
913
+ customRender: opt(import_zod12.z.string().optional(), {
914
+ description: "Name of the globally registered Vue component to render (overrides the base option)",
915
+ examples: [
916
+ "MyWidget"
917
+ ]
918
+ })
919
+ }).catchall(import_zod12.z.unknown()).meta({
920
+ title: "Custom options",
921
+ description: "Options for app-registered custom renderer components"
922
+ });
777
923
 
778
924
  // ../crouton-core/src/lib/resource/field-input/types/date-range.options.ts
779
925
  var import_zod13 = require("zod");
780
926
  var DateRangeOptionsSchema = BaseOptionsSchema.extend({
781
- fromLabel: import_zod13.z.string().optional().describe('Label above the start date input. Defaults to "From"'),
782
- toLabel: import_zod13.z.string().optional().describe('Label above the end date input. Defaults to "To"'),
783
- fromField: import_zod13.z.string().optional().describe('JSON object key for the start date. Defaults to "from"'),
784
- toField: import_zod13.z.string().optional().describe('JSON object key for the end date. Defaults to "to"')
927
+ fromLabel: opt(import_zod13.z.string().optional(), {
928
+ description: "Label above the start date input",
929
+ default: "From",
930
+ examples: [
931
+ "From",
932
+ "Start date"
933
+ ]
934
+ }),
935
+ toLabel: opt(import_zod13.z.string().optional(), {
936
+ description: "Label above the end date input",
937
+ default: "To",
938
+ examples: [
939
+ "To",
940
+ "End date"
941
+ ]
942
+ }),
943
+ fromField: opt(import_zod13.z.string().optional(), {
944
+ description: "JSON object key for the start date",
945
+ default: "from",
946
+ examples: [
947
+ "from",
948
+ "startDate"
949
+ ]
950
+ }),
951
+ toField: opt(import_zod13.z.string().optional(), {
952
+ description: "JSON object key for the end date",
953
+ default: "to",
954
+ examples: [
955
+ "to",
956
+ "endDate"
957
+ ]
958
+ })
959
+ }).meta({
960
+ title: "Date-range options",
961
+ description: "Options for the from/to date range picker"
785
962
  });
786
963
 
787
964
  // ../crouton-core/src/lib/resource/field-input/types/date.options.ts
788
965
  var import_zod14 = require("zod");
789
966
  var DateOptionsSchema = BaseOptionsSchema.extend({
790
- withTime: import_zod14.z.boolean().optional().describe("Force time input on/off, overrides format"),
791
- min: import_zod14.z.string().optional().describe("Earliest selectable date, ISO or yyyy-MM-dd"),
792
- max: import_zod14.z.string().optional().describe("Latest selectable date, ISO or yyyy-MM-dd"),
793
- locale: import_zod14.z.string().optional().describe("BCP-47 locale for month heading and weekday labels").default("en-GB"),
794
- firstDayOfWeek: import_zod14.z.number().optional().describe("0 = Sunday, 1 = Monday").default(1)
967
+ withTime: opt(import_zod14.z.boolean().optional(), {
968
+ description: "Force time input on/off, overrides format detection"
969
+ }),
970
+ min: opt(import_zod14.z.string().optional(), {
971
+ description: "Earliest selectable date (ISO 8601 or yyyy-MM-dd)",
972
+ examples: [
973
+ "2020-01-01"
974
+ ]
975
+ }),
976
+ max: opt(import_zod14.z.string().optional(), {
977
+ description: "Latest selectable date (ISO 8601 or yyyy-MM-dd)",
978
+ examples: [
979
+ "2099-12-31"
980
+ ]
981
+ }),
982
+ locale: opt(import_zod14.z.string().optional().default("en-GB"), {
983
+ description: "BCP-47 locale for month heading and weekday labels",
984
+ examples: [
985
+ "en-GB",
986
+ "nl-BE"
987
+ ],
988
+ default: "en-GB"
989
+ }),
990
+ firstDayOfWeek: opt(import_zod14.z.number().optional().default(1), {
991
+ description: "First day of the week: 0 = Sunday, 1 = Monday",
992
+ examples: [
993
+ 0,
994
+ 1
995
+ ],
996
+ default: 1
997
+ })
998
+ }).meta({
999
+ title: "Date options",
1000
+ description: "Options for the date picker (and dateTime alias)"
795
1001
  });
796
1002
 
797
1003
  // ../crouton-core/src/lib/resource/field-input/types/markdown.options.ts
798
1004
  var import_zod15 = require("zod");
799
1005
  var MarkdownOptionsSchema = BaseOptionsSchema.extend({
800
- minHeight: import_zod15.z.string().optional().describe('Minimum editor height, e.g. "200px"')
1006
+ minHeight: opt(import_zod15.z.string().optional(), {
1007
+ description: "Minimum editor height",
1008
+ examples: [
1009
+ "200px",
1010
+ "400px"
1011
+ ]
1012
+ })
1013
+ }).meta({
1014
+ title: "Markdown options",
1015
+ description: "Options for the Markdown rich-text editor"
801
1016
  });
802
1017
 
803
1018
  // ../crouton-core/src/lib/resource/field-input/types/number.options.ts
804
- var NumberOptionsSchema = BaseOptionsSchema.extend({});
1019
+ var NumberOptionsSchema = BaseOptionsSchema.extend({}).meta({
1020
+ title: "Number options",
1021
+ description: "Numeric input (also used for Integer alias). All common options apply; no type-specific options."
1022
+ });
805
1023
 
806
1024
  // ../crouton-core/src/lib/resource/field-input/types/relation.options.ts
807
1025
  var import_zod16 = require("zod");
808
1026
  var RelationOptionsSchema = BaseOptionsSchema.extend({
809
- colspan: import_zod16.z.number().optional().default(12),
810
- sort: import_zod16.z.string().optional().describe("Field to sort related records by"),
811
- sortDir: import_zod16.z.enum([
1027
+ colspan: opt(import_zod16.z.number().optional().default(12), {
1028
+ description: "Grid column span (1\u201312); defaults to 12 for relation controls",
1029
+ default: 12,
1030
+ examples: [
1031
+ 12,
1032
+ 6
1033
+ ]
1034
+ }),
1035
+ sort: opt(import_zod16.z.string().optional(), {
1036
+ description: "Field to sort related records by",
1037
+ examples: [
1038
+ "name",
1039
+ "createdAt"
1040
+ ]
1041
+ }),
1042
+ sortDir: opt(import_zod16.z.enum([
812
1043
  "asc",
813
1044
  "desc"
814
- ]).optional().describe("Sort direction, defaults to asc"),
815
- direction: import_zod16.z.string().optional().describe("CSS flex direction for the button layout"),
816
- displayKey: import_zod16.z.string().optional().describe("Key used as display label"),
817
- resource: import_zod16.z.string().optional().describe("Path to the related resource")
818
- }).catchall(import_zod16.z.unknown());
1045
+ ]).optional(), {
1046
+ description: "Sort direction",
1047
+ default: "asc",
1048
+ examples: [
1049
+ "asc",
1050
+ "desc"
1051
+ ]
1052
+ }),
1053
+ direction: opt(import_zod16.z.string().optional(), {
1054
+ description: "CSS flex direction for the button layout",
1055
+ examples: [
1056
+ "row",
1057
+ "column"
1058
+ ]
1059
+ }),
1060
+ displayKey: opt(import_zod16.z.string().optional(), {
1061
+ description: "Key used as the display label for related records",
1062
+ examples: [
1063
+ "name",
1064
+ "title"
1065
+ ]
1066
+ }),
1067
+ resource: opt(import_zod16.z.string().optional(), {
1068
+ description: "Path to the related resource.json",
1069
+ examples: [
1070
+ "./author.resource.json"
1071
+ ]
1072
+ })
1073
+ }).catchall(import_zod16.z.unknown()).meta({
1074
+ title: "Relation options",
1075
+ description: "Options for the sub-resource relation control"
1076
+ });
819
1077
 
820
1078
  // ../crouton-core/src/lib/resource/field-input/types/select.options.ts
821
1079
  var import_zod17 = require("zod");
822
1080
  var SelectOptionsSchema = BaseOptionsSchema.extend({
823
- options: import_zod17.z.array(import_zod17.z.unknown()).optional(),
824
- values: import_zod17.z.array(import_zod17.z.unknown()).optional(),
825
- resource: import_zod17.z.string().optional(),
826
- labelKey: import_zod17.z.string().optional(),
827
- valueKey: import_zod17.z.string().optional(),
828
- uri: import_zod17.z.string().optional().describe("Remote endpoint URL for fetching options"),
829
- dataField: import_zod17.z.string().optional().describe("Field in response containing the data array"),
830
- clearable: import_zod17.z.boolean().optional().describe("Allow clearing the selected value"),
831
- storeValue: import_zod17.z.boolean().optional().describe("Store option[valueKey] instead of the whole object")
1081
+ options: opt(import_zod17.z.array(import_zod17.z.unknown()).optional(), {
1082
+ description: "Inline array of option items",
1083
+ examples: [
1084
+ [
1085
+ {
1086
+ label: "Active",
1087
+ value: "active"
1088
+ }
1089
+ ]
1090
+ ]
1091
+ }),
1092
+ values: opt(import_zod17.z.array(import_zod17.z.unknown()).optional(), {
1093
+ description: "Alias for options (deprecated \u2014 prefer options)",
1094
+ deprecated: true
1095
+ }),
1096
+ resource: opt(import_zod17.z.string().optional(), {
1097
+ description: "Relative path to a resource.json used to populate the options",
1098
+ examples: [
1099
+ "./status.resource.json"
1100
+ ]
1101
+ }),
1102
+ labelKey: opt(import_zod17.z.string().optional(), {
1103
+ description: "Object key used as the display label",
1104
+ examples: [
1105
+ "label",
1106
+ "name"
1107
+ ]
1108
+ }),
1109
+ valueKey: opt(import_zod17.z.string().optional(), {
1110
+ description: "Object key used as the stored value",
1111
+ examples: [
1112
+ "value",
1113
+ "id"
1114
+ ]
1115
+ }),
1116
+ uri: opt(import_zod17.z.string().optional(), {
1117
+ description: "Remote endpoint URL for fetching options",
1118
+ examples: [
1119
+ "/api/statuses"
1120
+ ]
1121
+ }),
1122
+ dataField: opt(import_zod17.z.string().optional(), {
1123
+ description: "Field in the API response containing the data array",
1124
+ examples: [
1125
+ "data",
1126
+ "results"
1127
+ ]
1128
+ }),
1129
+ clearable: opt(import_zod17.z.boolean().optional(), {
1130
+ description: "Show a clear button to remove the selected value"
1131
+ }),
1132
+ storeValue: opt(import_zod17.z.boolean().optional(), {
1133
+ description: "Store option[valueKey] instead of the whole option object"
1134
+ })
1135
+ }).meta({
1136
+ title: "Select options",
1137
+ description: "Options for the select dropdown and multi-select (mutliSelect alias)"
832
1138
  });
833
1139
 
834
1140
  // ../crouton-core/src/lib/resource/field-input/types/string.options.ts
835
- var StringOptionsSchema = BaseOptionsSchema.extend({});
1141
+ var import_zod18 = require("zod");
1142
+ var NormalizedUniqueSchema = import_zod18.z.object({
1143
+ enabled: import_zod18.z.boolean(),
1144
+ scope: import_zod18.z.array(import_zod18.z.string()).optional(),
1145
+ caseInsensitive: import_zod18.z.boolean().optional(),
1146
+ message: import_zod18.z.string().optional()
1147
+ });
1148
+ var StringOptionsSchema = BaseOptionsSchema.extend({
1149
+ /** Async uniqueness validation: present when the column declares `unique`. */
1150
+ unique: NormalizedUniqueSchema.optional().meta({
1151
+ description: "Async uniqueness validation: present when the column declares `unique`."
1152
+ })
1153
+ }).meta({
1154
+ title: "String options",
1155
+ description: "Plain text input. All common options apply; no type-specific options."
1156
+ });
836
1157
 
837
1158
  // ../crouton-core/src/lib/resource/field-input/types/textarea.options.ts
838
- var TextareaOptionsSchema = BaseOptionsSchema.extend({});
1159
+ var TextareaOptionsSchema = BaseOptionsSchema.extend({}).meta({
1160
+ title: "Textarea options",
1161
+ description: "Multi-line text input. All common options apply; no type-specific options."
1162
+ });
839
1163
 
840
1164
  // ../crouton-core/src/lib/resource/field-input/types/toggle.options.ts
841
- var import_zod18 = require("zod");
1165
+ var import_zod19 = require("zod");
842
1166
  var ToggleOptionsSchema = BaseOptionsSchema.extend({
843
- options: import_zod18.z.array(import_zod18.z.unknown()).optional(),
844
- values: import_zod18.z.array(import_zod18.z.unknown()).optional(),
845
- labelKey: import_zod18.z.string().optional(),
846
- valueKey: import_zod18.z.string().optional(),
847
- storeValue: import_zod18.z.boolean().optional().describe("Store option[valueKey] instead of the whole object"),
848
- clearable: import_zod18.z.boolean().optional().describe("Clicking active button clears the value").default(true),
849
- color: import_zod18.z.string().optional().describe("Btn color for the active button").default("primary"),
850
- size: import_zod18.z.string().optional().describe("Btn size").default("sm")
1167
+ options: opt(import_zod19.z.array(import_zod19.z.unknown()).optional(), {
1168
+ description: "Array of option items rendered as toggle buttons",
1169
+ examples: [
1170
+ [
1171
+ {
1172
+ label: "Yes",
1173
+ value: true
1174
+ },
1175
+ {
1176
+ label: "No",
1177
+ value: false
1178
+ }
1179
+ ]
1180
+ ]
1181
+ }),
1182
+ values: opt(import_zod19.z.array(import_zod19.z.unknown()).optional(), {
1183
+ description: "Alias for options (deprecated \u2014 prefer options)",
1184
+ deprecated: true
1185
+ }),
1186
+ labelKey: opt(import_zod19.z.string().optional(), {
1187
+ description: "Object key used as the button label",
1188
+ examples: [
1189
+ "label"
1190
+ ]
1191
+ }),
1192
+ valueKey: opt(import_zod19.z.string().optional(), {
1193
+ description: "Object key used as the stored value",
1194
+ examples: [
1195
+ "value"
1196
+ ]
1197
+ }),
1198
+ storeValue: opt(import_zod19.z.boolean().optional(), {
1199
+ description: "Store option[valueKey] instead of the whole option object"
1200
+ }),
1201
+ clearable: opt(import_zod19.z.boolean().optional().default(true), {
1202
+ description: "Clicking the active button again clears the value",
1203
+ default: true
1204
+ }),
1205
+ color: opt(import_zod19.z.string().optional().default("primary"), {
1206
+ description: "Color token applied to the active button",
1207
+ examples: [
1208
+ "primary",
1209
+ "secondary"
1210
+ ],
1211
+ default: "primary"
1212
+ }),
1213
+ size: opt(import_zod19.z.string().optional().default("sm"), {
1214
+ description: "Size token for the buttons",
1215
+ examples: [
1216
+ "sm",
1217
+ "md",
1218
+ "lg"
1219
+ ],
1220
+ default: "sm"
1221
+ })
1222
+ }).meta({
1223
+ title: "Toggle options",
1224
+ description: "Options for the toggle button-group control"
851
1225
  });
852
1226
 
853
1227
  // ../crouton-core/src/lib/resource/field-input/registry.ts
@@ -967,42 +1341,42 @@ var fieldInputRegistry = /* @__PURE__ */ new Map([
967
1341
  ]);
968
1342
 
969
1343
  // ../crouton-core/src/lib/resource/FieldInput.schema.ts
970
- var RelationType = import_zod19.z.enum([
1344
+ var RelationType = import_zod20.z.enum([
971
1345
  "oneToOne",
972
1346
  "manyToOne",
973
1347
  "oneToMany",
974
1348
  "manyToMany"
975
1349
  ]);
976
- var DetailControlSchema = import_zod19.z.object({
977
- property: import_zod19.z.string(),
978
- type: import_zod19.z.string().optional(),
979
- options: import_zod19.z.record(import_zod19.z.string(), import_zod19.z.unknown()).optional(),
980
- hideLabel: import_zod19.z.boolean().optional(),
981
- width: import_zod19.z.string().optional()
1350
+ var DetailControlSchema = import_zod20.z.object({
1351
+ property: import_zod20.z.string(),
1352
+ type: import_zod20.z.string().optional(),
1353
+ options: import_zod20.z.record(import_zod20.z.string(), import_zod20.z.unknown()).optional(),
1354
+ hideLabel: import_zod20.z.boolean().optional(),
1355
+ width: import_zod20.z.string().optional()
982
1356
  });
983
- var DetailConfigSchema = import_zod19.z.object({
984
- layout: import_zod19.z.enum([
1357
+ var DetailConfigSchema = import_zod20.z.object({
1358
+ layout: import_zod20.z.enum([
985
1359
  "collapse",
986
1360
  "row"
987
1361
  ]),
988
- titleKey: import_zod19.z.string().optional(),
989
- controls: import_zod19.z.array(DetailControlSchema)
1362
+ titleKey: import_zod20.z.string().optional(),
1363
+ controls: import_zod20.z.array(DetailControlSchema)
990
1364
  });
991
- var FieldInputSchema = import_zod19.z.object({
992
- $schema: import_zod19.z.string().optional(),
993
- type: import_zod19.z.string().optional(),
994
- customRender: import_zod19.z.string().optional(),
1365
+ var FieldInputSchema = import_zod20.z.object({
1366
+ $schema: import_zod20.z.string().optional(),
1367
+ type: import_zod20.z.string().optional(),
1368
+ customRender: import_zod20.z.string().optional(),
995
1369
  /**
996
1370
  * Render format hint for the frontend (e.g. `"relation"` for sub-resource
997
1371
  * relations). When `format` is `"relation"`, `resource` must also be set.
998
1372
  */
999
- format: import_zod19.z.string().optional(),
1373
+ format: import_zod20.z.string().optional(),
1000
1374
  /**
1001
1375
  * Relative path to the child resource definition, e.g. `"./author.resource"`.
1002
1376
  * Only used when `format` is `"relation"`. Resolved at load time to inject
1003
1377
  * sub-resource URIs into `options`.
1004
1378
  */
1005
- resource: import_zod19.z.string().optional(),
1379
+ resource: import_zod20.z.string().optional(),
1006
1380
  /**
1007
1381
  * Cardinality of the relation. Used by the frontend to determine the correct
1008
1382
  * renderer and behaviour (e.g. single-select vs multi-select).
@@ -1011,11 +1385,11 @@ var FieldInputSchema = import_zod19.z.object({
1011
1385
  */
1012
1386
  relationType: RelationType.optional(),
1013
1387
  /** FK field on the child model pointing back to the parent, e.g. `"workId"`. Defaults to `${parentModel}Id`. */
1014
- foreignKey: import_zod19.z.string().optional(),
1388
+ foreignKey: import_zod20.z.string().optional(),
1015
1389
  /** Override the Prisma relation field name when it differs from the column id. */
1016
- relation: import_zod19.z.string().optional(),
1390
+ relation: import_zod20.z.string().optional(),
1017
1391
  /** Override the display order in form views. Lower values come first. */
1018
- position: import_zod19.z.number().optional(),
1392
+ position: import_zod20.z.number().optional(),
1019
1393
  /**
1020
1394
  * Value to pre-fill this field with on the create form. Injected into the
1021
1395
  * generated form view's JSON Schema as `properties[id].default`, which
@@ -1028,10 +1402,10 @@ var FieldInputSchema = import_zod19.z.object({
1028
1402
  * - `"$today"` — current date ISO string (date only, e.g. `"2025-03-14"`)
1029
1403
  * - `"$user"` — current user object (requires `defaults: { '$user': ... }` on `CroutonPlugin` config, or `useCrouton().setDefault('$user', ...)`)
1030
1404
  */
1031
- defaultValue: import_zod19.z.unknown().optional(),
1032
- options: import_zod19.z.union([
1405
+ defaultValue: import_zod20.z.unknown().optional(),
1406
+ options: import_zod20.z.union([
1033
1407
  RelationOptionsSchema,
1034
- import_zod19.z.unknown()
1408
+ import_zod20.z.unknown()
1035
1409
  ]).optional(),
1036
1410
  /** Nested array detail layout (renders via `detailFixed`). */
1037
1411
  detail: DetailConfigSchema.optional()
@@ -1082,16 +1456,16 @@ var normalizeLabel = /* @__PURE__ */ __name((obj) => {
1082
1456
  }, "normalizeLabel");
1083
1457
 
1084
1458
  // ../crouton-core/src/lib/resource/CalculatedColumn.schema.ts
1085
- var CalculatedColumnSchema = import_zod20.z.object({
1459
+ var CalculatedColumnSchema = import_zod21.z.object({
1086
1460
  /** Column identifier used in the response payload and UI schema. */
1087
- id: import_zod20.z.string(),
1461
+ id: import_zod21.z.string(),
1088
1462
  /** SQL alias — must match `id`. */
1089
- alias: import_zod20.z.string(),
1463
+ alias: import_zod21.z.string(),
1090
1464
  /** Human-readable label shown in the table header. */
1091
- label: import_zod20.z.string().optional(),
1092
- sqlExpression: import_zod20.z.string(),
1465
+ label: import_zod21.z.string().optional(),
1466
+ sqlExpression: import_zod21.z.string(),
1093
1467
  /** JSON schema / cast type for the computed value. Defaults to `"number"`. `"string"` skips CAST. */
1094
- type: import_zod20.z.enum([
1468
+ type: import_zod21.z.enum([
1095
1469
  "number",
1096
1470
  "boolean",
1097
1471
  "string"
@@ -1102,9 +1476,9 @@ var CalculatedColumnSchema = import_zod20.z.object({
1102
1476
  * `position: N` places this column before the element currently at index N.
1103
1477
  * Can also be set via `fieldInput.position` (takes precedence).
1104
1478
  */
1105
- position: import_zod20.z.number().optional(),
1106
- hiddenInTable: import_zod20.z.boolean().optional(),
1107
- hiddenInView: import_zod20.z.boolean().optional(),
1479
+ position: import_zod21.z.number().optional(),
1480
+ hiddenInTable: import_zod21.z.boolean().optional(),
1481
+ hiddenInView: import_zod21.z.boolean().optional(),
1108
1482
  /**
1109
1483
  * Field input options for rendering in form / view schemas.
1110
1484
  * Supports `colspan`, `format`, and any other options passed to the Control renderer.
@@ -1114,11 +1488,11 @@ var CalculatedColumnSchema = import_zod20.z.object({
1114
1488
  }).transform(normalizeLabel);
1115
1489
 
1116
1490
  // ../crouton-core/src/lib/resource/Column.ts
1117
- var import_zod22 = require("zod");
1491
+ var import_zod23 = require("zod");
1118
1492
 
1119
1493
  // ../crouton-core/src/lib/resource/ColumnType.schema.ts
1120
- var import_zod21 = require("zod");
1121
- var ColumnTypeShorthandSchema = import_zod21.z.enum([
1494
+ var import_zod22 = require("zod");
1495
+ var ColumnTypeShorthandSchema = import_zod22.z.enum([
1122
1496
  "string",
1123
1497
  "number",
1124
1498
  "integer",
@@ -1128,36 +1502,36 @@ var ColumnTypeShorthandSchema = import_zod21.z.enum([
1128
1502
  "object",
1129
1503
  "array"
1130
1504
  ]);
1131
- var JsonSchemaFragmentSchema = import_zod21.z.lazy(() => import_zod21.z.object({
1132
- type: import_zod21.z.union([
1133
- import_zod21.z.string(),
1134
- import_zod21.z.array(import_zod21.z.string()),
1135
- import_zod21.z.lazy(() => JsonSchemaFragmentSchema)
1505
+ var JsonSchemaFragmentSchema = import_zod22.z.lazy(() => import_zod22.z.object({
1506
+ type: import_zod22.z.union([
1507
+ import_zod22.z.string(),
1508
+ import_zod22.z.array(import_zod22.z.string()),
1509
+ import_zod22.z.lazy(() => JsonSchemaFragmentSchema)
1136
1510
  ]).optional(),
1137
- format: import_zod21.z.string().optional(),
1138
- enum: import_zod21.z.array(import_zod21.z.unknown()).optional(),
1139
- const: import_zod21.z.unknown().optional(),
1140
- nullable: import_zod21.z.boolean().optional(),
1141
- properties: import_zod21.z.record(import_zod21.z.string(), JsonSchemaFragmentSchema).optional(),
1142
- required: import_zod21.z.union([
1143
- import_zod21.z.array(import_zod21.z.string()),
1144
- import_zod21.z.boolean()
1511
+ format: import_zod22.z.string().optional(),
1512
+ enum: import_zod22.z.array(import_zod22.z.unknown()).optional(),
1513
+ const: import_zod22.z.unknown().optional(),
1514
+ nullable: import_zod22.z.boolean().optional(),
1515
+ properties: import_zod22.z.record(import_zod22.z.string(), JsonSchemaFragmentSchema).optional(),
1516
+ required: import_zod22.z.union([
1517
+ import_zod22.z.array(import_zod22.z.string()),
1518
+ import_zod22.z.boolean()
1145
1519
  ]).optional(),
1146
1520
  items: JsonSchemaFragmentSchema.optional(),
1147
- additionalProperties: import_zod21.z.union([
1148
- import_zod21.z.boolean(),
1521
+ additionalProperties: import_zod22.z.union([
1522
+ import_zod22.z.boolean(),
1149
1523
  JsonSchemaFragmentSchema
1150
1524
  ]).optional(),
1151
- title: import_zod21.z.string().optional(),
1152
- description: import_zod21.z.string().optional(),
1153
- default: import_zod21.z.unknown().optional(),
1154
- minimum: import_zod21.z.number().optional(),
1155
- maximum: import_zod21.z.number().optional(),
1156
- minLength: import_zod21.z.number().optional(),
1157
- maxLength: import_zod21.z.number().optional(),
1158
- pattern: import_zod21.z.string().optional()
1159
- }).catchall(import_zod21.z.unknown()));
1160
- var ColumnTypeSchema = import_zod21.z.union([
1525
+ title: import_zod22.z.string().optional(),
1526
+ description: import_zod22.z.string().optional(),
1527
+ default: import_zod22.z.unknown().optional(),
1528
+ minimum: import_zod22.z.number().optional(),
1529
+ maximum: import_zod22.z.number().optional(),
1530
+ minLength: import_zod22.z.number().optional(),
1531
+ maxLength: import_zod22.z.number().optional(),
1532
+ pattern: import_zod22.z.string().optional()
1533
+ }).catchall(import_zod22.z.unknown()));
1534
+ var ColumnTypeSchema = import_zod22.z.union([
1161
1535
  ColumnTypeShorthandSchema,
1162
1536
  JsonSchemaFragmentSchema
1163
1537
  ]);
@@ -1261,40 +1635,73 @@ var isObjectColumnType = /* @__PURE__ */ __name((type) => columnTypeName(type) =
1261
1635
  var isArrayColumnType = /* @__PURE__ */ __name((type) => columnTypeName(type) === "array", "isArrayColumnType");
1262
1636
 
1263
1637
  // ../crouton-core/src/lib/resource/Column.ts
1264
- var WhenConditionSchema = import_zod22.z.object({
1265
- field: import_zod22.z.string(),
1266
- eq: import_zod22.z.unknown().optional(),
1267
- neq: import_zod22.z.unknown().optional(),
1268
- exists: import_zod22.z.boolean().optional(),
1269
- notExists: import_zod22.z.boolean().optional()
1638
+ var UniqueConfigSchema = import_zod23.z.object({
1639
+ /** Other column ids the uniqueness is scoped by (composite unique). */
1640
+ scope: import_zod23.z.array(import_zod23.z.string()).optional(),
1641
+ /** Compare case-insensitively (maps to Prisma `mode: 'insensitive'`). */
1642
+ caseInsensitive: import_zod23.z.boolean().optional(),
1643
+ /** Message shown on the field when the value is already taken. */
1644
+ message: import_zod23.z.string().optional()
1645
+ });
1646
+ var UniqueSchema = import_zod23.z.union([
1647
+ import_zod23.z.boolean(),
1648
+ UniqueConfigSchema
1649
+ ]);
1650
+ var normalizeUnique = /* @__PURE__ */ __name((unique) => {
1651
+ if (!unique) return void 0;
1652
+ if (unique === true) return {
1653
+ enabled: true
1654
+ };
1655
+ return {
1656
+ enabled: true,
1657
+ ...unique
1658
+ };
1659
+ }, "normalizeUnique");
1660
+ var WhenConditionSchema = import_zod23.z.object({
1661
+ field: import_zod23.z.string(),
1662
+ eq: import_zod23.z.unknown().optional(),
1663
+ neq: import_zod23.z.unknown().optional(),
1664
+ exists: import_zod23.z.boolean().optional(),
1665
+ notExists: import_zod23.z.boolean().optional()
1270
1666
  });
1271
- var JsonColumnSchema = import_zod22.z.object({
1272
- id: import_zod22.z.string(),
1273
- column: import_zod22.z.string().optional(),
1274
- label: import_zod22.z.string().optional(),
1275
- hiddenInTable: import_zod22.z.boolean().default(false),
1276
- hiddenInForm: import_zod22.z.boolean().default(false),
1277
- hiddenInView: import_zod22.z.boolean().default(false),
1278
- sortable: import_zod22.z.boolean().default(false),
1279
- defaultSort: import_zod22.z.boolean().default(false),
1280
- searchable: import_zod22.z.boolean().default(false),
1281
- filterable: import_zod22.z.boolean().default(false),
1282
- createable: import_zod22.z.boolean().default(true),
1283
- updateable: import_zod22.z.boolean().default(true),
1284
- hideLabel: import_zod22.z.boolean().default(false),
1667
+ var JsonColumnSchema = import_zod23.z.object({
1668
+ id: import_zod23.z.string(),
1669
+ column: import_zod23.z.string().optional(),
1670
+ label: import_zod23.z.string().optional(),
1671
+ hiddenInTable: import_zod23.z.boolean().default(false),
1672
+ hiddenInForm: import_zod23.z.boolean().default(false),
1673
+ hiddenInView: import_zod23.z.boolean().default(false),
1674
+ sortable: import_zod23.z.boolean().default(false),
1675
+ defaultSort: import_zod23.z.boolean().default(false),
1676
+ searchable: import_zod23.z.boolean().default(false),
1677
+ filterable: import_zod23.z.boolean().default(false),
1678
+ createable: import_zod23.z.boolean().default(true),
1679
+ updateable: import_zod23.z.boolean().default(true),
1680
+ hideLabel: import_zod23.z.boolean().default(false),
1285
1681
  showWhen: WhenConditionSchema.optional(),
1286
1682
  hideWhen: WhenConditionSchema.optional(),
1287
1683
  disabledWhen: WhenConditionSchema.optional(),
1288
- displayKey: import_zod22.z.string().optional(),
1289
- sortId: import_zod22.z.string().optional(),
1684
+ displayKey: import_zod23.z.string().optional(),
1685
+ sortId: import_zod23.z.string().optional(),
1290
1686
  /**
1291
1687
  * Name of a shared enum in the project enum registry (`crouton.enums.json`).
1292
1688
  * At load time the loader injects that enum's `{ value, label }[]` into
1293
1689
  * `fieldInput.options.values`, so columns don't duplicate the option list.
1294
1690
  */
1295
- enum: import_zod22.z.string().optional(),
1296
- idField: import_zod22.z.boolean().default(false),
1297
- showInLookup: import_zod22.z.boolean().default(false),
1691
+ enum: import_zod23.z.string().optional(),
1692
+ idField: import_zod23.z.boolean().default(false),
1693
+ showInLookup: import_zod23.z.boolean().default(false),
1694
+ /**
1695
+ * Enforce that this column's value is unique across the resource. `true` is
1696
+ * simple single-column uniqueness; the object form allows a composite
1697
+ * `scope`, case-insensitive comparison, and a custom `message`.
1698
+ *
1699
+ * Requires a matching DB unique constraint (`@unique` in Prisma) to be
1700
+ * actually enforceable - the flag alone does not create one. The form uses it
1701
+ * to validate as-you-type against a lightweight check endpoint; the API maps
1702
+ * a Prisma unique violation back to this field on write.
1703
+ */
1704
+ unique: UniqueSchema.optional(),
1298
1705
  /**
1299
1706
  * Data type of the column, as a shorthand name (`"string"`, `"integer"`, …)
1300
1707
  * or a full JSON Schema fragment:
@@ -1325,12 +1732,12 @@ var JsonColumnSchema = import_zod22.z.object({
1325
1732
  * Ignored on the id column and on columns that are neither createable nor
1326
1733
  * updateable, since the form cannot supply a value for those.
1327
1734
  */
1328
- required: import_zod22.z.boolean().optional(),
1735
+ required: import_zod23.z.boolean().optional(),
1329
1736
  /**
1330
1737
  * @deprecated Use `type` instead. Retained because it is still consumed by
1331
1738
  * the boolean predicate and the schema-less sub-resource view builder.
1332
1739
  */
1333
- columnType: import_zod22.z.string().default("string"),
1740
+ columnType: import_zod23.z.string().default("string"),
1334
1741
  fieldInput: FieldInputSchema.optional(),
1335
1742
  /**
1336
1743
  * Optional per-context override for the read-only VIEW ui. Same shape as
@@ -1354,27 +1761,27 @@ var JsonColumnSchema = import_zod22.z.object({
1354
1761
  * Visibility (`hiddenInTable/Form/View`) on this column is inherited by all sub-columns as a
1355
1762
  * default; the referenced resource's own column visibility further restricts it.
1356
1763
  */
1357
- extend: import_zod22.z.string().optional(),
1764
+ extend: import_zod23.z.string().optional(),
1358
1765
  /**
1359
1766
  * Per-sub-column overrides when using `extend`.
1360
1767
  * Key: the virtual column id (`"{extendId}_{refColId}"`) or just the referenced column id.
1361
1768
  * Value: any `JsonColumn` fields to merge over the generated virtual column.
1362
1769
  */
1363
- columns: import_zod22.z.record(import_zod22.z.string(), import_zod22.z.record(import_zod22.z.string(), import_zod22.z.unknown())).optional()
1770
+ columns: import_zod23.z.record(import_zod23.z.string(), import_zod23.z.record(import_zod23.z.string(), import_zod23.z.unknown())).optional()
1364
1771
  });
1365
1772
 
1366
1773
  // ../crouton-core/src/lib/resource/ParentRef.schema.ts
1367
- var import_zod23 = require("zod");
1368
- var ParentRefSchema = import_zod23.z.object({
1774
+ var import_zod24 = require("zod");
1775
+ var ParentRefSchema = import_zod24.z.object({
1369
1776
  /** Route segment of the parent, e.g. `"group"`. */
1370
- route: import_zod23.z.string(),
1777
+ route: import_zod24.z.string(),
1371
1778
  /**
1372
1779
  * Name of the path parameter carrying the parent id, e.g. `"groupId"`.
1373
1780
  * Avoid `"id"` — that is the child's own id in `/:id` routes.
1374
1781
  */
1375
- param: import_zod23.z.string().default("parentId"),
1782
+ param: import_zod24.z.string().default("parentId"),
1376
1783
  /** Type of the parent id, used to coerce the path param. Defaults to `'string'`. */
1377
- idType: import_zod23.z.enum([
1784
+ idType: import_zod24.z.enum([
1378
1785
  "string",
1379
1786
  "number"
1380
1787
  ]).optional()
@@ -1382,37 +1789,37 @@ var ParentRefSchema = import_zod23.z.object({
1382
1789
  var resourceControllerPath = /* @__PURE__ */ __name((route, parent) => parent ? `${parent.route}/:${parent.param}/${route}` : route, "resourceControllerPath");
1383
1790
 
1384
1791
  // ../crouton-core/src/lib/resource/ResourceKind.ts
1385
- var import_zod24 = require("zod");
1386
- var ResourceKindSchema = import_zod24.z.enum([
1792
+ var import_zod25 = require("zod");
1793
+ var ResourceKindSchema = import_zod25.z.enum([
1387
1794
  "prisma",
1388
1795
  "custom"
1389
1796
  ]).default("prisma");
1390
1797
 
1391
1798
  // ../crouton-core/src/lib/resource/Sidebar.schema.ts
1392
- var import_zod25 = __toESM(require("zod"), 1);
1393
- var SidebarGroupSchema = import_zod25.default.object({
1799
+ var import_zod26 = __toESM(require("zod"), 1);
1800
+ var SidebarGroupSchema = import_zod26.default.object({
1394
1801
  /** Human-readable heading shown in the sidebar. Defaults to a title-cased version of the slug. */
1395
- label: import_zod25.default.string().optional(),
1802
+ label: import_zod26.default.string().optional(),
1396
1803
  /** Controls the order of this group among top-level sidebar items. */
1397
- position: import_zod25.default.number().optional()
1804
+ position: import_zod26.default.number().optional()
1398
1805
  });
1399
- var SidebarSchema = import_zod25.default.object({
1400
- hide: import_zod25.default.boolean().default(false),
1401
- position: import_zod25.default.number().optional(),
1402
- label: import_zod25.default.string().optional(),
1806
+ var SidebarSchema = import_zod26.default.object({
1807
+ hide: import_zod26.default.boolean().default(false),
1808
+ position: import_zod26.default.number().optional(),
1809
+ label: import_zod26.default.string().optional(),
1403
1810
  /**
1404
1811
  * Slug of the group this resource belongs to.
1405
1812
  * Must match a key in `sidebarGroups` in `crouton.json`.
1406
1813
  * Resources with the same `group` are nested under a shared collapsible section.
1407
1814
  */
1408
- group: import_zod25.default.string().optional()
1815
+ group: import_zod26.default.string().optional()
1409
1816
  });
1410
1817
 
1411
1818
  // ../crouton-core/src/lib/resource/TableAction.schema.ts
1412
- var import_zod26 = require("zod");
1413
- var ActionConditionSchema = import_zod26.z.object({
1819
+ var import_zod27 = require("zod");
1820
+ var ActionConditionSchema = import_zod27.z.object({
1414
1821
  /** Row field to evaluate. */
1415
- field: import_zod26.z.string(),
1822
+ field: import_zod27.z.string(),
1416
1823
  /**
1417
1824
  * Comparison operator. Defaults to `"eq"`.
1418
1825
  * - `eq` / `neq` — strict equality
@@ -1420,7 +1827,7 @@ var ActionConditionSchema = import_zod26.z.object({
1420
1827
  * - `exists` — field is not null / undefined / empty string
1421
1828
  * - `notExists` — field is null / undefined / empty string
1422
1829
  */
1423
- op: import_zod26.z.enum([
1830
+ op: import_zod27.z.enum([
1424
1831
  "eq",
1425
1832
  "neq",
1426
1833
  "gt",
@@ -1431,38 +1838,38 @@ var ActionConditionSchema = import_zod26.z.object({
1431
1838
  "notExists"
1432
1839
  ]).default("eq"),
1433
1840
  /** Comparison value. Not required for `exists` / `notExists`. */
1434
- value: import_zod26.z.unknown().optional()
1841
+ value: import_zod27.z.unknown().optional()
1435
1842
  });
1436
- var ActionSchema = import_zod26.z.object({
1437
- type: import_zod26.z.string(),
1843
+ var ActionSchema = import_zod27.z.object({
1844
+ type: import_zod27.z.string(),
1438
1845
  /** Unique identifier for the action. */
1439
- id: import_zod26.z.string(),
1846
+ id: import_zod27.z.string(),
1440
1847
  /** Human-readable button label. */
1441
- label: import_zod26.z.string(),
1848
+ label: import_zod27.z.string(),
1442
1849
  /** MDI icon name, e.g. `"mdi:open-in-new"`. */
1443
- icon: import_zod26.z.string().optional(),
1850
+ icon: import_zod27.z.string().optional(),
1444
1851
  /** Tooltip text. Falls back to `label` when omitted. */
1445
- tooltip: import_zod26.z.string().optional(),
1852
+ tooltip: import_zod27.z.string().optional(),
1446
1853
  condition: ActionConditionSchema.optional()
1447
1854
  });
1448
1855
  var JsonProcedureActionSchema = ActionSchema.extend({
1449
- type: import_zod26.z.literal("procedure").default("procedure"),
1856
+ type: import_zod27.z.literal("procedure").default("procedure"),
1450
1857
  /**
1451
1858
  * Filename (without extension) inside the resource's `actions/` directory
1452
1859
  * that exports the table-action procedure, e.g. `"syncZotero"`.
1453
1860
  */
1454
- procedure: import_zod26.z.string(),
1861
+ procedure: import_zod27.z.string(),
1455
1862
  /** HTTP method for the frontend. Defaults to `"post"`. */
1456
- method: import_zod26.z.string().optional().default("post"),
1863
+ method: import_zod27.z.string().optional().default("post"),
1457
1864
  /** Static query / body params passed to the endpoint. */
1458
- data: import_zod26.z.record(import_zod26.z.string(), import_zod26.z.unknown()).optional()
1865
+ data: import_zod27.z.record(import_zod27.z.string(), import_zod27.z.unknown()).optional()
1459
1866
  });
1460
1867
  var JsonLinkActionSchema = ActionSchema.extend({
1461
1868
  /** Optional condition evaluated per row. Button is hidden when the condition is false. */
1462
- type: import_zod26.z.literal("link").default("link"),
1869
+ type: import_zod27.z.literal("link").default("link"),
1463
1870
  /** URL to open. May contain `{env.VAR}` placeholders. */
1464
- href: import_zod26.z.string(),
1465
- blank: import_zod26.z.boolean().optional().default(true)
1871
+ href: import_zod27.z.string(),
1872
+ blank: import_zod27.z.boolean().optional().default(true)
1466
1873
  });
1467
1874
  var transformAction = /* @__PURE__ */ __name((action) => {
1468
1875
  const label = action.label;
@@ -1471,7 +1878,7 @@ var transformAction = /* @__PURE__ */ __name((action) => {
1471
1878
  ...action
1472
1879
  };
1473
1880
  }, "transformAction");
1474
- var JsonActionSchema = import_zod26.z.union([
1881
+ var JsonActionSchema = import_zod27.z.union([
1475
1882
  JsonProcedureActionSchema,
1476
1883
  JsonLinkActionSchema
1477
1884
  ]).transform(transformAction);
@@ -1513,14 +1920,14 @@ var registerResourceExtensions = /* @__PURE__ */ __name((map = {}) => Object.ent
1513
1920
  var getResourceExtensions = /* @__PURE__ */ __name(() => registry, "getResourceExtensions");
1514
1921
 
1515
1922
  // ../crouton-core/src/lib/resource/include.schema.ts
1516
- var import_zod27 = require("zod");
1517
- var JsonIncludeEntrySchema = import_zod27.z.lazy(() => import_zod27.z.union([
1518
- import_zod27.z.string(),
1519
- import_zod27.z.object({
1520
- relation: import_zod27.z.string(),
1521
- include: import_zod27.z.array(JsonIncludeEntrySchema).optional(),
1923
+ var import_zod28 = require("zod");
1924
+ var JsonIncludeEntrySchema = import_zod28.z.lazy(() => import_zod28.z.union([
1925
+ import_zod28.z.string(),
1926
+ import_zod28.z.object({
1927
+ relation: import_zod28.z.string(),
1928
+ include: import_zod28.z.array(JsonIncludeEntrySchema).optional(),
1522
1929
  /** Prisma-format orderBy clause applied to the included records. */
1523
- orderBy: import_zod27.z.record(import_zod27.z.string(), import_zod27.z.unknown()).optional()
1930
+ orderBy: import_zod28.z.record(import_zod28.z.string(), import_zod28.z.unknown()).optional()
1524
1931
  })
1525
1932
  ]));
1526
1933
 
@@ -1529,36 +1936,36 @@ var BASELINE_RESOURCE_VERSION = 1;
1529
1936
  var CURRENT_RESOURCE_VERSION = 1;
1530
1937
 
1531
1938
  // ../crouton-core/src/lib/data-source/Operations.schema.ts
1532
- var import_zod29 = require("zod");
1939
+ var import_zod30 = require("zod");
1533
1940
 
1534
1941
  // ../crouton-core/src/lib/data-source/Security.schema.ts
1535
- var import_zod28 = require("zod");
1536
- var SecuritySchema = import_zod28.z.union([
1537
- import_zod28.z.object({
1538
- public: import_zod28.z.literal(true)
1942
+ var import_zod29 = require("zod");
1943
+ var SecuritySchema = import_zod29.z.union([
1944
+ import_zod29.z.object({
1945
+ public: import_zod29.z.literal(true)
1539
1946
  }),
1540
- import_zod28.z.object({
1541
- guard: import_zod28.z.union([
1542
- import_zod28.z.string(),
1543
- import_zod28.z.array(import_zod28.z.string()).min(1)
1947
+ import_zod29.z.object({
1948
+ guard: import_zod29.z.union([
1949
+ import_zod29.z.string(),
1950
+ import_zod29.z.array(import_zod29.z.string()).min(1)
1544
1951
  ])
1545
1952
  })
1546
1953
  ]);
1547
1954
 
1548
1955
  // ../crouton-core/src/lib/data-source/Operations.schema.ts
1549
- var ExternalOpEntry = import_zod29.z.object({
1550
- uri: import_zod29.z.string(),
1551
- method: import_zod29.z.string().optional(),
1956
+ var ExternalOpEntry = import_zod30.z.object({
1957
+ uri: import_zod30.z.string(),
1958
+ method: import_zod30.z.string().optional(),
1552
1959
  security: SecuritySchema.optional()
1553
1960
  });
1554
- var OpEntry = import_zod29.z.union([
1555
- import_zod29.z.boolean(),
1961
+ var OpEntry = import_zod30.z.union([
1962
+ import_zod30.z.boolean(),
1556
1963
  ExternalOpEntry,
1557
- import_zod29.z.object({
1964
+ import_zod30.z.object({
1558
1965
  security: SecuritySchema.optional()
1559
1966
  })
1560
1967
  ]);
1561
- var JsonOperationsSchema = import_zod29.z.object({
1968
+ var JsonOperationsSchema = import_zod30.z.object({
1562
1969
  findAll: OpEntry.default(true),
1563
1970
  findOne: OpEntry.default(true),
1564
1971
  create: OpEntry.default(true),
@@ -1610,16 +2017,16 @@ var buildSubResourceOperations = /* @__PURE__ */ __name((ops, baseUri, idField =
1610
2017
  }, "buildSubResourceOperations");
1611
2018
 
1612
2019
  // ../crouton-core/src/lib/resource/ResourceJson.schema.ts
1613
- var JsonColumnsMapSchema = import_zod30.z.record(import_zod30.z.string(), JsonColumnSchema.omit({
2020
+ var JsonColumnsMapSchema = import_zod31.z.record(import_zod31.z.string(), JsonColumnSchema.omit({
1614
2021
  id: true
1615
- }).catchall(import_zod30.z.unknown()));
2022
+ }).catchall(import_zod31.z.unknown()));
1616
2023
  var ColumnsSchema = JsonColumnsMapSchema;
1617
- var JsonDisplaySchema = import_zod30.z.object({
1618
- mode: import_zod30.z.enum([
2024
+ var JsonDisplaySchema = import_zod31.z.object({
2025
+ mode: import_zod31.z.enum([
1619
2026
  "page",
1620
2027
  "modal"
1621
2028
  ]).default("modal"),
1622
- customComponent: import_zod30.z.string().nullable().optional().default(null)
2029
+ customComponent: import_zod31.z.string().nullable().optional().default(null)
1623
2030
  });
1624
2031
  var normalizeColumns = /* @__PURE__ */ __name((columns) => {
1625
2032
  if (!columns) return void 0;
@@ -1632,47 +2039,47 @@ var normalizeColumns = /* @__PURE__ */ __name((columns) => {
1632
2039
  label: col.label ?? labelFromId(col.id)
1633
2040
  }));
1634
2041
  }, "normalizeColumns");
1635
- var ResourceJsonShape = import_zod30.z.object({
2042
+ var ResourceJsonShape = import_zod31.z.object({
1636
2043
  /**
1637
2044
  * URL of the generated JSON Schema, for editor autocomplete/validation. Declared so the
1638
2045
  * key is *allowed* (not stripped, and not flagged by the very schema it points at).
1639
2046
  * Ignored at runtime.
1640
2047
  */
1641
- $schema: import_zod30.z.string().optional(),
2048
+ $schema: import_zod31.z.string().optional(),
1642
2049
  /**
1643
2050
  * resource.json shape version. Missing ⇒ baseline (see `./version`). Auto-migrated
1644
2051
  * toward `CURRENT_RESOURCE_VERSION` on load in the dev environment.
1645
2052
  */
1646
- schemaVersion: import_zod30.z.number().int().positive().optional(),
2053
+ schemaVersion: import_zod31.z.number().int().positive().optional(),
1647
2054
  /** When `true`, the resource lives in the repo but is NOT loaded/served (work in progress). */
1648
- draft: import_zod30.z.boolean().optional().default(false),
2055
+ draft: import_zod31.z.boolean().optional().default(false),
1649
2056
  /**
1650
2057
  * Where the data comes from. `prisma` (the default) is backed by a Prisma
1651
2058
  * model plus a `schema.ts`; `custom` is configuration only and the developer
1652
2059
  * supplies a `repository.ts`. See `./ResourceKind`.
1653
2060
  */
1654
2061
  kind: ResourceKindSchema,
1655
- name: import_zod30.z.string(),
1656
- route: import_zod30.z.string().optional(),
1657
- id: import_zod30.z.string().optional(),
2062
+ name: import_zod31.z.string(),
2063
+ route: import_zod31.z.string().optional(),
2064
+ id: import_zod31.z.string().optional(),
1658
2065
  /**
1659
2066
  * Prisma model name. Required when `kind` is `prisma` (enforced by the
1660
2067
  * refinement on `ResourceJsonSchema`), and must be absent when `kind` is
1661
2068
  * `custom` — there is no Prisma delegate to address.
1662
2069
  */
1663
- model: import_zod30.z.string().optional(),
1664
- tag: import_zod30.z.string().optional().default("Crouton"),
1665
- title: import_zod30.z.string().optional(),
1666
- table: import_zod30.z.string().optional(),
2070
+ model: import_zod31.z.string().optional(),
2071
+ tag: import_zod31.z.string().optional().default("Crouton"),
2072
+ title: import_zod31.z.string().optional(),
2073
+ table: import_zod31.z.string().optional(),
1667
2074
  /**
1668
2075
  * Type of the resource's primary key, used to coerce `:id` route params.
1669
2076
  * Written by codegen from the Prisma model; defaults to `'string'`.
1670
2077
  */
1671
- idType: import_zod30.z.enum([
2078
+ idType: import_zod31.z.enum([
1672
2079
  "string",
1673
2080
  "number"
1674
2081
  ]).optional(),
1675
- database: import_zod30.z.string().optional(),
2082
+ database: import_zod31.z.string().optional(),
1676
2083
  /**
1677
2084
  * Mount this resource under a parent route instead of at the top level —
1678
2085
  * see `./ParentRef.schema`. Only valid on a `kind: "custom"` resource.
@@ -1684,12 +2091,12 @@ var ResourceJsonShape = import_zod30.z.object({
1684
2091
  security: SecuritySchema.optional(),
1685
2092
  operations: JsonOperationsSchema.optional(),
1686
2093
  columns: ColumnsSchema.optional().default(ColumnsSchema.parse({})),
1687
- calculatedColumns: import_zod30.z.array(CalculatedColumnSchema).default([]),
1688
- actions: import_zod30.z.array(JsonActionSchema).default([]),
2094
+ calculatedColumns: import_zod31.z.array(CalculatedColumnSchema).default([]),
2095
+ actions: import_zod31.z.array(JsonActionSchema).default([]),
1689
2096
  /** Global table-level actions (no record id). Shown as toolbar buttons. */
1690
- tableActions: import_zod30.z.array(JsonActionSchema).default([]),
2097
+ tableActions: import_zod31.z.array(JsonActionSchema).default([]),
1691
2098
  /** Modal width when opening a form for this resource. */
1692
- modalSize: import_zod30.z.enum([
2099
+ modalSize: import_zod31.z.enum([
1693
2100
  "xs",
1694
2101
  "sm",
1695
2102
  "lg",
@@ -1702,7 +2109,7 @@ var ResourceJsonShape = import_zod30.z.object({
1702
2109
  * `{ "relation": "text_author", "include": ["author"] }` →
1703
2110
  * `include: { text_author: { include: { author: true } } }`
1704
2111
  */
1705
- include: import_zod30.z.array(JsonIncludeEntrySchema).default([]),
2112
+ include: import_zod31.z.array(JsonIncludeEntrySchema).default([]),
1706
2113
  /** Explicit layout for generated views (`form`, `view`, `table`). When absent, source-order grid is used. */
1707
2114
  layout: LayoutSchema.optional()
1708
2115
  });
@@ -1771,7 +2178,7 @@ var buildResourceJsonSchema = /* @__PURE__ */ __name(() => {
1771
2178
  schema.optional()
1772
2179
  ]));
1773
2180
  const shape = ext.size ? ResourceJsonShape.extend(extShape) : ResourceJsonShape;
1774
- return import_zod30.z.preprocess((raw) => {
2181
+ return import_zod31.z.preprocess((raw) => {
1775
2182
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
1776
2183
  const obj = raw;
1777
2184
  if (obj["kind"] === void 0 && obj["model"] !== void 0) {
@@ -2165,6 +2572,9 @@ var buildFormControl = /* @__PURE__ */ __name((col) => {
2165
2572
  const type = fieldInput?.type ?? defaultControlFormat(col);
2166
2573
  control.control(type, options).width("full");
2167
2574
  }
2575
+ if (col.unique) control.opt({
2576
+ unique: normalizeUnique(col.unique)
2577
+ });
2168
2578
  if (fieldInput?.customRender) control.setCustomRender(fieldInput?.customRender);
2169
2579
  if (col.hideLabel) control.hideLabel();
2170
2580
  if (col.label) control.label(col.label);
@@ -2258,7 +2668,7 @@ var injectCalculatedColumns = /* @__PURE__ */ __name((tableView, calculated) =>
2258
2668
  var injectCalculatedColumnsToView = /* @__PURE__ */ __name((viewConfig, calculated) => injectCalculatedColumnsIntoView(viewConfig, calculated, "view"), "injectCalculatedColumnsToView");
2259
2669
 
2260
2670
  // ../crouton-core/src/lib/view/view.builder.ts
2261
- var import_zod32 = require("zod");
2671
+ var import_zod33 = require("zod");
2262
2672
 
2263
2673
  // ../crouton-core/src/lib/view/column-type-schema.source.ts
2264
2674
  var optionValues = /* @__PURE__ */ __name((col) => {
@@ -2294,20 +2704,20 @@ var columnTypeSchemaSource = /* @__PURE__ */ __name((schemaCols) => {
2294
2704
  }, "columnTypeSchemaSource");
2295
2705
 
2296
2706
  // ../crouton-core/src/lib/view/json-schema.opts.ts
2297
- var import_zod31 = require("zod");
2707
+ var import_zod32 = require("zod");
2298
2708
  var isInstanceOf = /* @__PURE__ */ __name((zodSchema, className) => {
2299
- if (!(zodSchema instanceof import_zod31.z.ZodCustom)) return false;
2709
+ if (!(zodSchema instanceof import_zod32.z.ZodCustom)) return false;
2300
2710
  const result = zodSchema.safeParse(null);
2301
2711
  if (result.success) return false;
2302
2712
  const issue = result.error.issues[0];
2303
2713
  return issue?.expected === className || (issue?.message?.includes(className) ?? false);
2304
2714
  }, "isInstanceOf");
2305
2715
  var jsonSchemaOverride = /* @__PURE__ */ __name(({ zodSchema, jsonSchema }) => {
2306
- if (zodSchema instanceof import_zod31.z.ZodDate) {
2716
+ if (zodSchema instanceof import_zod32.z.ZodDate) {
2307
2717
  jsonSchema.type = "string";
2308
2718
  jsonSchema.format = "date-time";
2309
2719
  }
2310
- if (zodSchema instanceof import_zod31.z.ZodBigInt) {
2720
+ if (zodSchema instanceof import_zod32.z.ZodBigInt) {
2311
2721
  jsonSchema.type = "integer";
2312
2722
  }
2313
2723
  if (isInstanceOf(zodSchema, "Decimal") || isInstanceOf(zodSchema, "Decimal2")) {
@@ -2372,8 +2782,10 @@ var applyControlOverrides = /* @__PURE__ */ __name((base, ctrl) => {
2372
2782
  if (ctrl.width !== void 0) base.width(ctrl.width);
2373
2783
  if (ctrl.label !== void 0) base.label(ctrl.label);
2374
2784
  if (ctrl.hideLabel) base.hideLabel();
2375
- if (ctrl.type !== void 0 || ctrl.options !== void 0) {
2376
- base.control(ctrl.type ?? "text", ctrl.options);
2785
+ if (ctrl.type !== void 0) {
2786
+ base.control(ctrl.type, ctrl.options);
2787
+ } else if (ctrl.options !== void 0) {
2788
+ base.opt(ctrl.options);
2377
2789
  }
2378
2790
  return base;
2379
2791
  }, "applyControlOverrides");
@@ -2387,10 +2799,6 @@ var buildNodeInto = /* @__PURE__ */ __name((node, colMap, warn, seenIds) => {
2387
2799
  warn(`layout: unknown or hidden column id "${id}" \u2014 skipping`);
2388
2800
  continue;
2389
2801
  }
2390
- if (typeof ctrl !== "string") {
2391
- if (ctrl.colspan !== void 0 && ctrl.rowspan !== void 0) {
2392
- }
2393
- }
2394
2802
  seenIds.add(id);
2395
2803
  const control = buildFormControl(col);
2396
2804
  container.addControl(applyControlOverrides(control, ctrl));
@@ -2420,7 +2828,7 @@ var buildFormUiSchemaFromLayout = /* @__PURE__ */ __name((node, cols, warn = con
2420
2828
  }
2421
2829
  return root.build();
2422
2830
  }, "buildFormUiSchemaFromLayout");
2423
- var flattenLayoutControlIds = /* @__PURE__ */ __name((node) => {
2831
+ var flattenLayoutControlIds = /* @__PURE__ */ __name((node, warn = console.warn) => {
2424
2832
  const ids = [];
2425
2833
  const visit = /* @__PURE__ */ __name((n) => {
2426
2834
  for (const ctrl of n.controls ?? []) {
@@ -2428,6 +2836,7 @@ var flattenLayoutControlIds = /* @__PURE__ */ __name((node) => {
2428
2836
  if (typeof ctrl !== "string") {
2429
2837
  const c = ctrl;
2430
2838
  if (c.colspan !== void 0 || c.rowspan !== void 0) {
2839
+ warn(`layout: colspan/rowspan on table control "${id}" \u2014 ignored, table is flat`);
2431
2840
  }
2432
2841
  }
2433
2842
  ids.push(id);
@@ -2440,7 +2849,7 @@ var flattenLayoutControlIds = /* @__PURE__ */ __name((node) => {
2440
2849
 
2441
2850
  // ../crouton-core/src/lib/view/layout-table-order.ts
2442
2851
  var orderTableColumnsFromLayout = /* @__PURE__ */ __name((view, node, warn = console.warn) => {
2443
- const orderedIds = flattenLayoutControlIds(node);
2852
+ const orderedIds = flattenLayoutControlIds(node, warn);
2444
2853
  if (!orderedIds.length) return view;
2445
2854
  const elements = view.ui_schema?.elements ?? [];
2446
2855
  const columns = view.columns ?? [];
@@ -2451,7 +2860,7 @@ var orderTableColumnsFromLayout = /* @__PURE__ */ __name((view, node, warn = con
2451
2860
  el
2452
2861
  ];
2453
2862
  }));
2454
- const columnById = new Map(columns.map((c) => [
2863
+ const columnById2 = new Map(columns.map((c) => [
2455
2864
  c.id,
2456
2865
  c
2457
2866
  ]));
@@ -2469,14 +2878,14 @@ var orderTableColumnsFromLayout = /* @__PURE__ */ __name((view, node, warn = con
2469
2878
  seen.add(id);
2470
2879
  const el = elementById.get(id);
2471
2880
  if (el) reorderedElements.push(el);
2472
- const col = columnById.get(id);
2881
+ const col = columnById2.get(id);
2473
2882
  if (col) reorderedColumns.push(col);
2474
2883
  }
2475
2884
  for (const el of elements) {
2476
2885
  const id = el.scope?.replace("#/properties/", "");
2477
2886
  if (id && !seen.has(id)) {
2478
2887
  reorderedElements.push(el);
2479
- const col = columnById.get(id);
2888
+ const col = columnById2.get(id);
2480
2889
  if (col) reorderedColumns.push(col);
2481
2890
  }
2482
2891
  }
@@ -2550,7 +2959,7 @@ var zodSchemaSource = /* @__PURE__ */ __name((schema) => (schemaCols) => {
2550
2959
  ]));
2551
2960
  const picked = schema.pick(mask);
2552
2961
  try {
2553
- return (0, import_zod32.toJSONSchema)(picked, {
2962
+ return (0, import_zod33.toJSONSchema)(picked, {
2554
2963
  target: "draft-07",
2555
2964
  ...jsonSchemaOpts
2556
2965
  });
@@ -2764,81 +3173,81 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
2764
3173
  }, "buildViewsFromColumns");
2765
3174
 
2766
3175
  // ../crouton-core/src/lib/view/view.schema.ts
2767
- var import_zod33 = require("zod");
2768
- var ViewColumnConfigSchema = import_zod33.z.object({
2769
- id: import_zod33.z.string(),
2770
- label: import_zod33.z.string().optional(),
2771
- sortable: import_zod33.z.boolean().optional(),
2772
- searchable: import_zod33.z.boolean().optional(),
3176
+ var import_zod34 = require("zod");
3177
+ var ViewColumnConfigSchema = import_zod34.z.object({
3178
+ id: import_zod34.z.string(),
3179
+ label: import_zod34.z.string().optional(),
3180
+ sortable: import_zod34.z.boolean().optional(),
3181
+ searchable: import_zod34.z.boolean().optional(),
2773
3182
  fieldInput: FieldInputSchema.optional()
2774
3183
  });
2775
- var ViewConfigSchema = import_zod33.z.object({
3184
+ var ViewConfigSchema = import_zod34.z.object({
2776
3185
  // TODO why we have both?
2777
- json_schema: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown()).optional(),
2778
- ui_schema: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown()).optional(),
3186
+ json_schema: import_zod34.z.record(import_zod34.z.string(), import_zod34.z.unknown()).optional(),
3187
+ ui_schema: import_zod34.z.record(import_zod34.z.string(), import_zod34.z.unknown()).optional(),
2779
3188
  // TODO why we have both?
2780
- ui: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown()).optional(),
2781
- data: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown()).optional(),
2782
- columns: import_zod33.z.array(ViewColumnConfigSchema),
2783
- defaultSort: import_zod33.z.string().optional()
3189
+ ui: import_zod34.z.record(import_zod34.z.string(), import_zod34.z.unknown()).optional(),
3190
+ data: import_zod34.z.record(import_zod34.z.string(), import_zod34.z.unknown()).optional(),
3191
+ columns: import_zod34.z.array(ViewColumnConfigSchema),
3192
+ defaultSort: import_zod34.z.string().optional()
2784
3193
  });
2785
3194
  var viewDefList = [
2786
3195
  "table",
2787
3196
  "view",
2788
3197
  "form"
2789
3198
  ];
2790
- var ViewDefEnum = import_zod33.z.enum(viewDefList);
3199
+ var ViewDefEnum = import_zod34.z.enum(viewDefList);
2791
3200
 
2792
3201
  // ../crouton-core/src/lib/compile/enum-registry.ts
2793
- var import_zod34 = require("zod");
2794
- var EnumEntrySchema = import_zod34.z.object({
2795
- value: import_zod34.z.unknown(),
2796
- label: import_zod34.z.string()
3202
+ var import_zod35 = require("zod");
3203
+ var EnumEntrySchema = import_zod35.z.object({
3204
+ value: import_zod35.z.unknown(),
3205
+ label: import_zod35.z.string()
2797
3206
  });
2798
- var EnumRegistrySchema = import_zod34.z.record(import_zod34.z.string(), import_zod34.z.array(EnumEntrySchema)).default({});
3207
+ var EnumRegistrySchema = import_zod35.z.record(import_zod35.z.string(), import_zod35.z.array(EnumEntrySchema)).default({});
2799
3208
 
2800
3209
  // ../crouton-core/src/lib/compile/relation-type.ts
2801
- var import_zod35 = require("zod");
3210
+ var import_zod36 = require("zod");
2802
3211
 
2803
3212
  // ../crouton-core/src/lib/compile/compiled-resource.schema.ts
2804
- var import_zod42 = require("zod");
3213
+ var import_zod43 = require("zod");
2805
3214
 
2806
3215
  // ../crouton-core/src/lib/compile/compiled-sub-resource.schema.ts
2807
- var import_zod38 = require("zod");
3216
+ var import_zod39 = require("zod");
2808
3217
 
2809
3218
  // ../crouton-core/src/lib/data-source/DataSourceSchema.ts
2810
- var import_zod36 = __toESM(require("zod"), 1);
2811
- var DataSourceShape = import_zod36.default.object({
3219
+ var import_zod37 = __toESM(require("zod"), 1);
3220
+ var DataSourceShape = import_zod37.default.object({
2812
3221
  /** Datasource name (folder + `name` in data-source.json). */
2813
- name: import_zod36.default.string(),
3222
+ name: import_zod37.default.string(),
2814
3223
  /**
2815
3224
  * Env var holding the connection URL. Required for Prisma adapters; optional
2816
3225
  * for custom adapters whose connection config may differ (secrets file, SDK
2817
3226
  * key, etc.).
2818
3227
  */
2819
- urlEnv: import_zod36.default.string().optional(),
3228
+ urlEnv: import_zod37.default.string().optional(),
2820
3229
  /** Import path for this datasource's generated Zod types (for resource `schema.ts`). */
2821
- generatedTypesImport: import_zod36.default.string().optional(),
3230
+ generatedTypesImport: import_zod37.default.string().optional(),
2822
3231
  /** Datasource type tag. Default `postgres`. */
2823
- type: import_zod36.default.string().default("postgres"),
3232
+ type: import_zod37.default.string().default("postgres"),
2824
3233
  /** Mark this as the default datasource. Default `false`. */
2825
- default: import_zod36.default.boolean().default(false),
3234
+ default: import_zod37.default.boolean().default(false),
2826
3235
  /**
2827
3236
  * Adapter kind. `"prisma"` (default) expects `index.ts` to default-export a
2828
3237
  * `PrismaClient`; `"custom"` expects it to default-export a `DataSourceAdapter`.
2829
3238
  */
2830
- adapter: import_zod36.default.enum([
3239
+ adapter: import_zod37.default.enum([
2831
3240
  "prisma",
2832
3241
  "custom"
2833
3242
  ]).default("prisma"),
2834
3243
  /** Prisma schema path, relative to project root. Default `prisma/<name>/schema.prisma`. */
2835
- prismaSchema: import_zod36.default.string().optional(),
3244
+ prismaSchema: import_zod37.default.string().optional(),
2836
3245
  /** Prisma config path, relative to project root. Default `prisma/<name>/prisma.config.ts`. */
2837
- prismaConfig: import_zod36.default.string().optional(),
3246
+ prismaConfig: import_zod37.default.string().optional(),
2838
3247
  /** zod-prisma-types output dir, relative to project root. Default `generated/<name>/src`. */
2839
- zodOutput: import_zod36.default.string().optional(),
3248
+ zodOutput: import_zod37.default.string().optional(),
2840
3249
  /** Prisma client output dir, relative to project root. Default `generated/<name>/client`. */
2841
- clientOutput: import_zod36.default.string().optional()
3250
+ clientOutput: import_zod37.default.string().optional()
2842
3251
  });
2843
3252
  var transformDataSource = /* @__PURE__ */ __name((data) => {
2844
3253
  const name = data.name ?? "name";
@@ -2854,88 +3263,88 @@ var transformDataSource = /* @__PURE__ */ __name((data) => {
2854
3263
  var DataSourceSchema = DataSourceShape.transform(transformDataSource);
2855
3264
 
2856
3265
  // ../crouton-core/src/lib/compile/value-label-column.ts
2857
- var import_zod37 = require("zod");
2858
- var ValueSchema = import_zod37.z.object({
2859
- value: import_zod37.z.unknown(),
2860
- label: import_zod37.z.string()
3266
+ var import_zod38 = require("zod");
3267
+ var ValueSchema = import_zod38.z.object({
3268
+ value: import_zod38.z.unknown(),
3269
+ label: import_zod38.z.string()
2861
3270
  });
2862
- var ValueLabelColumnSchema = import_zod37.z.object({
2863
- field: import_zod37.z.string(),
2864
- values: import_zod37.z.array(ValueSchema).default([]),
2865
- enumName: import_zod37.z.string().optional()
3271
+ var ValueLabelColumnSchema = import_zod38.z.object({
3272
+ field: import_zod38.z.string(),
3273
+ values: import_zod38.z.array(ValueSchema).default([]),
3274
+ enumName: import_zod38.z.string().optional()
2866
3275
  });
2867
3276
 
2868
3277
  // ../crouton-core/src/lib/compile/compiled-sub-resource.schema.ts
2869
- var CompiledSubResourceConfigSchema = import_zod38.z.object({
2870
- column: import_zod38.z.string(),
2871
- relation: import_zod38.z.string(),
2872
- childRoute: import_zod38.z.string(),
2873
- childKind: import_zod38.z.enum([
3278
+ var CompiledSubResourceConfigSchema = import_zod39.z.object({
3279
+ column: import_zod39.z.string(),
3280
+ relation: import_zod39.z.string(),
3281
+ childRoute: import_zod39.z.string(),
3282
+ childKind: import_zod39.z.enum([
2874
3283
  "prisma",
2875
3284
  "custom"
2876
3285
  ]).default("prisma"),
2877
- childDir: import_zod38.z.string().optional(),
2878
- childModel: import_zod38.z.string(),
2879
- foreignKey: import_zod38.z.string(),
2880
- name: import_zod38.z.string().optional(),
2881
- title: import_zod38.z.string().optional(),
2882
- idField: import_zod38.z.string().optional(),
2883
- idType: import_zod38.z.enum([
3286
+ childDir: import_zod39.z.string().optional(),
3287
+ childModel: import_zod39.z.string(),
3288
+ foreignKey: import_zod39.z.string(),
3289
+ name: import_zod39.z.string().optional(),
3290
+ title: import_zod39.z.string().optional(),
3291
+ idField: import_zod39.z.string().optional(),
3292
+ idType: import_zod39.z.enum([
2884
3293
  "string",
2885
3294
  "number"
2886
3295
  ]).optional(),
2887
- views: import_zod38.z.record(import_zod38.z.string(), ViewConfigSchema).optional(),
3296
+ views: import_zod39.z.record(import_zod39.z.string(), ViewConfigSchema).optional(),
2888
3297
  operations: JsonOperationsSchema,
2889
- actions: import_zod38.z.array(JsonActionSchema).optional(),
2890
- modalSize: import_zod38.z.enum([
3298
+ actions: import_zod39.z.array(JsonActionSchema).optional(),
3299
+ modalSize: import_zod39.z.enum([
2891
3300
  "xs",
2892
3301
  "sm",
2893
3302
  "lg",
2894
3303
  "xl"
2895
3304
  ]).optional(),
2896
3305
  display: JsonDisplaySchema.optional(),
2897
- include: import_zod38.z.array(JsonIncludeEntrySchema).optional(),
2898
- calculatedColumns: import_zod38.z.array(CalculatedColumnSchema).optional(),
2899
- includeInFindOne: import_zod38.z.boolean().optional(),
2900
- hiddenInTable: import_zod38.z.boolean().optional(),
2901
- findOneOrderBy: import_zod38.z.record(import_zod38.z.string(), import_zod38.z.unknown()).optional(),
2902
- valueLabelColumns: import_zod38.z.array(ValueLabelColumnSchema).optional(),
2903
- relationType: import_zod38.z.enum([
3306
+ include: import_zod39.z.array(JsonIncludeEntrySchema).optional(),
3307
+ calculatedColumns: import_zod39.z.array(CalculatedColumnSchema).optional(),
3308
+ includeInFindOne: import_zod39.z.boolean().optional(),
3309
+ hiddenInTable: import_zod39.z.boolean().optional(),
3310
+ findOneOrderBy: import_zod39.z.record(import_zod39.z.string(), import_zod39.z.unknown()).optional(),
3311
+ valueLabelColumns: import_zod39.z.array(ValueLabelColumnSchema).optional(),
3312
+ relationType: import_zod39.z.enum([
2904
3313
  "oneToMany",
2905
3314
  "manyToOne"
2906
3315
  ]).optional()
2907
3316
  });
2908
3317
 
2909
3318
  // ../crouton-core/src/lib/compile/definition.schema.ts
2910
- var import_zod40 = require("zod");
3319
+ var import_zod41 = require("zod");
2911
3320
 
2912
3321
  // ../crouton-core/src/lib/compile/schema-input.ts
2913
- var import_zod39 = require("zod");
2914
- var JsonSchemaInputSchema = import_zod39.z.object({
2915
- type: import_zod39.z.literal("object"),
2916
- properties: import_zod39.z.record(import_zod39.z.string(), import_zod39.z.unknown()),
2917
- required: import_zod39.z.array(import_zod39.z.string()).optional(),
2918
- additionalProperties: import_zod39.z.boolean().optional()
3322
+ var import_zod40 = require("zod");
3323
+ var JsonSchemaInputSchema = import_zod40.z.object({
3324
+ type: import_zod40.z.literal("object"),
3325
+ properties: import_zod40.z.record(import_zod40.z.string(), import_zod40.z.unknown()),
3326
+ required: import_zod40.z.array(import_zod40.z.string()).optional(),
3327
+ additionalProperties: import_zod40.z.boolean().optional()
2919
3328
  });
2920
- var SchemaInputSchema = import_zod39.z.union([
2921
- import_zod39.z.custom((val) => val instanceof import_zod39.ZodObject),
3329
+ var SchemaInputSchema = import_zod40.z.union([
3330
+ import_zod40.z.custom((val) => val instanceof import_zod40.ZodObject),
2922
3331
  JsonSchemaInputSchema
2923
3332
  ]);
2924
3333
 
2925
3334
  // ../crouton-core/src/lib/compile/definition.schema.ts
2926
- var ExternalOperationDefSchema = import_zod40.z.object({
2927
- uri: import_zod40.z.string(),
2928
- method: import_zod40.z.string().optional()
3335
+ var ExternalOperationDefSchema = import_zod41.z.object({
3336
+ uri: import_zod41.z.string(),
3337
+ method: import_zod41.z.string().optional()
2929
3338
  });
2930
- var OperationDefSchema = import_zod40.z.union([
2931
- import_zod40.z.literal(true),
3339
+ var OperationDefSchema = import_zod41.z.union([
3340
+ import_zod41.z.literal(true),
2932
3341
  ExternalOperationDefSchema,
2933
- import_zod40.z.object({
3342
+ import_zod41.z.object({
2934
3343
  schema: SchemaInputSchema.optional(),
2935
3344
  security: SecuritySchema.optional()
2936
3345
  })
2937
3346
  ]);
2938
- var ResourceDefinitionSchema = import_zod40.z.object({
3347
+ var ResourceDefinitionSchema = import_zod41.z.object({
2939
3348
  findAll: OperationDefSchema.optional(),
2940
3349
  findOne: OperationDefSchema.optional(),
2941
3350
  create: OperationDefSchema.optional(),
@@ -2943,93 +3352,348 @@ var ResourceDefinitionSchema = import_zod40.z.object({
2943
3352
  patch: OperationDefSchema.optional(),
2944
3353
  delete: OperationDefSchema.optional()
2945
3354
  });
2946
- var DefinitionCallbackSchema = import_zod40.z.function({
3355
+ var DefinitionCallbackSchema = import_zod41.z.function({
2947
3356
  input: [],
2948
3357
  output: ResourceDefinitionSchema
2949
3358
  });
2950
- var DefinitionSchema = import_zod40.z.union([
3359
+ var DefinitionSchema = import_zod41.z.union([
2951
3360
  ResourceDefinitionSchema,
2952
3361
  DefinitionCallbackSchema
2953
3362
  ]);
2954
3363
 
2955
3364
  // ../crouton-core/src/lib/compile/lookup.schema.ts
2956
- var import_zod41 = require("zod");
2957
- var LookupSchema = import_zod41.z.object({
2958
- key: import_zod41.z.string(),
2959
- label: import_zod41.z.string().optional()
3365
+ var import_zod42 = require("zod");
3366
+ var LookupSchema = import_zod42.z.object({
3367
+ key: import_zod42.z.string(),
3368
+ label: import_zod42.z.string().optional()
2960
3369
  });
2961
3370
 
2962
3371
  // ../crouton-core/src/lib/compile/compiled-resource.schema.ts
2963
3372
  var CompiledResourceSchema = ResourceJsonShape.extend({
2964
- route: import_zod42.z.string(),
2965
- valueLabelColumns: import_zod42.z.array(ValueLabelColumnSchema).default([]),
2966
- subResources: import_zod42.z.lazy(() => import_zod42.z.array(CompiledSubResourceConfigSchema)).default([]),
3373
+ route: import_zod43.z.string(),
3374
+ valueLabelColumns: import_zod43.z.array(ValueLabelColumnSchema).default([]),
3375
+ subResources: import_zod43.z.lazy(() => import_zod43.z.array(CompiledSubResourceConfigSchema)).default([]),
2967
3376
  lookup: LookupSchema.optional(),
2968
- views: import_zod42.z.record(import_zod42.z.string(), ViewConfigSchema),
3377
+ views: import_zod43.z.record(import_zod43.z.string(), ViewConfigSchema),
2969
3378
  definition: DefinitionSchema,
2970
- idField: import_zod42.z.string(),
2971
- idType: import_zod42.z.enum([
3379
+ idField: import_zod43.z.string(),
3380
+ idType: import_zod43.z.enum([
2972
3381
  "string",
2973
3382
  "number"
2974
3383
  ]).default("string")
2975
3384
  });
2976
3385
 
3386
+ // ../crouton-core/src/lib/compile/crud-config.ts
3387
+ var resolveDefinition = /* @__PURE__ */ __name((config) => {
3388
+ const def2 = config.definition;
3389
+ return typeof def2 === "function" ? def2() : def2;
3390
+ }, "resolveDefinition");
3391
+ var isOperationEnabled = /* @__PURE__ */ __name((def2, op) => def2[op] != null, "isOperationEnabled");
3392
+ var schemaFor = /* @__PURE__ */ __name((def2, op) => {
3393
+ const entry = def2[op];
3394
+ if (!entry || entry === true) return void 0;
3395
+ return entry.schema;
3396
+ }, "schemaFor");
3397
+ var isOperationExternal = /* @__PURE__ */ __name((def2, op) => {
3398
+ const entry = def2[op];
3399
+ return typeof entry === "object" && entry !== null && "uri" in entry;
3400
+ }, "isOperationExternal");
3401
+ var externalRouteFor = /* @__PURE__ */ __name((def2, op) => {
3402
+ const entry = def2[op];
3403
+ if (typeof entry === "object" && entry !== null && "uri" in entry) {
3404
+ return entry.uri;
3405
+ }
3406
+ return void 0;
3407
+ }, "externalRouteFor");
3408
+
2977
3409
  // ../crouton-core/src/lib/compile/to-json-schema.ts
2978
- var import_zod43 = require("zod");
3410
+ var import_zod44 = require("zod");
3411
+ var isZodSchema = /* @__PURE__ */ __name((schema) => schema instanceof import_zod44.ZodObject, "isZodSchema");
3412
+ var dropNullableFromRequired2 = /* @__PURE__ */ __name((jsonSchema) => {
3413
+ const { properties, required } = jsonSchema;
3414
+ if (!properties || !Array.isArray(required)) return;
3415
+ jsonSchema.required = required.filter((key) => !isNullableProperty(properties[key]));
3416
+ }, "dropNullableFromRequired");
3417
+ var toJsonSchema = /* @__PURE__ */ __name((schema) => {
3418
+ if (isZodSchema(schema)) {
3419
+ let jsonSchema;
3420
+ try {
3421
+ jsonSchema = (0, import_zod44.toJSONSchema)(schema, {
3422
+ target: "openApi3",
3423
+ ...jsonSchemaOpts
3424
+ });
3425
+ } catch {
3426
+ return {
3427
+ type: "object"
3428
+ };
3429
+ }
3430
+ dropNullableFromRequired2(jsonSchema);
3431
+ return jsonSchema;
3432
+ }
3433
+ return schema;
3434
+ }, "toJsonSchema");
3435
+
3436
+ // ../crouton-core/src/lib/compile/payload-builders.ts
3437
+ var pickExtensions = /* @__PURE__ */ __name((config) => Object.fromEntries([
3438
+ ...getResourceExtensions().keys()
3439
+ ].filter((k) => config[k] !== void 0).map((k) => [
3440
+ k,
3441
+ config[k]
3442
+ ])), "pickExtensions");
3443
+ var resolveEnvPlaceholders = /* @__PURE__ */ __name((value) => value.replace(/\{env\.([^}]+)\}/g, (match, varName) => (
3444
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3445
+ (typeof globalThis.process !== "undefined" ? globalThis.process?.env?.[varName] : void 0) ?? match
3446
+ )), "resolveEnvPlaceholders");
3447
+ var RESOURCE_OPS = [
3448
+ "findAll",
3449
+ "findOne",
3450
+ "create",
3451
+ "update",
3452
+ "patch",
3453
+ "delete"
3454
+ ];
3455
+ var OP_SUFFIX = {
3456
+ findAll: "",
3457
+ findOne: "/{id}",
3458
+ create: "",
3459
+ update: "/{id}",
3460
+ patch: "/{id}",
3461
+ delete: "/{id}"
3462
+ };
3463
+ var OP_METHOD = {
3464
+ findAll: "get",
3465
+ findOne: "get",
3466
+ create: "post",
3467
+ update: "put",
3468
+ patch: "patch",
3469
+ delete: "delete"
3470
+ };
3471
+ var buildResourceOperations = /* @__PURE__ */ __name((definition, baseUri) => Object.fromEntries(RESOURCE_OPS.filter((op) => isOperationEnabled(definition, op)).map((op) => {
3472
+ const extRoute = externalRouteFor(definition, op);
3473
+ const uri = extRoute ? resolveEnvPlaceholders(extRoute) : `${baseUri}${OP_SUFFIX[op]}`;
3474
+ const entry = definition[op];
3475
+ const method = (extRoute && typeof entry === "object" && entry !== null && "method" in entry ? entry.method : void 0) ?? OP_METHOD[op];
3476
+ return [
3477
+ op,
3478
+ {
3479
+ uri,
3480
+ method
3481
+ }
3482
+ ];
3483
+ })), "buildResourceOperations");
3484
+ var _resolveActions = /* @__PURE__ */ __name((baseUrl, actions) => {
3485
+ if (!actions?.length) return [];
3486
+ return actions.map((a) => a.type === "link" ? {
3487
+ ...a,
3488
+ href: resolveEnvPlaceholders(a.href)
3489
+ } : {
3490
+ ...a,
3491
+ uri: baseUrl.map((b) => b === "{actionId}" ? a.id : b).join("/"),
3492
+ method: a.method ?? "post"
3493
+ });
3494
+ }, "_resolveActions");
3495
+ var resolveActions = /* @__PURE__ */ __name((baseUrl, actions) => _resolveActions([
3496
+ baseUrl,
3497
+ "procedure",
3498
+ "{actionId}",
3499
+ "{id}"
3500
+ ], actions), "resolveActions");
3501
+ var resolveTableActions = /* @__PURE__ */ __name((baseUrl, actions) => _resolveActions([
3502
+ baseUrl,
3503
+ "table-action",
3504
+ "{actionId}"
3505
+ ], actions), "resolveTableActions");
3506
+ var buildDefinitionPayload = /* @__PURE__ */ __name((config) => {
3507
+ const { route, name, tag, idType = "string" } = config;
3508
+ const definition = resolveDefinition(config);
3509
+ const listSchema = schemaFor(definition, "findAll");
3510
+ const oneSchema = schemaFor(definition, "findOne") ?? listSchema;
3511
+ const createSchema = schemaFor(definition, "create");
3512
+ const updateSchema = schemaFor(definition, "update");
3513
+ const patchSchema = schemaFor(definition, "patch");
3514
+ const operations = [
3515
+ "findAll",
3516
+ "findOne",
3517
+ "create",
3518
+ "update",
3519
+ "patch",
3520
+ "delete"
3521
+ ].filter((op) => isOperationEnabled(definition, op));
3522
+ return {
3523
+ name,
3524
+ route,
3525
+ idType,
3526
+ tag,
3527
+ operations,
3528
+ display: config.display,
3529
+ schemas: {
3530
+ ...listSchema && {
3531
+ findAll: toJsonSchema(listSchema)
3532
+ },
3533
+ ...oneSchema && {
3534
+ findOne: toJsonSchema(oneSchema)
3535
+ },
3536
+ ...createSchema && {
3537
+ create: toJsonSchema(createSchema)
3538
+ },
3539
+ ...updateSchema && {
3540
+ update: toJsonSchema(updateSchema)
3541
+ },
3542
+ ...patchSchema && {
3543
+ patch: toJsonSchema(patchSchema)
3544
+ }
3545
+ },
3546
+ ...pickExtensions(config)
3547
+ };
3548
+ }, "buildDefinitionPayload");
3549
+ var buildResourceJsonPayload = /* @__PURE__ */ __name((config, baseUrl) => {
3550
+ const { route } = config;
3551
+ const definition = resolveDefinition(config);
3552
+ const uri = `${baseUrl}/${route}`;
3553
+ const operations = Object.fromEntries(RESOURCE_OPS.map((op) => [
3554
+ op,
3555
+ isOperationEnabled(definition, op)
3556
+ ]));
3557
+ if (!isOperationExternal(definition, "findAll")) {
3558
+ operations["lookup"] = `${uri}?q={text}`;
3559
+ }
3560
+ const form = config.views?.["form"];
3561
+ const schema = form?.json_schema ? {
3562
+ data: form.json_schema,
3563
+ ui: form.ui_schema
3564
+ } : null;
3565
+ return {
3566
+ id: config.id ?? route,
3567
+ uri,
3568
+ operations,
3569
+ schema,
3570
+ ...pickExtensions(config)
3571
+ };
3572
+ }, "buildResourceJsonPayload");
3573
+ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
3574
+ if (!config.views || !Object.keys(config.views).length) return void 0;
3575
+ const definition = resolveDefinition(config);
3576
+ const baseUri = config.parent ? `${baseUrl}/${config.parent.route}/{${config.parent.param}}/${config.route}` : `${baseUrl}/${config.route}`;
3577
+ const operations = buildResourceOperations(definition, baseUri);
3578
+ if (isOperationEnabled(definition, "findAll") && !isOperationExternal(definition, "findAll")) {
3579
+ operations["lookup"] = `${baseUri}?q={text}`;
3580
+ }
3581
+ const schemas = Object.fromEntries(Object.entries(config.views).map(([key, v]) => [
3582
+ key,
3583
+ {
3584
+ data: v.json_schema,
3585
+ ui: v.ui_schema,
3586
+ ...v.defaultSort !== void 0 && {
3587
+ defaultSort: v.defaultSort
3588
+ }
3589
+ }
3590
+ ]));
3591
+ return {
3592
+ id: config.id ?? config.route,
3593
+ name: config.name,
3594
+ route: config.route,
3595
+ uri: baseUri,
3596
+ title: config.title ?? config.tag,
3597
+ idField: config.lookup?.key ?? "id",
3598
+ idType: config.idType ?? "string",
3599
+ ...config.modalSize && {
3600
+ modalSize: config.modalSize
3601
+ },
3602
+ operations,
3603
+ display: config.display,
3604
+ schemas,
3605
+ actions: resolveActions(baseUri, config.actions),
3606
+ tableActions: resolveTableActions(baseUri, config.tableActions),
3607
+ ...pickExtensions(config)
3608
+ };
3609
+ }, "buildViewsPayload");
3610
+ var buildSubResourceViewsPayload = /* @__PURE__ */ __name((config, sub, baseUrl) => {
3611
+ if (!sub.views) return void 0;
3612
+ const { route } = config;
3613
+ const childUri = `${baseUrl}/${route}/{parent.id}/${sub.childRoute}`;
3614
+ return {
3615
+ id: `${route}/${sub.childRoute}`,
3616
+ name: sub.name ?? sub.childRoute,
3617
+ route: sub.childRoute,
3618
+ uri: childUri,
3619
+ title: sub.title ?? sub.childRoute,
3620
+ idField: sub.idField ?? "id",
3621
+ idType: sub.idType ?? "string",
3622
+ ...sub.modalSize && {
3623
+ modalSize: sub.modalSize
3624
+ },
3625
+ ...sub.display && {
3626
+ display: sub.display
3627
+ },
3628
+ // ponytail: cast because JsonOperationsSchema uses boolean|{security?} but buildSubResourceOperations wants boolean
3629
+ operations: buildSubResourceOperations(sub.operations, childUri, sub.idField ?? "id"),
3630
+ schemas: Object.fromEntries(Object.entries(sub.views).map(([key, v]) => [
3631
+ key,
3632
+ {
3633
+ data: v.json_schema,
3634
+ ui: v.ui_schema,
3635
+ ...v.defaultSort !== void 0 && {
3636
+ defaultSort: v.defaultSort
3637
+ }
3638
+ }
3639
+ ])),
3640
+ actions: resolveActions(`${baseUrl}/${sub.childRoute}`, sub.actions)
3641
+ };
3642
+ }, "buildSubResourceViewsPayload");
2979
3643
 
2980
3644
  // ../crouton-core/src/lib/config/CroutonConfig.schema.ts
2981
- var import_zod44 = require("zod");
2982
- var RulesetSchema = import_zod44.z.object({
2983
- hideIdInTable: import_zod44.z.boolean().default(true),
2984
- hideIdInForm: import_zod44.z.boolean().default(true),
2985
- hideIdInView: import_zod44.z.boolean().default(true),
2986
- hideTimestamps: import_zod44.z.boolean().default(true),
2987
- hideForeignKeys: import_zod44.z.boolean().default(true),
2988
- includeRelations: import_zod44.z.boolean().default(true),
2989
- hideRelationsInTable: import_zod44.z.boolean().default(true),
2990
- showRelationsInForm: import_zod44.z.boolean().default(true),
2991
- enumValueLabel: import_zod44.z.boolean().default(true),
2992
- sharedEnums: import_zod44.z.boolean().default(true),
3645
+ var import_zod45 = require("zod");
3646
+ var RulesetSchema = import_zod45.z.object({
3647
+ hideIdInTable: import_zod45.z.boolean().default(true),
3648
+ hideIdInForm: import_zod45.z.boolean().default(true),
3649
+ hideIdInView: import_zod45.z.boolean().default(true),
3650
+ hideTimestamps: import_zod45.z.boolean().default(true),
3651
+ hideForeignKeys: import_zod45.z.boolean().default(true),
3652
+ includeRelations: import_zod45.z.boolean().default(true),
3653
+ hideRelationsInTable: import_zod45.z.boolean().default(true),
3654
+ showRelationsInForm: import_zod45.z.boolean().default(true),
3655
+ enumValueLabel: import_zod45.z.boolean().default(true),
3656
+ sharedEnums: import_zod45.z.boolean().default(true),
2993
3657
  defaultOperations: JsonOperationsSchema.default(JsonOperationsSchema.parse({}))
2994
3658
  });
2995
- var I18nConfigSchema = import_zod44.z.object({
3659
+ var I18nConfigSchema = import_zod45.z.object({
2996
3660
  /** Default / fallback language. */
2997
- defaultLanguage: import_zod44.z.string().default("en"),
3661
+ defaultLanguage: import_zod45.z.string().default("en"),
2998
3662
  /** All supported language codes. */
2999
- languages: import_zod44.z.array(import_zod44.z.string()).min(1),
3663
+ languages: import_zod45.z.array(import_zod45.z.string()).min(1),
3000
3664
  /** Directory containing `<lang>.json` bundles, relative to project root. */
3001
- translationsDir: import_zod44.z.string().default("translations")
3665
+ translationsDir: import_zod45.z.string().default("translations")
3002
3666
  });
3003
- var CroutonConfigSchema = import_zod44.z.object({
3667
+ var CroutonConfigSchema = import_zod45.z.object({
3004
3668
  /**
3005
3669
  * Application title served to the frontend via `GET /_app/layout`.
3006
3670
  * Displayed in the admin sidebar header.
3007
3671
  */
3008
- title: import_zod44.z.string(),
3672
+ title: import_zod45.z.string(),
3009
3673
  /** Where resource directories live, relative to the project root. */
3010
- resourcesDir: import_zod44.z.string(),
3674
+ resourcesDir: import_zod45.z.string(),
3011
3675
  /** Where datasource folders live (each with a `data-source.json`). */
3012
- dataSourcesDir: import_zod44.z.string(),
3676
+ dataSourcesDir: import_zod45.z.string(),
3013
3677
  /**
3014
3678
  * Template for a model's Zod export name. `{Model}` → Prisma model name.
3015
3679
  * Defaults to `{Model}WithRelationsSchema` (the relations-aware schema
3016
3680
  * emitted by zod-prisma-types when `createRelationValuesTypes` is on).
3017
3681
  */
3018
- schemaExportName: import_zod44.z.string().default("{Model}WithRelationsSchema"),
3682
+ schemaExportName: import_zod45.z.string().default("{Model}WithRelationsSchema"),
3019
3683
  /** Path to the shared enum registry, relative to project root. Default `crouton.enums.json`. */
3020
- enumsFile: import_zod44.z.string().default("crouton.enums.json"),
3684
+ enumsFile: import_zod45.z.string().default("crouton.enums.json"),
3021
3685
  /** Optional overrides of the default visibility ruleset. */
3022
3686
  rules: RulesetSchema.default(RulesetSchema.parse({})),
3023
3687
  /**
3024
3688
  * Sidebar group definitions, keyed by group slug (e.g. `"metadata"`).
3025
3689
  * Resources reference a group via `sidebar.group` in their `resource.json`.
3026
3690
  */
3027
- sidebarGroups: import_zod44.z.record(import_zod44.z.string(), SidebarGroupSchema).default({}),
3691
+ sidebarGroups: import_zod45.z.record(import_zod45.z.string(), SidebarGroupSchema).default({}),
3028
3692
  /**
3029
3693
  * Whether form fields are saved automatically as the user edits them.
3030
3694
  * @default true
3031
3695
  */
3032
- autoSave: import_zod44.z.boolean().default(true),
3696
+ autoSave: import_zod45.z.boolean().default(true),
3033
3697
  /** Optional i18n / translation configuration. */
3034
3698
  i18n: I18nConfigSchema.optional()
3035
3699
  });
@@ -3042,29 +3706,29 @@ var CONFIG_FILES = [
3042
3706
  ];
3043
3707
 
3044
3708
  // ../crouton-core/src/lib/i18n/Translations.schema.ts
3045
- var import_zod45 = require("zod");
3046
- var NestedStringRecord = import_zod45.z.lazy(() => import_zod45.z.record(import_zod45.z.string(), import_zod45.z.union([
3047
- import_zod45.z.string(),
3709
+ var import_zod46 = require("zod");
3710
+ var NestedStringRecord = import_zod46.z.lazy(() => import_zod46.z.record(import_zod46.z.string(), import_zod46.z.union([
3711
+ import_zod46.z.string(),
3048
3712
  NestedStringRecord
3049
3713
  ])));
3050
- var TranslationBundleSchema = import_zod45.z.object({
3051
- app: import_zod45.z.object({
3052
- title: import_zod45.z.string().optional()
3714
+ var TranslationBundleSchema = import_zod46.z.object({
3715
+ app: import_zod46.z.object({
3716
+ title: import_zod46.z.string().optional()
3053
3717
  }).optional(),
3054
- sidebarGroups: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.string()).optional(),
3055
- resources: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.object({
3056
- title: import_zod45.z.string().optional(),
3057
- sidebar: import_zod45.z.string().optional(),
3058
- columns: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.string()).optional(),
3059
- actions: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.string()).optional(),
3060
- subResources: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.object({
3061
- title: import_zod45.z.string().optional(),
3062
- columns: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.string()).optional()
3718
+ sidebarGroups: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.string()).optional(),
3719
+ resources: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.object({
3720
+ title: import_zod46.z.string().optional(),
3721
+ sidebar: import_zod46.z.string().optional(),
3722
+ columns: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.string()).optional(),
3723
+ actions: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.string()).optional(),
3724
+ subResources: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.object({
3725
+ title: import_zod46.z.string().optional(),
3726
+ columns: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.string()).optional()
3063
3727
  }).optional()).optional()
3064
3728
  }).optional()).optional(),
3065
- enums: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.record(import_zod45.z.string(), import_zod45.z.string())).optional(),
3729
+ enums: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.record(import_zod46.z.string(), import_zod46.z.string())).optional(),
3066
3730
  ui: NestedStringRecord.optional(),
3067
- validation: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.string()).optional()
3731
+ validation: import_zod46.z.record(import_zod46.z.string(), import_zod46.z.string()).optional()
3068
3732
  }).passthrough();
3069
3733
 
3070
3734
  // ../crouton-core/src/lib/i18n/translation-keys.ts
@@ -3144,25 +3808,25 @@ var resolveLanguage = /* @__PURE__ */ __name((acceptLanguage, supported, default
3144
3808
  }, "resolveLanguage");
3145
3809
 
3146
3810
  // src/lib/crouton-api.module.ts
3147
- var import_common27 = require("@nestjs/common");
3811
+ var import_common28 = require("@nestjs/common");
3148
3812
  var import_core2 = require("@nestjs/core");
3149
3813
 
3150
3814
  // src/lib/crud/app-layout/app-layout.types.ts
3151
- var import_zod46 = require("zod");
3152
- var SidebarLeafSchema = import_zod46.z.object({
3153
- kind: import_zod46.z.literal("item").default("item"),
3154
- id: import_zod46.z.string(),
3155
- label: import_zod46.z.string(),
3156
- position: import_zod46.z.number().optional()
3815
+ var import_zod47 = require("zod");
3816
+ var SidebarLeafSchema = import_zod47.z.object({
3817
+ kind: import_zod47.z.literal("item").default("item"),
3818
+ id: import_zod47.z.string(),
3819
+ label: import_zod47.z.string(),
3820
+ position: import_zod47.z.number().optional()
3157
3821
  });
3158
- var SidebarGroupSchema2 = import_zod46.z.object({
3159
- kind: import_zod46.z.literal("group").default("group"),
3160
- id: import_zod46.z.string(),
3161
- label: import_zod46.z.string(),
3162
- position: import_zod46.z.number().optional(),
3163
- children: import_zod46.z.array(SidebarLeafSchema).default([])
3822
+ var SidebarGroupSchema2 = import_zod47.z.object({
3823
+ kind: import_zod47.z.literal("group").default("group"),
3824
+ id: import_zod47.z.string(),
3825
+ label: import_zod47.z.string(),
3826
+ position: import_zod47.z.number().optional(),
3827
+ children: import_zod47.z.array(SidebarLeafSchema).default([])
3164
3828
  });
3165
- var SidebarNodeSchema = import_zod46.z.discriminatedUnion("kind", [
3829
+ var SidebarNodeSchema = import_zod47.z.discriminatedUnion("kind", [
3166
3830
  SidebarLeafSchema,
3167
3831
  SidebarGroupSchema2
3168
3832
  ]);
@@ -3837,9 +4501,12 @@ var loadConfig = /* @__PURE__ */ __name(async () => {
3837
4501
  config = mod.default ?? mod;
3838
4502
  }
3839
4503
  const parsed = CroutonConfigSchema.parse(config);
4504
+ const configDir = (0, import_node_path3.dirname)(path);
4505
+ const enumsFile = (0, import_node_path3.isAbsolute)(parsed.enumsFile) ? parsed.enumsFile : (0, import_node_path3.resolve)(configDir, parsed.enumsFile);
3840
4506
  return {
3841
4507
  ...parsed,
3842
- configDir: (0, import_node_path3.dirname)(path)
4508
+ configDir,
4509
+ enumsFile
3843
4510
  };
3844
4511
  }, "loadConfig");
3845
4512
 
@@ -3994,54 +4661,54 @@ CroutonValidationExceptionFilter = _ts_decorate3([
3994
4661
  ], CroutonValidationExceptionFilter);
3995
4662
 
3996
4663
  // src/lib/crud/crud-controller.factory.ts
3997
- var import_common23 = require("@nestjs/common");
3998
- var import_swagger11 = require("@nestjs/swagger");
4664
+ var import_common24 = require("@nestjs/common");
4665
+ var import_swagger12 = require("@nestjs/swagger");
3999
4666
 
4000
4667
  // src/lib/crud/crud-repository.factory.ts
4001
4668
  var import_common10 = require("@nestjs/common");
4002
4669
 
4003
4670
  // src/lib/crud/action/action.types.ts
4004
- var import_zod47 = require("zod");
4005
- var ActionMetadataSchema2 = import_zod47.z.object({
4671
+ var import_zod48 = require("zod");
4672
+ var ActionMetadataSchema2 = import_zod48.z.object({
4006
4673
  /** URL segment used in the endpoint. */
4007
- id: import_zod47.z.string(),
4674
+ id: import_zod48.z.string(),
4008
4675
  /** Human-readable label shown as a button. */
4009
- label: import_zod47.z.string().optional(),
4676
+ label: import_zod48.z.string().optional(),
4010
4677
  /** MDI icon name, e.g. `"mdi:open-in-new"`. */
4011
- icon: import_zod47.z.string().optional(),
4678
+ icon: import_zod48.z.string().optional(),
4012
4679
  /** Tooltip text. Falls back to `label` when omitted. */
4013
- tooltip: import_zod47.z.string().optional(),
4680
+ tooltip: import_zod48.z.string().optional(),
4014
4681
  /** Per-row condition — button is hidden when false. */
4015
- condition: import_zod47.z.custom().optional()
4682
+ condition: import_zod48.z.custom().optional()
4016
4683
  });
4017
4684
  var ResourceLinkActionSchema2 = ActionMetadataSchema2.extend({
4018
- type: import_zod47.z.literal("link"),
4685
+ type: import_zod48.z.literal("link"),
4019
4686
  /** URL to open. May contain `{id}` or `{env.VAR}` placeholders. */
4020
- href: import_zod47.z.string()
4687
+ href: import_zod48.z.string()
4021
4688
  });
4022
4689
  var ResourceRowProcedureActionSchema2 = ActionMetadataSchema2.extend({
4023
- type: import_zod47.z.literal("procedure").optional(),
4690
+ type: import_zod48.z.literal("procedure").optional(),
4024
4691
  /** HTTP method for the endpoint. Defaults to `"post"`. */
4025
- method: import_zod47.z.string().optional(),
4692
+ method: import_zod48.z.string().optional(),
4026
4693
  /** Static data payload merged into the request body by the frontend. */
4027
- data: import_zod47.z.record(import_zod47.z.string(), import_zod47.z.unknown()).optional(),
4694
+ data: import_zod48.z.record(import_zod48.z.string(), import_zod48.z.unknown()).optional(),
4028
4695
  /** Procedure called with `(prisma, recordId)`. */
4029
- procedure: import_zod47.z.custom((v) => typeof v === "function")
4696
+ procedure: import_zod48.z.custom((v) => typeof v === "function")
4030
4697
  });
4031
4698
  var ResourceTableProcedureActionSchema2 = ActionMetadataSchema2.extend({
4032
- type: import_zod47.z.literal("procedure").optional(),
4699
+ type: import_zod48.z.literal("procedure").optional(),
4033
4700
  /** HTTP method for the endpoint. Defaults to `"post"`. */
4034
- method: import_zod47.z.string().optional(),
4701
+ method: import_zod48.z.string().optional(),
4035
4702
  /** Static data payload merged into the request body by the frontend. */
4036
- data: import_zod47.z.record(import_zod47.z.string(), import_zod47.z.unknown()).optional(),
4703
+ data: import_zod48.z.record(import_zod48.z.string(), import_zod48.z.unknown()).optional(),
4037
4704
  /** Procedure called with `(prisma)` — no record id. */
4038
- procedure: import_zod47.z.custom((v) => typeof v === "function")
4705
+ procedure: import_zod48.z.custom((v) => typeof v === "function")
4039
4706
  });
4040
- var ResourceRowActionSchema2 = import_zod47.z.union([
4707
+ var ResourceRowActionSchema2 = import_zod48.z.union([
4041
4708
  ResourceRowProcedureActionSchema2,
4042
4709
  ResourceLinkActionSchema2
4043
4710
  ]);
4044
- var ResourceTableActionSchema2 = import_zod47.z.union([
4711
+ var ResourceTableActionSchema2 = import_zod48.z.union([
4045
4712
  ResourceTableProcedureActionSchema2,
4046
4713
  ResourceLinkActionSchema2
4047
4714
  ]);
@@ -4053,9 +4720,8 @@ var import_node_fs4 = require("fs");
4053
4720
  var import_node_module = require("module");
4054
4721
  var import_node_path5 = require("path");
4055
4722
  var import_node_url = require("url");
4056
- var import_meta = {};
4057
4723
  var _require = (0, import_node_module.createRequire)(importMetaUrl ?? __filename);
4058
- var IS_ESM = typeof import_meta !== "undefined" && typeof importMetaUrl === "string";
4724
+ var IS_ESM = typeof __filename === "undefined";
4059
4725
  var findModule = /* @__PURE__ */ __name((dir, name) => {
4060
4726
  for (const ext of [
4061
4727
  ".ts",
@@ -4148,7 +4814,7 @@ var securityForSub = /* @__PURE__ */ __name((config, sub, op, moduleDefault) =>
4148
4814
  }, "securityForSub");
4149
4815
 
4150
4816
  // src/lib/crud/custom-repository/custom-repository.types.ts
4151
- var import_zod48 = require("zod");
4817
+ var import_zod49 = require("zod");
4152
4818
  var CUSTOM_OPS = [
4153
4819
  "findAll",
4154
4820
  "findOne",
@@ -4165,7 +4831,7 @@ var PARENT_METHOD = {
4165
4831
  patch: "patchByParent",
4166
4832
  delete: "deleteByParent"
4167
4833
  };
4168
- var CustomRepositorySchema = import_zod48.z.custom((value) => typeof value === "object" && value !== null);
4834
+ var CustomRepositorySchema = import_zod49.z.custom((value) => typeof value === "object" && value !== null);
4169
4835
 
4170
4836
  // src/lib/crud/resource/resource-load-errors.registry.ts
4171
4837
  var ResourceLoadErrorsRegistry = class ResourceLoadErrorsRegistry2 {
@@ -4235,6 +4901,7 @@ var import_common9 = require("@nestjs/common");
4235
4901
 
4236
4902
  // src/lib/crud/constants.ts
4237
4903
  var PRISMA_NOT_FOUND_CODE = "P2025";
4904
+ var PRISMA_UNIQUE_CONSTRAINT_CODE = "P2002";
4238
4905
  var DEFAULT_ID_TYPE = "string";
4239
4906
  var DEFAULT_ID_FIELD = "id";
4240
4907
 
@@ -4747,8 +5414,13 @@ var ReadRepository = class {
4747
5414
  ...mergedInclude
4748
5415
  };
4749
5416
  } else {
4750
- if (countClause) query._count = countClause;
4751
- if (mergedInclude) query.include = mergedInclude;
5417
+ const includeClause = {
5418
+ ...mergedInclude ?? {},
5419
+ ...countClause ? {
5420
+ _count: countClause
5421
+ } : {}
5422
+ };
5423
+ if (Object.keys(includeClause).length) query.include = includeClause;
4752
5424
  }
4753
5425
  const rows = await this.prismaModel.findMany(query);
4754
5426
  const mapped = countableSubResources.length ? rows.map((row) => {
@@ -4966,10 +5638,133 @@ var ReadRepository = class {
4966
5638
  });
4967
5639
  return withCalc;
4968
5640
  }
4969
- };
5641
+ };
5642
+
5643
+ // src/lib/crud/write.repository.ts
5644
+ var import_common7 = require("@nestjs/common");
5645
+
5646
+ // src/lib/crud/unique-validation.ts
5647
+ var columnsOf = /* @__PURE__ */ __name((config) => config.columns ?? [], "columnsOf");
5648
+ var columnDbName = /* @__PURE__ */ __name((col) => col.column ?? col.id, "columnDbName");
5649
+ var columnById = /* @__PURE__ */ __name((config, id) => columnsOf(config).find((c) => c.id === id), "columnById");
5650
+ var dbNameForId = /* @__PURE__ */ __name((config, id) => {
5651
+ const col = columnById(config, id);
5652
+ return col ? columnDbName(col) : id;
5653
+ }, "dbNameForId");
5654
+ var uniqueColumns = /* @__PURE__ */ __name((config) => columnsOf(config).map((column) => ({
5655
+ column,
5656
+ unique: normalizeUnique(column.unique)
5657
+ })).filter((e) => e.unique != null), "uniqueColumns");
5658
+ var findUniqueColumn = /* @__PURE__ */ __name((config, fieldId) => uniqueColumns(config).find((e) => e.column.id === fieldId), "findUniqueColumn");
5659
+ var extractTargets = /* @__PURE__ */ __name((meta) => {
5660
+ const target = meta?.target;
5661
+ if (Array.isArray(target)) return target.map(String);
5662
+ if (typeof target === "string") return [
5663
+ target
5664
+ ];
5665
+ return [];
5666
+ }, "extractTargets");
5667
+ var columnForTarget = /* @__PURE__ */ __name((config, target) => columnsOf(config).find((c) => columnDbName(c) === target || c.id === target), "columnForTarget");
5668
+ var sameSet = /* @__PURE__ */ __name((a, b) => a.length === b.length && (/* @__PURE__ */ new Set([
5669
+ ...a,
5670
+ ...b
5671
+ ])).size === a.length, "sameSet");
5672
+ var mapPrismaUniqueError = /* @__PURE__ */ __name((e, config) => {
5673
+ if (e?.code !== PRISMA_UNIQUE_CONSTRAINT_CODE) return void 0;
5674
+ const targets = extractTargets(e.meta);
5675
+ if (targets.length > 1) {
5676
+ for (const { column, unique } of uniqueColumns(config)) {
5677
+ const names = [
5678
+ columnDbName(column),
5679
+ ...(unique.scope ?? []).map((id) => dbNameForId(config, id))
5680
+ ];
5681
+ if (sameSet(names, targets)) {
5682
+ const label = column.label ?? column.id;
5683
+ return new CroutonValidationError([
5684
+ {
5685
+ field: column.id,
5686
+ message: unique.message ?? `${label} already exists`,
5687
+ code: "unique"
5688
+ }
5689
+ ]);
5690
+ }
5691
+ }
5692
+ }
5693
+ const resolved = targets.length ? targets : [
5694
+ "value"
5695
+ ];
5696
+ const errors = resolved.map((target) => {
5697
+ const col = columnForTarget(config, target);
5698
+ const unique = normalizeUnique(col?.unique);
5699
+ const label = col?.label ?? col?.id ?? target;
5700
+ return {
5701
+ field: col?.id ?? target,
5702
+ message: unique?.message ?? `${label} already exists`,
5703
+ code: "unique"
5704
+ };
5705
+ });
5706
+ return new CroutonValidationError(errors);
5707
+ }, "mapPrismaUniqueError");
5708
+ var dmmfUniqueInfo = /* @__PURE__ */ __name((client, modelName) => {
5709
+ try {
5710
+ const dmmf = client?._baseDmmf;
5711
+ if (!dmmf) return null;
5712
+ const model = dmmf.modelMap?.[modelName];
5713
+ if (!model) return null;
5714
+ const singleColumns = /* @__PURE__ */ new Set();
5715
+ for (const field of model.fields ?? []) {
5716
+ if (field.isUnique || field.isId) singleColumns.add(field.dbName ?? field.name);
5717
+ }
5718
+ const compositeFieldSets = (model.uniqueFields ?? []).filter((f) => f.length > 1).map((f) => new Set(f));
5719
+ return {
5720
+ singleColumns,
5721
+ compositeFieldSets
5722
+ };
5723
+ } catch {
5724
+ return null;
5725
+ }
5726
+ }, "dmmfUniqueInfo");
5727
+ var warnMissingUniqueConstraints = /* @__PURE__ */ __name((configs, dataSourceRegistry) => {
5728
+ if (process.env["NODE_ENV"] === "production") return;
5729
+ for (const config of configs) {
5730
+ const cols = uniqueColumns(config);
5731
+ if (!cols.length || !config.model) continue;
5732
+ let client;
5733
+ try {
5734
+ const adapter = dataSourceRegistry.resolveAdapter(config.database);
5735
+ if (adapter.kind !== "prisma") continue;
5736
+ client = adapter.client;
5737
+ } catch {
5738
+ continue;
5739
+ }
5740
+ const info = dmmfUniqueInfo(client, config.model);
5741
+ if (!info) continue;
5742
+ for (const { column, unique } of cols) {
5743
+ const dbName = columnDbName(column);
5744
+ if (!unique.scope?.length) {
5745
+ if (!info.singleColumns.has(dbName)) {
5746
+ console.warn(`[crouton] WARNING: column "${column.id}" on resource "${config.name}" declares unique: true but Prisma model "${config.model}" has no @unique on "${dbName}". Add @unique to the Prisma schema to enforce the race-condition guarantee.`);
5747
+ }
5748
+ } else {
5749
+ const expectedSet = /* @__PURE__ */ new Set([
5750
+ dbName,
5751
+ ...unique.scope.map((id) => dbNameForId(config, id))
5752
+ ]);
5753
+ const hasComposite = info.compositeFieldSets.some((s) => s.size === expectedSet.size && [
5754
+ ...expectedSet
5755
+ ].every((f) => s.has(f)));
5756
+ if (!hasComposite) {
5757
+ console.warn(`[crouton] WARNING: column "${column.id}" on resource "${config.name}" declares composite unique (scope: ${JSON.stringify(unique.scope)}) but Prisma model "${config.model}" has no matching @@unique constraint. Add @@unique([${[
5758
+ dbName,
5759
+ ...expectedSet
5760
+ ].join(", ")}]) to the Prisma schema.`);
5761
+ }
5762
+ }
5763
+ }
5764
+ }
5765
+ }, "warnMissingUniqueConstraints");
4970
5766
 
4971
5767
  // src/lib/crud/write.repository.ts
4972
- var import_common7 = require("@nestjs/common");
4973
5768
  var includeRelationNames = /* @__PURE__ */ __name((include) => new Set((include ?? []).map((e) => typeof e === "string" ? e : e.relation)), "includeRelationNames");
4974
5769
  var PRISMA_RELATION_WRITE_KEYS = /* @__PURE__ */ new Set([
4975
5770
  "connect",
@@ -5040,9 +5835,13 @@ var WriteRepository = class {
5040
5835
  * normalized, and run through `beforeWrite` by the factory wrapper.
5041
5836
  */
5042
5837
  async create(data, _request) {
5043
- return this.prismaModel.create({
5044
- data
5045
- });
5838
+ try {
5839
+ return await this.prismaModel.create({
5840
+ data
5841
+ });
5842
+ } catch (e) {
5843
+ throw mapPrismaUniqueError(e, this.config) ?? e;
5844
+ }
5046
5845
  }
5047
5846
  /**
5048
5847
  * Update a record. The data has already been stripped, normalized, and hooked
@@ -5059,6 +5858,8 @@ var WriteRepository = class {
5059
5858
  });
5060
5859
  } catch (e) {
5061
5860
  if (e?.code === PRISMA_NOT_FOUND_CODE) throw this.notFound(id);
5861
+ const uniqueError = mapPrismaUniqueError(e, this.config);
5862
+ if (uniqueError) throw uniqueError;
5062
5863
  throw e;
5063
5864
  }
5064
5865
  }
@@ -5076,6 +5877,8 @@ var WriteRepository = class {
5076
5877
  });
5077
5878
  } catch (e) {
5078
5879
  if (e?.code === PRISMA_NOT_FOUND_CODE) throw this.notFound(id);
5880
+ const uniqueError = mapPrismaUniqueError(e, this.config);
5881
+ if (uniqueError) throw uniqueError;
5079
5882
  throw e;
5080
5883
  }
5081
5884
  }
@@ -5089,6 +5892,8 @@ var WriteRepository = class {
5089
5892
  });
5090
5893
  } catch (e) {
5091
5894
  if (e?.code === PRISMA_NOT_FOUND_CODE) throw this.notFound(id);
5895
+ const uniqueError = mapPrismaUniqueError(e, this.config);
5896
+ if (uniqueError) throw uniqueError;
5092
5897
  throw e;
5093
5898
  }
5094
5899
  }
@@ -5358,23 +6163,23 @@ var PrismaDataSourceAdapter = class {
5358
6163
  };
5359
6164
 
5360
6165
  // src/lib/crud/hooks/hooks.types.ts
5361
- var import_zod49 = require("zod");
5362
- var WriteOpSchema = import_zod49.z.enum([
6166
+ var import_zod50 = require("zod");
6167
+ var WriteOpSchema = import_zod50.z.enum([
5363
6168
  "create",
5364
6169
  "update",
5365
6170
  "patch",
5366
6171
  "delete"
5367
6172
  ]);
5368
- var ReadOpSchema = import_zod49.z.enum([
6173
+ var ReadOpSchema = import_zod50.z.enum([
5369
6174
  "findAll",
5370
6175
  "findOne"
5371
6176
  ]);
5372
- var ResourceHooksSchema = import_zod49.z.object({
5373
- beforeWrite: import_zod49.z.custom().optional(),
5374
- afterWrite: import_zod49.z.custom().optional(),
5375
- afterRead: import_zod49.z.custom().optional(),
5376
- beforeFindAll: import_zod49.z.custom().optional(),
5377
- afterFindAll: import_zod49.z.custom().optional()
6177
+ var ResourceHooksSchema = import_zod50.z.object({
6178
+ beforeWrite: import_zod50.z.custom().optional(),
6179
+ afterWrite: import_zod50.z.custom().optional(),
6180
+ afterRead: import_zod50.z.custom().optional(),
6181
+ beforeFindAll: import_zod50.z.custom().optional(),
6182
+ afterFindAll: import_zod50.z.custom().optional()
5378
6183
  });
5379
6184
 
5380
6185
  // src/lib/crud/hooks/hooks.loader.ts
@@ -5624,21 +6429,21 @@ var validateCustomRepository = /* @__PURE__ */ __name((config, repository, adapt
5624
6429
  }, "validateCustomRepository");
5625
6430
 
5626
6431
  // src/lib/crud/schema.utils.ts
5627
- var import_zod50 = require("zod");
5628
- function isZodSchema(schema) {
5629
- return schema instanceof import_zod50.ZodObject;
6432
+ var import_zod51 = require("zod");
6433
+ function isZodSchema2(schema) {
6434
+ return schema instanceof import_zod51.ZodObject;
5630
6435
  }
5631
- __name(isZodSchema, "isZodSchema");
5632
- var dropNullableFromRequired2 = /* @__PURE__ */ __name((jsonSchema) => {
6436
+ __name(isZodSchema2, "isZodSchema");
6437
+ var dropNullableFromRequired3 = /* @__PURE__ */ __name((jsonSchema) => {
5633
6438
  const { properties, required } = jsonSchema;
5634
6439
  if (!properties || !Array.isArray(required)) return;
5635
6440
  jsonSchema.required = required.filter((key) => !isNullableProperty(properties[key]));
5636
6441
  }, "dropNullableFromRequired");
5637
6442
  function toJsonSchema2(schema) {
5638
- if (isZodSchema(schema)) {
6443
+ if (isZodSchema2(schema)) {
5639
6444
  let jsonSchema;
5640
6445
  try {
5641
- jsonSchema = (0, import_zod50.toJSONSchema)(schema, {
6446
+ jsonSchema = (0, import_zod51.toJSONSchema)(schema, {
5642
6447
  target: "openApi3",
5643
6448
  ...jsonSchemaOpts
5644
6449
  });
@@ -5647,7 +6452,7 @@ function toJsonSchema2(schema) {
5647
6452
  type: "object"
5648
6453
  };
5649
6454
  }
5650
- dropNullableFromRequired2(jsonSchema);
6455
+ dropNullableFromRequired3(jsonSchema);
5651
6456
  return jsonSchema;
5652
6457
  }
5653
6458
  return schema;
@@ -5664,7 +6469,7 @@ var unwrap2 = /* @__PURE__ */ __name((schema) => {
5664
6469
  }, "unwrap");
5665
6470
  var typeOf = /* @__PURE__ */ __name((schema) => schema?._zod?.def?.type, "typeOf");
5666
6471
  function toSelectFields(schema) {
5667
- if (!isZodSchema(schema)) {
6472
+ if (!isZodSchema2(schema)) {
5668
6473
  return Object.keys(schema.properties).reduce((acc, key) => {
5669
6474
  acc[key] = true;
5670
6475
  return acc;
@@ -6767,26 +7572,26 @@ var import_common20 = require("@nestjs/common");
6767
7572
  var import_swagger8 = require("@nestjs/swagger");
6768
7573
 
6769
7574
  // src/lib/crud/resource/PatchResourceJson.schema.ts
6770
- var import_zod51 = require("zod");
6771
- var FieldVariantPatchSchema = import_zod51.z.object({
6772
- type: import_zod51.z.string().nullable().optional(),
6773
- format: import_zod51.z.string().nullable().optional(),
6774
- resource: import_zod51.z.string().nullable().optional(),
6775
- position: import_zod51.z.number().nullable().optional(),
6776
- options: import_zod51.z.record(import_zod51.z.string(), import_zod51.z.unknown().nullable()).optional()
7575
+ var import_zod52 = require("zod");
7576
+ var FieldVariantPatchSchema = import_zod52.z.object({
7577
+ type: import_zod52.z.string().nullable().optional(),
7578
+ format: import_zod52.z.string().nullable().optional(),
7579
+ resource: import_zod52.z.string().nullable().optional(),
7580
+ position: import_zod52.z.number().nullable().optional(),
7581
+ options: import_zod52.z.record(import_zod52.z.string(), import_zod52.z.unknown().nullable()).optional()
6777
7582
  }).partial();
6778
- var PatchColumnSchema = import_zod51.z.object({
6779
- label: import_zod51.z.string().optional(),
6780
- column: import_zod51.z.string().optional(),
6781
- hiddenInTable: import_zod51.z.boolean().optional(),
6782
- hiddenInForm: import_zod51.z.boolean().optional(),
6783
- hiddenInView: import_zod51.z.boolean().optional(),
7583
+ var PatchColumnSchema = import_zod52.z.object({
7584
+ label: import_zod52.z.string().optional(),
7585
+ column: import_zod52.z.string().optional(),
7586
+ hiddenInTable: import_zod52.z.boolean().optional(),
7587
+ hiddenInForm: import_zod52.z.boolean().optional(),
7588
+ hiddenInView: import_zod52.z.boolean().optional(),
6784
7589
  fieldInput: FieldVariantPatchSchema.optional(),
6785
7590
  fieldView: FieldVariantPatchSchema.optional(),
6786
7591
  fieldTable: FieldVariantPatchSchema.optional()
6787
7592
  }).partial();
6788
- var PatchResourceJsonSchema = import_zod51.z.object({
6789
- columns: import_zod51.z.record(import_zod51.z.string(), PatchColumnSchema)
7593
+ var PatchResourceJsonSchema = import_zod52.z.object({
7594
+ columns: import_zod52.z.record(import_zod52.z.string(), PatchColumnSchema)
6790
7595
  });
6791
7596
 
6792
7597
  // src/lib/crud/resource/WriteResourceJson.ts
@@ -6853,207 +7658,10 @@ var writeRawResourceJson = /* @__PURE__ */ __name((jsonPath, raw) => {
6853
7658
  }, "writeRawResourceJson");
6854
7659
 
6855
7660
  // src/lib/crud/operations/payload-builders.ts
6856
- var pickExtensions = /* @__PURE__ */ __name((config) => Object.fromEntries([
6857
- ...getResourceExtensions().keys()
6858
- ].filter((k) => config[k] !== void 0).map((k) => [
6859
- k,
6860
- config[k]
6861
- ])), "pickExtensions");
6862
- var resolveEnvPlaceholders = /* @__PURE__ */ __name((value) => value.replace(/\{env\.([^}]+)\}/g, (match, varName) => process.env[varName] ?? match), "resolveEnvPlaceholders");
6863
- var RESOURCE_OPS = [
6864
- "findAll",
6865
- "findOne",
6866
- "create",
6867
- "update",
6868
- "patch",
6869
- "delete"
6870
- ];
6871
- var OP_METHOD = {
6872
- findAll: "get",
6873
- findOne: "get",
6874
- create: "post",
6875
- update: "put",
6876
- patch: "patch",
6877
- delete: "delete"
6878
- };
6879
- var OP_SUFFIX = {
6880
- findAll: "",
6881
- findOne: "/{id}",
6882
- create: "",
6883
- update: "/{id}",
6884
- patch: "/{id}",
6885
- delete: "/{id}"
6886
- };
6887
- var buildResourceOperations = /* @__PURE__ */ __name((definition, baseUri) => Object.fromEntries(RESOURCE_OPS.filter((op) => isOperationEnabled2(definition, op)).map((op) => {
6888
- const extUri = externalRouteFor2(definition, op);
6889
- const uri = extUri ? resolveEnvPlaceholders(extUri) : `${baseUri}${OP_SUFFIX[op]}`;
6890
- const entry = definition[op];
6891
- const method = (extUri && typeof entry === "object" && entry !== null && "method" in entry ? entry.method : void 0) ?? OP_METHOD[op];
6892
- return [
6893
- op,
6894
- {
6895
- uri,
6896
- method
6897
- }
6898
- ];
6899
- })), "buildResourceOperations");
6900
- var buildDefinitionPayload2 = /* @__PURE__ */ __name((config) => {
6901
- const { route, name, tag, idType = "string" } = config;
6902
- const definition = resolveDefinition2(config);
6903
- const listSchema = schemaFor2(definition, "findAll");
6904
- const oneSchema = schemaFor2(definition, "findOne") ?? listSchema;
6905
- const createSchema = schemaFor2(definition, "create");
6906
- const updateSchema = schemaFor2(definition, "update");
6907
- const patchSchema = schemaFor2(definition, "patch");
6908
- const operations = [
6909
- "findAll",
6910
- "findOne",
6911
- "create",
6912
- "update",
6913
- "patch",
6914
- "delete"
6915
- ].filter((op) => isOperationEnabled2(definition, op));
6916
- return {
6917
- name,
6918
- route,
6919
- idType,
6920
- tag,
6921
- operations,
6922
- display: config.display,
6923
- schemas: {
6924
- ...listSchema && {
6925
- findAll: toJsonSchema2(listSchema)
6926
- },
6927
- ...oneSchema && {
6928
- findOne: toJsonSchema2(oneSchema)
6929
- },
6930
- ...createSchema && {
6931
- create: toJsonSchema2(createSchema)
6932
- },
6933
- ...updateSchema && {
6934
- update: toJsonSchema2(updateSchema)
6935
- },
6936
- ...patchSchema && {
6937
- patch: toJsonSchema2(patchSchema)
6938
- }
6939
- },
6940
- ...pickExtensions(config)
6941
- };
6942
- }, "buildDefinitionPayload");
6943
- var buildResourceJsonPayload2 = /* @__PURE__ */ __name((config, baseUrl) => {
6944
- const { name, route } = config;
6945
- const definition = resolveDefinition2(config);
6946
- const uri = `${baseUrl}/${route}`;
6947
- const operations = Object.fromEntries(RESOURCE_OPS.map((op) => [
6948
- op,
6949
- isOperationEnabled2(definition, op)
6950
- ]));
6951
- if (!isOperationExternal2(definition, "findAll")) {
6952
- operations.lookup = `${uri}?q={text}`;
6953
- }
6954
- const form = config.views?.["form"];
6955
- const schema = form?.json_schema ? {
6956
- data: form.json_schema,
6957
- ui: form.ui_schema
6958
- } : null;
6959
- return {
6960
- id: config.id ?? route,
6961
- uri,
6962
- operations,
6963
- schema,
6964
- ...pickExtensions(config)
6965
- };
6966
- }, "buildResourceJsonPayload");
6967
- var _resolveActions = /* @__PURE__ */ __name((baseUrl, actions) => {
6968
- if (!actions?.length) return [];
6969
- const resolved = actions.map((a) => a.type === "link" ? {
6970
- ...a,
6971
- href: resolveEnvPlaceholders(a.href)
6972
- } : {
6973
- ...a,
6974
- uri: baseUrl.map((b) => b === "{actionId}" ? a.id : b).join("/"),
6975
- method: a.method ?? "post"
6976
- });
6977
- return resolved;
6978
- }, "_resolveActions");
6979
- var resolveActions = /* @__PURE__ */ __name((baseUrl, actions) => {
6980
- return _resolveActions([
6981
- baseUrl,
6982
- "procedure",
6983
- "{actionId}",
6984
- "{id}"
6985
- ], actions);
6986
- }, "resolveActions");
6987
- var buildViewsPayload2 = /* @__PURE__ */ __name((config, baseUrl) => {
6988
- if (!config.views || !Object.keys(config.views).length) return void 0;
6989
- const definition = resolveDefinition2(config);
6990
- const baseUri = config.parent ? `${baseUrl}/${config.parent.route}/{${config.parent.param}}/${config.route}` : `${baseUrl}/${config.route}`;
6991
- const operations = buildResourceOperations(definition, baseUri);
6992
- if (isOperationEnabled2(definition, "findAll") && !isOperationExternal2(definition, "findAll")) {
6993
- operations["lookup"] = `${baseUri}?q={text}`;
6994
- }
6995
- const schemas = Object.fromEntries(Object.entries(config.views).map(([key, v]) => [
6996
- key,
6997
- {
6998
- data: v.json_schema,
6999
- ui: v.ui_schema,
7000
- ...v.defaultSort !== void 0 && {
7001
- defaultSort: v.defaultSort
7002
- }
7003
- }
7004
- ]));
7005
- const baseAction = `${baseUri}${config.route}`;
7006
- return {
7007
- id: config.id ?? config.route,
7008
- name: config.name,
7009
- route: config.route,
7010
- uri: baseUri,
7011
- title: config.title ?? config.tag,
7012
- idField: config.lookup?.key ?? "id",
7013
- idType: config.idType ?? "string",
7014
- ...config.modalSize && {
7015
- modalSize: config.modalSize
7016
- },
7017
- operations,
7018
- display: config.display,
7019
- schemas,
7020
- actions: resolveActions(baseAction, config.actions),
7021
- tableActions: resolveActions(baseAction, config.tableActions),
7022
- ...pickExtensions(config)
7023
- };
7024
- }, "buildViewsPayload");
7025
- var buildSubResourceViewsPayload = /* @__PURE__ */ __name((config, sub, baseUrl) => {
7026
- if (!sub.views) return void 0;
7027
- const { route } = config;
7028
- const childUri = `${baseUrl}/${route}/{parent.id}/${sub.childRoute}`;
7029
- return {
7030
- id: `${route}/${sub.childRoute}`,
7031
- name: sub.name ?? sub.childRoute,
7032
- route: sub.childRoute,
7033
- uri: childUri,
7034
- title: sub.title ?? sub.childRoute,
7035
- idField: sub.idField ?? "id",
7036
- idType: sub.idType ?? "string",
7037
- ...sub.modalSize && {
7038
- modalSize: sub.modalSize
7039
- },
7040
- ...sub.display && {
7041
- display: sub.display
7042
- },
7043
- operations: buildSubResourceOperations(sub.operations, childUri, sub.idField ?? "id"),
7044
- schemas: Object.fromEntries(Object.entries(sub.views).map(([key, v]) => [
7045
- key,
7046
- {
7047
- data: v.json_schema,
7048
- ui: v.ui_schema,
7049
- ...v.defaultSort !== void 0 && {
7050
- defaultSort: v.defaultSort
7051
- }
7052
- }
7053
- ])),
7054
- actions: resolveActions(`${baseUrl}/${sub.childRoute}`, sub.actions)
7055
- };
7056
- }, "buildSubResourceViewsPayload");
7661
+ var buildDefinitionPayload2 = /* @__PURE__ */ __name((config) => buildDefinitionPayload(config), "buildDefinitionPayload");
7662
+ var buildResourceJsonPayload2 = /* @__PURE__ */ __name((config, baseUrl) => buildResourceJsonPayload(config, baseUrl), "buildResourceJsonPayload");
7663
+ var buildViewsPayload2 = /* @__PURE__ */ __name((config, baseUrl) => buildViewsPayload(config, baseUrl), "buildViewsPayload");
7664
+ var buildSubResourceViewsPayload2 = /* @__PURE__ */ __name((config, sub, baseUrl) => buildSubResourceViewsPayload(config, sub, baseUrl), "buildSubResourceViewsPayload");
7057
7665
  var buildEditableColumnsPayload = /* @__PURE__ */ __name((config) => {
7058
7666
  const columns = config.columns ?? [];
7059
7667
  return {
@@ -7293,7 +7901,7 @@ var childSchemas = /* @__PURE__ */ __name((sub) => (ctx) => {
7293
7901
  if (!sub.views) return null;
7294
7902
  const { config, baseUrl } = ctx;
7295
7903
  const { route } = config;
7296
- const schemasPayload = buildSubResourceViewsPayload(config, sub, baseUrl);
7904
+ const schemasPayload = buildSubResourceViewsPayload2(config, sub, baseUrl);
7297
7905
  return {
7298
7906
  route: `${sub.childRoute}/schemas`,
7299
7907
  methodName: `getSchemas_${sub.childRoute}`,
@@ -7305,7 +7913,7 @@ var childSchemas = /* @__PURE__ */ __name((sub) => (ctx) => {
7305
7913
  if (localizedParent) {
7306
7914
  const localizedSub = (localizedParent.subResources ?? []).find((s) => s.childRoute === sub.childRoute);
7307
7915
  if (localizedSub) {
7308
- return buildSubResourceViewsPayload(localizedParent, localizedSub, baseUrl) ?? schemasPayload;
7916
+ return buildSubResourceViewsPayload2(localizedParent, localizedSub, baseUrl) ?? schemasPayload;
7309
7917
  }
7310
7918
  }
7311
7919
  }
@@ -7338,9 +7946,103 @@ var registerSchemas = /* @__PURE__ */ __name((ctx, sub) => {
7338
7946
  }
7339
7947
  }, "registerSchemas");
7340
7948
 
7341
- // src/lib/crud/operations/register-update.ts
7949
+ // src/lib/crud/operations/register-unique-check.ts
7342
7950
  var import_common22 = require("@nestjs/common");
7343
7951
  var import_swagger10 = require("@nestjs/swagger");
7952
+ var import_zod53 = require("zod");
7953
+ var UniqueQuerySchema = import_zod53.z.object({
7954
+ /** The column id to check (must be a declared-unique column). */
7955
+ field: import_zod53.z.string(),
7956
+ /** The candidate value. */
7957
+ value: import_zod53.z.string(),
7958
+ /** On edit, the id of the record being edited, so it does not match itself. */
7959
+ excludeId: import_zod53.z.string().optional(),
7960
+ /** JSON object of `{ scopeFieldId: value }` for composite (scoped) uniqueness. */
7961
+ scope: import_zod53.z.string().optional()
7962
+ });
7963
+ var registerUniqueCheck = /* @__PURE__ */ __name((ctx) => {
7964
+ const { cls, config } = ctx;
7965
+ if (!isOperationEnabled2(ctx.definition, "findAll")) return;
7966
+ if (!config.model) return;
7967
+ if (uniqueColumns(config).length === 0) return;
7968
+ const methodName = "uniqueCheck";
7969
+ const idField = config.idField ?? "id";
7970
+ const idType = config.idType ?? "string";
7971
+ const modelName = config.model;
7972
+ const handler = /* @__PURE__ */ __name(async function(query) {
7973
+ const { field, value, excludeId } = query;
7974
+ const entry = findUniqueColumn(config, field);
7975
+ if (!entry) {
7976
+ throw new CroutonValidationError([
7977
+ {
7978
+ field,
7979
+ message: `"${field}" is not a unique field`,
7980
+ code: "unknown_field"
7981
+ }
7982
+ ]);
7983
+ }
7984
+ const dbName = columnDbName(entry.column);
7985
+ const where = {
7986
+ [dbName]: entry.unique.caseInsensitive ? {
7987
+ equals: value,
7988
+ mode: "insensitive"
7989
+ } : value
7990
+ };
7991
+ if (excludeId !== void 0 && excludeId !== "") {
7992
+ where.NOT = {
7993
+ [idField]: idType === "number" ? Number(excludeId) : String(excludeId)
7994
+ };
7995
+ }
7996
+ if (entry.unique.scope?.length && query.scope) {
7997
+ let parsed;
7998
+ try {
7999
+ parsed = JSON.parse(query.scope);
8000
+ } catch {
8001
+ parsed = {};
8002
+ }
8003
+ for (const scopeId of entry.unique.scope) {
8004
+ where[dbNameForId(config, scopeId)] = parsed[scopeId] ?? null;
8005
+ }
8006
+ }
8007
+ const model = this.repo.prisma?.[modelName];
8008
+ if (!model) return {
8009
+ unique: true
8010
+ };
8011
+ const row = await model.findFirst({
8012
+ where,
8013
+ select: {
8014
+ [idField]: true
8015
+ }
8016
+ });
8017
+ return {
8018
+ unique: !row
8019
+ };
8020
+ }, "handler");
8021
+ def(cls, methodName, handler);
8022
+ const d = desc(cls, methodName);
8023
+ (0, import_common22.Get)("unique")(cls.prototype, methodName, d);
8024
+ (0, import_common22.Query)(new ZodValidationPipe(UniqueQuerySchema))(cls.prototype, methodName, 0);
8025
+ (0, import_swagger10.ApiOperation)({
8026
+ summary: `Check whether a ${config.name} field value is unique`
8027
+ })(cls.prototype, methodName, d);
8028
+ (0, import_swagger10.ApiResponse)({
8029
+ status: 200,
8030
+ description: "Uniqueness result",
8031
+ schema: {
8032
+ type: "object",
8033
+ properties: {
8034
+ unique: {
8035
+ type: "boolean"
8036
+ }
8037
+ }
8038
+ }
8039
+ })(cls.prototype, methodName, d);
8040
+ ctx.secure(methodName, "findAll");
8041
+ }, "registerUniqueCheck");
8042
+
8043
+ // src/lib/crud/operations/register-update.ts
8044
+ var import_common23 = require("@nestjs/common");
8045
+ var import_swagger11 = require("@nestjs/swagger");
7344
8046
  var defaultUpdate = /* @__PURE__ */ __name((ctx) => {
7345
8047
  if (!isOperationEnabled2(ctx.definition, "update")) return null;
7346
8048
  if (isOperationExternal2(ctx.definition, "update")) return null;
@@ -7354,9 +8056,9 @@ var defaultUpdate = /* @__PURE__ */ __name((ctx) => {
7354
8056
  return this.repo.update(id, body, req);
7355
8057
  }, "updateFn"),
7356
8058
  decorators: /* @__PURE__ */ __name(() => {
7357
- (0, import_common22.Param)("id")(cls.prototype, methodName, 0);
8059
+ (0, import_common23.Param)("id")(cls.prototype, methodName, 0);
7358
8060
  bodyDecorator(updateSchema)(cls.prototype, methodName, 1);
7359
- (0, import_common22.Req)()(cls.prototype, methodName, 2);
8061
+ (0, import_common23.Req)()(cls.prototype, methodName, 2);
7360
8062
  }, "decorators")
7361
8063
  };
7362
8064
  }, "defaultUpdate");
@@ -7368,10 +8070,10 @@ var childUpdate = /* @__PURE__ */ __name((sub) => (ctx) => {
7368
8070
  return this.repo.updateChild(sub, childId, body, req);
7369
8071
  }, "updateFn");
7370
8072
  const decorators = /* @__PURE__ */ __name(() => {
7371
- (0, import_common22.Param)("id")(cls.prototype, methodName, 0);
7372
- (0, import_common22.Param)("childId")(cls.prototype, methodName, 1);
7373
- (0, import_common22.Body)()(cls.prototype, methodName, 2);
7374
- (0, import_common22.Req)()(cls.prototype, methodName, 3);
8073
+ (0, import_common23.Param)("id")(cls.prototype, methodName, 0);
8074
+ (0, import_common23.Param)("childId")(cls.prototype, methodName, 1);
8075
+ (0, import_common23.Body)()(cls.prototype, methodName, 2);
8076
+ (0, import_common23.Req)()(cls.prototype, methodName, 3);
7375
8077
  }, "decorators");
7376
8078
  return {
7377
8079
  route: `:id/${sub.childRoute}/:childId`,
@@ -7389,15 +8091,15 @@ var registerUpdate = /* @__PURE__ */ __name((ctx, sub) => {
7389
8091
  const { cls } = ctx;
7390
8092
  def(cls, methodName, properties.updateFn);
7391
8093
  const d = desc(cls, methodName);
7392
- (0, import_common22.Put)(route)(cls.prototype, methodName, d);
7393
- (0, import_swagger10.ApiOperation)({
8094
+ (0, import_common23.Put)(route)(cls.prototype, methodName, d);
8095
+ (0, import_swagger11.ApiOperation)({
7394
8096
  summary: `Replace a ${name}`
7395
8097
  })(cls.prototype, methodName, d);
7396
- (0, import_swagger10.ApiResponse)({
8098
+ (0, import_swagger11.ApiResponse)({
7397
8099
  status: 200,
7398
8100
  description: `${name} replaced`
7399
8101
  })(cls.prototype, methodName, d);
7400
- (0, import_swagger10.ApiNotFoundResponse)({
8102
+ (0, import_swagger11.ApiNotFoundResponse)({
7401
8103
  description: "Not found"
7402
8104
  })(cls.prototype, methodName, d);
7403
8105
  properties.decorators();
@@ -7414,6 +8116,7 @@ var registerEndpoints = /* @__PURE__ */ __name((ctx) => {
7414
8116
  registerResourceJsonEndpoint(ctx);
7415
8117
  registerResourceColumnsEndpoint(ctx);
7416
8118
  registerDefinitionEndpoint(ctx);
8119
+ registerUniqueCheck(ctx);
7417
8120
  registerEndpoint(ctx);
7418
8121
  registerActionRoutes(ctx);
7419
8122
  registerTableActionRoutes(ctx);
@@ -7442,11 +8145,11 @@ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEn
7442
8145
  const createSchema = schemaFor2(definition, "create");
7443
8146
  const updateSchema = schemaFor2(definition, "update");
7444
8147
  const explicitPatchSchema = schemaFor2(definition, "patch");
7445
- const patchSchema = explicitPatchSchema ?? (updateSchema && isZodSchema(updateSchema) ? updateSchema.partial() : void 0) ?? updateSchema;
8148
+ const patchSchema = explicitPatchSchema ?? (updateSchema && isZodSchema2(updateSchema) ? updateSchema.partial() : void 0) ?? updateSchema;
7446
8149
  const bodyDecorator = /* @__PURE__ */ __name((schema, options) => {
7447
- if (!schema) return (0, import_common23.Body)();
7448
- if (isZodSchema(schema)) return (0, import_common23.Body)(new ZodValidationPipe(schema, options));
7449
- return (0, import_common23.Body)();
8150
+ if (!schema) return (0, import_common24.Body)();
8151
+ if (isZodSchema2(schema)) return (0, import_common24.Body)(new ZodValidationPipe(schema, options));
8152
+ return (0, import_common24.Body)();
7450
8153
  }, "bodyDecorator");
7451
8154
  const resolveClient = /* @__PURE__ */ __name((registry2, resource) => {
7452
8155
  try {
@@ -7476,7 +8179,7 @@ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEn
7476
8179
  const secure = /* @__PURE__ */ __name((methodName, op, sub) => {
7477
8180
  const sec = sub ? securityForSub(config, sub, op, moduleDefaultSecurity) : securityFor(config, definition, op, moduleDefaultSecurity);
7478
8181
  if (sec) {
7479
- (0, import_common23.SetMetadata)(CROUTON_SECURITY, sec)(CrudControllerBase.prototype, methodName, Object.getOwnPropertyDescriptor(CrudControllerBase.prototype, methodName));
8182
+ (0, import_common24.SetMetadata)(CROUTON_SECURITY, sec)(CrudControllerBase.prototype, methodName, Object.getOwnPropertyDescriptor(CrudControllerBase.prototype, methodName));
7480
8183
  }
7481
8184
  }, "secure");
7482
8185
  const ctx = {
@@ -7500,11 +8203,11 @@ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEn
7500
8203
  };
7501
8204
  registerEndpoints(ctx);
7502
8205
  const controllerPath = resourceControllerPath(route, config.parent);
7503
- (0, import_common23.Controller)(prefix ? `${prefix}/${controllerPath}` : controllerPath)(CrudControllerBase);
8206
+ (0, import_common24.Controller)(prefix ? `${prefix}/${controllerPath}` : controllerPath)(CrudControllerBase);
7504
8207
  if (securityEnabled) {
7505
- (0, import_common23.UseGuards)(CroutonSecurityGuard)(CrudControllerBase);
8208
+ (0, import_common24.UseGuards)(CroutonSecurityGuard)(CrudControllerBase);
7506
8209
  }
7507
- (0, import_swagger11.ApiTags)(tag)(CrudControllerBase);
8210
+ (0, import_swagger12.ApiTags)(tag)(CrudControllerBase);
7508
8211
  Object.defineProperty(CrudControllerBase, "name", {
7509
8212
  value: `${name.charAt(0).toUpperCase() + name.slice(1)}Controller`
7510
8213
  });
@@ -7517,8 +8220,8 @@ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEn
7517
8220
  __name(createCrudController, "createCrudController");
7518
8221
 
7519
8222
  // src/lib/crud/dev-tools/dev-resources.controller.ts
7520
- var import_common24 = require("@nestjs/common");
7521
- var import_swagger12 = require("@nestjs/swagger");
8223
+ var import_common25 = require("@nestjs/common");
8224
+ var import_swagger13 = require("@nestjs/swagger");
7522
8225
 
7523
8226
  // ../crouton-codegen/src/naming.ts
7524
8227
  var clientAccessor = /* @__PURE__ */ __name((prismaModelName) => {
@@ -8236,12 +8939,12 @@ var makeSchemaExportName = /* @__PURE__ */ __name((config) => {
8236
8939
  var resolveFromRoot = /* @__PURE__ */ __name((root, p) => (0, import_node_path11.isAbsolute)(p) ? p : (0, import_node_path11.join)(root, p), "resolveFromRoot");
8237
8940
 
8238
8941
  // ../crouton-codegen/src/scaffold.ts
8239
- var import_zod52 = require("zod");
8942
+ var import_zod54 = require("zod");
8240
8943
  var import_promises5 = require("fs/promises");
8241
8944
  var import_node_path12 = require("path");
8242
8945
  var ScallfoldDatasourceSchema = DataSourceShape.extend({
8243
8946
  /** Folder name under `dataSourcesDir`. */
8244
- folder: import_zod52.z.string().default("default")
8947
+ folder: import_zod54.z.string().default("default")
8245
8948
  }).transform(transformDataSource);
8246
8949
 
8247
8950
  // ../crouton-codegen/src/datasource-scaffold.ts
@@ -8619,7 +9322,7 @@ var DevResourcesController = class {
8619
9322
  }
8620
9323
  assertDev() {
8621
9324
  if (!IS_DEV) {
8622
- throw new import_common24.ForbiddenException("The database sync tools are only available when CROUTON_SCHEMA_EDITOR is enabled.");
9325
+ throw new import_common25.ForbiddenException("The database sync tools are only available when CROUTON_SCHEMA_EDITOR is enabled.");
8623
9326
  }
8624
9327
  }
8625
9328
  /** Loads project config + resolves the datasource + Prisma schema path. Throws 404/400 with a clear message on misconfiguration. */
@@ -8628,14 +9331,14 @@ var DevResourcesController = class {
8628
9331
  try {
8629
9332
  loaded = await loadConfig2(process.cwd());
8630
9333
  } catch (e) {
8631
- throw new import_common24.NotFoundException(e.message ?? "No crouton.json config found.");
9334
+ throw new import_common25.NotFoundException(e.message ?? "No crouton.json config found.");
8632
9335
  }
8633
9336
  const datasources = await loadDatasources(loaded);
8634
9337
  let ds;
8635
9338
  try {
8636
9339
  ds = resolveDatasource(datasources, datasourceName);
8637
9340
  } catch (e) {
8638
- throw new import_common24.BadRequestException(e.message);
9341
+ throw new import_common25.BadRequestException(e.message);
8639
9342
  }
8640
9343
  const schemaPath = resolveFromRoot(loaded.root, ds.prismaSchema);
8641
9344
  return {
@@ -8650,7 +9353,7 @@ var DevResourcesController = class {
8650
9353
  schemaPath
8651
9354
  });
8652
9355
  } catch (e) {
8653
- throw new import_common24.BadRequestException(`Failed to read Prisma schema at ${schemaPath}: ${e.message}`);
9356
+ throw new import_common25.BadRequestException(`Failed to read Prisma schema at ${schemaPath}: ${e.message}`);
8654
9357
  }
8655
9358
  }
8656
9359
  buildApplyContext(loaded, ds) {
@@ -8720,7 +9423,7 @@ var DevResourcesController = class {
8720
9423
  zodOutputDir: zodDir
8721
9424
  });
8722
9425
  if (!result.ok) {
8723
- throw new import_common24.BadRequestException(`prisma db pull failed:
9426
+ throw new import_common25.BadRequestException(`prisma db pull failed:
8724
9427
  ${result.dbPull.output}`);
8725
9428
  }
8726
9429
  return {
@@ -8737,13 +9440,13 @@ ${result.dbPull.output}`);
8737
9440
  async sync(body) {
8738
9441
  this.assertDev();
8739
9442
  if (!body?.model) {
8740
- throw new import_common24.BadRequestException('"model" is required.');
9443
+ throw new import_common25.BadRequestException('"model" is required.');
8741
9444
  }
8742
9445
  const { loaded, ds, schemaPath } = await this.loadProject(body.datasource);
8743
9446
  const models = await this.introspectModels(schemaPath);
8744
9447
  const model = models.find((m) => m.prismaName === body.model || m.clientAccessor === body.model);
8745
9448
  if (!model) {
8746
- throw new import_common24.NotFoundException(`Model "${body.model}" not found in ${schemaPath}.`);
9449
+ throw new import_common25.NotFoundException(`Model "${body.model}" not found in ${schemaPath}.`);
8747
9450
  }
8748
9451
  const resolveRelationResource = await makeRelationResolver(loaded);
8749
9452
  const diff2 = await buildResourceDiff(model, {
@@ -8865,14 +9568,14 @@ ${result.dbPull.output}`);
8865
9568
  const resourcesDir = resolveFromRoot(loaded.root, loaded.config.resourcesDir);
8866
9569
  const jsonPath = resolveResourcePath(resourcesDir, name);
8867
9570
  const raw = readRawResourceJson(jsonPath);
8868
- if (!raw) throw new import_common24.NotFoundException(`Resource "${name}" not found.`);
8869
- if (jsonPath.endsWith(".ts")) throw new import_common24.ForbiddenException("TypeScript resources cannot be edited.");
9571
+ if (!raw) throw new import_common25.NotFoundException(`Resource "${name}" not found.`);
9572
+ if (jsonPath.endsWith(".ts")) throw new import_common25.ForbiddenException("TypeScript resources cannot be edited.");
8870
9573
  const patched = applyResourceFlagPatch(raw, {
8871
9574
  draft: false
8872
9575
  });
8873
9576
  const result = validateResourceJson(patched);
8874
9577
  if (!result.success) {
8875
- throw new import_common24.BadRequestException(`Validation failed: ${result.error.message}`);
9578
+ throw new import_common25.BadRequestException(`Validation failed: ${result.error.message}`);
8876
9579
  }
8877
9580
  writeRawResourceJson(jsonPath, patched);
8878
9581
  return {
@@ -8885,8 +9588,8 @@ ${result.dbPull.output}`);
8885
9588
  const resourcesDir = resolveFromRoot(loaded.root, loaded.config.resourcesDir);
8886
9589
  const jsonPath = resolveResourcePath(resourcesDir, name);
8887
9590
  const raw = readRawResourceJson(jsonPath);
8888
- if (!raw) throw new import_common24.NotFoundException(`Resource "${name}" not found.`);
8889
- if (jsonPath.endsWith(".ts")) throw new import_common24.ForbiddenException("TypeScript resources cannot be edited.");
9591
+ if (!raw) throw new import_common25.NotFoundException(`Resource "${name}" not found.`);
9592
+ if (jsonPath.endsWith(".ts")) throw new import_common25.ForbiddenException("TypeScript resources cannot be edited.");
8890
9593
  const patched = applyResourceFlagPatch(raw, {
8891
9594
  sidebar: {
8892
9595
  hide: true
@@ -8894,7 +9597,7 @@ ${result.dbPull.output}`);
8894
9597
  });
8895
9598
  const result = validateResourceJson(patched);
8896
9599
  if (!result.success) {
8897
- throw new import_common24.BadRequestException(`Validation failed: ${result.error.message}`);
9600
+ throw new import_common25.BadRequestException(`Validation failed: ${result.error.message}`);
8898
9601
  }
8899
9602
  writeRawResourceJson(jsonPath, patched);
8900
9603
  return {
@@ -8907,8 +9610,8 @@ ${result.dbPull.output}`);
8907
9610
  const resourcesDir = resolveFromRoot(loaded.root, loaded.config.resourcesDir);
8908
9611
  const jsonPath = resolveResourcePath(resourcesDir, name);
8909
9612
  const raw = readRawResourceJson(jsonPath);
8910
- if (!raw) throw new import_common24.NotFoundException(`Resource "${name}" not found.`);
8911
- if (jsonPath.endsWith(".ts")) throw new import_common24.ForbiddenException("TypeScript resources cannot be edited.");
9613
+ if (!raw) throw new import_common25.NotFoundException(`Resource "${name}" not found.`);
9614
+ if (jsonPath.endsWith(".ts")) throw new import_common25.ForbiddenException("TypeScript resources cannot be edited.");
8912
9615
  const sidebarPatch = {
8913
9616
  hide: false
8914
9617
  };
@@ -8921,7 +9624,7 @@ ${result.dbPull.output}`);
8921
9624
  });
8922
9625
  const result = validateResourceJson(patched);
8923
9626
  if (!result.success) {
8924
- throw new import_common24.BadRequestException(`Validation failed: ${result.error.message}`);
9627
+ throw new import_common25.BadRequestException(`Validation failed: ${result.error.message}`);
8925
9628
  }
8926
9629
  writeRawResourceJson(jsonPath, patched);
8927
9630
  return {
@@ -8934,12 +9637,12 @@ ${result.dbPull.output}`);
8934
9637
  const resourcesDir = resolveFromRoot(loaded.root, loaded.config.resourcesDir);
8935
9638
  const jsonPath = resolveResourcePath(resourcesDir, name);
8936
9639
  const raw = readRawResourceJson(jsonPath);
8937
- if (!raw) throw new import_common24.NotFoundException(`Resource "${name}" not found.`);
8938
- if (jsonPath.endsWith(".ts")) throw new import_common24.ForbiddenException("TypeScript resources cannot be edited.");
9640
+ if (!raw) throw new import_common25.NotFoundException(`Resource "${name}" not found.`);
9641
+ if (jsonPath.endsWith(".ts")) throw new import_common25.ForbiddenException("TypeScript resources cannot be edited.");
8939
9642
  const patched = applyResourceFlagPatch(raw, body);
8940
9643
  const result = validateResourceJson(patched);
8941
9644
  if (!result.success) {
8942
- throw new import_common24.BadRequestException(`Validation failed: ${result.error.message}`);
9645
+ throw new import_common25.BadRequestException(`Validation failed: ${result.error.message}`);
8943
9646
  }
8944
9647
  writeRawResourceJson(jsonPath, patched);
8945
9648
  return {
@@ -8948,11 +9651,11 @@ ${result.dbPull.output}`);
8948
9651
  }
8949
9652
  };
8950
9653
  _ts_decorate8([
8951
- (0, import_common24.Get)("models"),
8952
- (0, import_swagger12.ApiOperation)({
9654
+ (0, import_common25.Get)("models"),
9655
+ (0, import_swagger13.ApiOperation)({
8953
9656
  summary: "Dev-only: list DB models from the Prisma schema, flagging which already have a resource.json and whether the running backend can actually use them yet"
8954
9657
  }),
8955
- (0, import_swagger12.ApiResponse)({
9658
+ (0, import_swagger13.ApiResponse)({
8956
9659
  status: 200,
8957
9660
  description: "DB models and their resource/client status"
8958
9661
  }),
@@ -8961,11 +9664,11 @@ _ts_decorate8([
8961
9664
  _ts_metadata8("design:returntype", Promise)
8962
9665
  ], DevResourcesController.prototype, "listModels", null);
8963
9666
  _ts_decorate8([
8964
- (0, import_common24.Post)("restart"),
8965
- (0, import_swagger12.ApiOperation)({
9667
+ (0, import_common25.Post)("restart"),
9668
+ (0, import_swagger13.ApiOperation)({
8966
9669
  summary: "Dev-only: exit this process so a dev watcher/process manager (nodemon, `nest start --watch`, pm2, a Docker restart policy, ...) restarts it with a fresh Prisma client. Does nothing useful if this process isn't supervised by one of those."
8967
9670
  }),
8968
- (0, import_swagger12.ApiResponse)({
9671
+ (0, import_swagger13.ApiResponse)({
8969
9672
  status: 200,
8970
9673
  description: "Restart scheduled \u2014 the connection will drop shortly after"
8971
9674
  }),
@@ -8974,15 +9677,15 @@ _ts_decorate8([
8974
9677
  _ts_metadata8("design:returntype", Object)
8975
9678
  ], DevResourcesController.prototype, "restart", null);
8976
9679
  _ts_decorate8([
8977
- (0, import_common24.Post)("pull"),
8978
- (0, import_swagger12.ApiOperation)({
9680
+ (0, import_common25.Post)("pull"),
9681
+ (0, import_swagger13.ApiOperation)({
8979
9682
  summary: "Dev-only: run `prisma db pull` + case-format + `prisma generate` for a datasource, refreshing schema.prisma and the generated Prisma client/Zod types from the live database"
8980
9683
  }),
8981
- (0, import_swagger12.ApiResponse)({
9684
+ (0, import_swagger13.ApiResponse)({
8982
9685
  status: 200,
8983
9686
  description: "Result of each step, or requiresConfirmation if schema.prisma has uncommitted changes"
8984
9687
  }),
8985
- _ts_param2(0, (0, import_common24.Body)()),
9688
+ _ts_param2(0, (0, import_common25.Body)()),
8986
9689
  _ts_metadata8("design:type", Function),
8987
9690
  _ts_metadata8("design:paramtypes", [
8988
9691
  Object
@@ -8990,15 +9693,15 @@ _ts_decorate8([
8990
9693
  _ts_metadata8("design:returntype", Promise)
8991
9694
  ], DevResourcesController.prototype, "pull", null);
8992
9695
  _ts_decorate8([
8993
- (0, import_common24.Post)("sync"),
8994
- (0, import_swagger12.ApiOperation)({
9696
+ (0, import_common25.Post)("sync"),
9697
+ (0, import_swagger13.ApiOperation)({
8995
9698
  summary: "Dev-only: generate or update a single resource.json from its DB model, using recommended defaults (non-interactive)"
8996
9699
  }),
8997
- (0, import_swagger12.ApiResponse)({
9700
+ (0, import_swagger13.ApiResponse)({
8998
9701
  status: 200,
8999
9702
  description: "Files written for this resource"
9000
9703
  }),
9001
- _ts_param2(0, (0, import_common24.Body)()),
9704
+ _ts_param2(0, (0, import_common25.Body)()),
9002
9705
  _ts_metadata8("design:type", Function),
9003
9706
  _ts_metadata8("design:paramtypes", [
9004
9707
  Object
@@ -9006,15 +9709,15 @@ _ts_decorate8([
9006
9709
  _ts_metadata8("design:returntype", Promise)
9007
9710
  ], DevResourcesController.prototype, "sync", null);
9008
9711
  _ts_decorate8([
9009
- (0, import_common24.Post)("plan"),
9010
- (0, import_swagger12.ApiOperation)({
9712
+ (0, import_common25.Post)("plan"),
9713
+ (0, import_swagger13.ApiOperation)({
9011
9714
  summary: "Dev-only: dry-run introspect + diff across all (or selected) DB models using recommended defaults \u2014 computes what would change, writes nothing"
9012
9715
  }),
9013
- (0, import_swagger12.ApiResponse)({
9716
+ (0, import_swagger13.ApiResponse)({
9014
9717
  status: 200,
9015
9718
  description: "Proposed per-resource changes"
9016
9719
  }),
9017
- _ts_param2(0, (0, import_common24.Body)()),
9720
+ _ts_param2(0, (0, import_common25.Body)()),
9018
9721
  _ts_metadata8("design:type", Function),
9019
9722
  _ts_metadata8("design:paramtypes", [
9020
9723
  Object
@@ -9022,15 +9725,15 @@ _ts_decorate8([
9022
9725
  _ts_metadata8("design:returntype", Promise)
9023
9726
  ], DevResourcesController.prototype, "plan", null);
9024
9727
  _ts_decorate8([
9025
- (0, import_common24.Post)("apply"),
9026
- (0, import_swagger12.ApiOperation)({
9728
+ (0, import_common25.Post)("apply"),
9729
+ (0, import_swagger13.ApiOperation)({
9027
9730
  summary: "Dev-only: commit resource.json/schema.ts changes to disk for the given resources (or all, if omitted), using recommended defaults"
9028
9731
  }),
9029
- (0, import_swagger12.ApiResponse)({
9732
+ (0, import_swagger13.ApiResponse)({
9030
9733
  status: 200,
9031
9734
  description: "Per-resource commit results"
9032
9735
  }),
9033
- _ts_param2(0, (0, import_common24.Body)()),
9736
+ _ts_param2(0, (0, import_common25.Body)()),
9034
9737
  _ts_metadata8("design:type", Function),
9035
9738
  _ts_metadata8("design:paramtypes", [
9036
9739
  Object
@@ -9038,11 +9741,11 @@ _ts_decorate8([
9038
9741
  _ts_metadata8("design:returntype", Promise)
9039
9742
  ], DevResourcesController.prototype, "apply", null);
9040
9743
  _ts_decorate8([
9041
- (0, import_common24.Get)("visibility"),
9042
- (0, import_swagger12.ApiOperation)({
9744
+ (0, import_common25.Get)("visibility"),
9745
+ (0, import_swagger13.ApiOperation)({
9043
9746
  summary: "Dev-only: list all resources with their menu visibility state (in-menu, hidden, draft, error)"
9044
9747
  }),
9045
- (0, import_swagger12.ApiResponse)({
9748
+ (0, import_swagger13.ApiResponse)({
9046
9749
  status: 200,
9047
9750
  description: "Resource visibility list"
9048
9751
  }),
@@ -9051,15 +9754,15 @@ _ts_decorate8([
9051
9754
  _ts_metadata8("design:returntype", Promise)
9052
9755
  ], DevResourcesController.prototype, "visibility", null);
9053
9756
  _ts_decorate8([
9054
- (0, import_common24.Post)(":name/publish"),
9055
- (0, import_swagger12.ApiOperation)({
9757
+ (0, import_common25.Post)(":name/publish"),
9758
+ (0, import_swagger13.ApiOperation)({
9056
9759
  summary: "Dev-only: publish a draft resource (removes `draft: true` from resource.json)"
9057
9760
  }),
9058
- (0, import_swagger12.ApiResponse)({
9761
+ (0, import_swagger13.ApiResponse)({
9059
9762
  status: 200,
9060
9763
  description: "Resource published"
9061
9764
  }),
9062
- _ts_param2(0, (0, import_common24.Param)("name")),
9765
+ _ts_param2(0, (0, import_common25.Param)("name")),
9063
9766
  _ts_metadata8("design:type", Function),
9064
9767
  _ts_metadata8("design:paramtypes", [
9065
9768
  String
@@ -9067,15 +9770,15 @@ _ts_decorate8([
9067
9770
  _ts_metadata8("design:returntype", Promise)
9068
9771
  ], DevResourcesController.prototype, "publish", null);
9069
9772
  _ts_decorate8([
9070
- (0, import_common24.Post)(":name/remove-from-menu"),
9071
- (0, import_swagger12.ApiOperation)({
9773
+ (0, import_common25.Post)(":name/remove-from-menu"),
9774
+ (0, import_swagger13.ApiOperation)({
9072
9775
  summary: "Dev-only: hide a resource from the sidebar menu (sets `sidebar.hide: true` in resource.json)"
9073
9776
  }),
9074
- (0, import_swagger12.ApiResponse)({
9777
+ (0, import_swagger13.ApiResponse)({
9075
9778
  status: 200,
9076
9779
  description: "Resource removed from menu"
9077
9780
  }),
9078
- _ts_param2(0, (0, import_common24.Param)("name")),
9781
+ _ts_param2(0, (0, import_common25.Param)("name")),
9079
9782
  _ts_metadata8("design:type", Function),
9080
9783
  _ts_metadata8("design:paramtypes", [
9081
9784
  String
@@ -9083,16 +9786,16 @@ _ts_decorate8([
9083
9786
  _ts_metadata8("design:returntype", Promise)
9084
9787
  ], DevResourcesController.prototype, "removeFromMenu", null);
9085
9788
  _ts_decorate8([
9086
- (0, import_common24.Post)(":name/add-to-menu"),
9087
- (0, import_swagger12.ApiOperation)({
9789
+ (0, import_common25.Post)(":name/add-to-menu"),
9790
+ (0, import_swagger13.ApiOperation)({
9088
9791
  summary: "Dev-only: publish + un-hide a resource and optionally set sidebar group/position/label"
9089
9792
  }),
9090
- (0, import_swagger12.ApiResponse)({
9793
+ (0, import_swagger13.ApiResponse)({
9091
9794
  status: 200,
9092
9795
  description: "Resource added to menu"
9093
9796
  }),
9094
- _ts_param2(0, (0, import_common24.Param)("name")),
9095
- _ts_param2(1, (0, import_common24.Body)()),
9797
+ _ts_param2(0, (0, import_common25.Param)("name")),
9798
+ _ts_param2(1, (0, import_common25.Body)()),
9096
9799
  _ts_metadata8("design:type", Function),
9097
9800
  _ts_metadata8("design:paramtypes", [
9098
9801
  String,
@@ -9101,16 +9804,16 @@ _ts_decorate8([
9101
9804
  _ts_metadata8("design:returntype", Promise)
9102
9805
  ], DevResourcesController.prototype, "addToMenu", null);
9103
9806
  _ts_decorate8([
9104
- (0, import_common24.Patch)(":name/flags"),
9105
- (0, import_swagger12.ApiOperation)({
9807
+ (0, import_common25.Patch)(":name/flags"),
9808
+ (0, import_swagger13.ApiOperation)({
9106
9809
  summary: "Dev-only: set arbitrary resource flags (draft, sidebar.hide, group, position, label)"
9107
9810
  }),
9108
- (0, import_swagger12.ApiResponse)({
9811
+ (0, import_swagger13.ApiResponse)({
9109
9812
  status: 200,
9110
9813
  description: "Flags updated"
9111
9814
  }),
9112
- _ts_param2(0, (0, import_common24.Param)("name")),
9113
- _ts_param2(1, (0, import_common24.Body)()),
9815
+ _ts_param2(0, (0, import_common25.Param)("name")),
9816
+ _ts_param2(1, (0, import_common25.Body)()),
9114
9817
  _ts_metadata8("design:type", Function),
9115
9818
  _ts_metadata8("design:paramtypes", [
9116
9819
  String,
@@ -9119,8 +9822,8 @@ _ts_decorate8([
9119
9822
  _ts_metadata8("design:returntype", Promise)
9120
9823
  ], DevResourcesController.prototype, "updateFlags", null);
9121
9824
  DevResourcesController = _ts_decorate8([
9122
- (0, import_common24.Controller)("_app/resources"),
9123
- (0, import_swagger12.ApiTags)("Dev tools"),
9825
+ (0, import_common25.Controller)("_app/resources"),
9826
+ (0, import_swagger13.ApiTags)("Dev tools"),
9124
9827
  _ts_metadata8("design:type", Function),
9125
9828
  _ts_metadata8("design:paramtypes", [
9126
9829
  typeof DataSourceRegistry === "undefined" ? Object : DataSourceRegistry,
@@ -9129,12 +9832,12 @@ DevResourcesController = _ts_decorate8([
9129
9832
  ], DevResourcesController);
9130
9833
 
9131
9834
  // src/lib/crud/enum-registry/enum-registry.types.ts
9132
- var import_zod53 = require("zod");
9133
- var EnumEntrySchema2 = import_zod53.z.object({
9134
- value: import_zod53.z.unknown(),
9135
- label: import_zod53.z.string()
9835
+ var import_zod55 = require("zod");
9836
+ var EnumEntrySchema2 = import_zod55.z.object({
9837
+ value: import_zod55.z.unknown(),
9838
+ label: import_zod55.z.string()
9136
9839
  });
9137
- var EnumRegistrySchema2 = import_zod53.z.record(import_zod53.z.string(), import_zod53.z.array(EnumEntrySchema2)).default({});
9840
+ var EnumRegistrySchema2 = import_zod55.z.record(import_zod55.z.string(), import_zod55.z.array(EnumEntrySchema2)).default({});
9138
9841
 
9139
9842
  // src/lib/crud/enum-registry/enum-registry.loader.ts
9140
9843
  var import_node_fs8 = require("fs");
@@ -9182,7 +9885,7 @@ var injectEnumValues2 = /* @__PURE__ */ __name((columns, enums) => {
9182
9885
  }, "injectEnumValues");
9183
9886
 
9184
9887
  // src/lib/crud/interceptors/no-cache.interceptor.ts
9185
- var import_common25 = require("@nestjs/common");
9888
+ var import_common26 = require("@nestjs/common");
9186
9889
  function _ts_decorate9(decorators, target, key, desc2) {
9187
9890
  var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
9188
9891
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -9210,7 +9913,7 @@ var NoCacheInterceptor = class {
9210
9913
  }
9211
9914
  };
9212
9915
  NoCacheInterceptor = _ts_decorate9([
9213
- (0, import_common25.Injectable)()
9916
+ (0, import_common26.Injectable)()
9214
9917
  ], NoCacheInterceptor);
9215
9918
 
9216
9919
  // src/lib/crud/builder/schema.helpers.ts
@@ -9269,9 +9972,9 @@ var buildResourceDefinitions2 = /* @__PURE__ */ __name((schema, operations, enri
9269
9972
  }, "buildResourceDefinitions");
9270
9973
 
9271
9974
  // src/lib/crud/adapter/relation-type.ts
9272
- var import_zod54 = require("zod");
9975
+ var import_zod56 = require("zod");
9273
9976
  var unwrapZodType = /* @__PURE__ */ __name((type) => {
9274
- if (type instanceof import_zod54.ZodOptional || type instanceof import_zod54.ZodNullable) {
9977
+ if (type instanceof import_zod56.ZodOptional || type instanceof import_zod56.ZodNullable) {
9275
9978
  return unwrapZodType(type.unwrap());
9276
9979
  }
9277
9980
  return type;
@@ -9281,7 +9984,7 @@ var deriveRelationType = /* @__PURE__ */ __name((schema, columnId) => {
9281
9984
  const field = schema.shape[columnId];
9282
9985
  if (!field) return void 0;
9283
9986
  const inner = unwrapZodType(field);
9284
- return inner instanceof import_zod54.ZodArray ? "oneToMany" : "manyToOne";
9987
+ return inner instanceof import_zod56.ZodArray ? "oneToMany" : "manyToOne";
9285
9988
  }, "deriveRelationType");
9286
9989
  var deriveRelationTypeFromColumns = /* @__PURE__ */ __name((col, cols) => {
9287
9990
  const base = col.column ?? col.id;
@@ -9535,7 +10238,7 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
9535
10238
  const subResources = isCustom ? [] : buildSubResources(columns, json.route, json.model ?? "", dirPath, enums, baseUrl);
9536
10239
  const enrichedColumns = resolveColumnFieldVariants2(enrichResourceRefColumns(enrichActionColumns2(columns, json.route, subResources, baseUrl), dirPath, baseUrl) ?? columns);
9537
10240
  const calculatedColumns = json.calculatedColumns ?? [];
9538
- let views = buildViews(schema, enrichedColumns);
10241
+ let views = buildViews(schema, enrichedColumns, json.layout);
9539
10242
  if (views && calculatedColumns.length) {
9540
10243
  views = {
9541
10244
  ...views,
@@ -9717,6 +10420,20 @@ var validateResourceConfig = /* @__PURE__ */ __name((config, opts) => {
9717
10420
  warnings.push('repository.ts is present on a prisma resource and will be ignored \u2014 set kind: "custom" if you want to use it for data access.');
9718
10421
  }
9719
10422
  }
10423
+ const columns = config.columns ?? [];
10424
+ const columnIds = new Set(columns.map((c) => c.id));
10425
+ for (const col of columns) {
10426
+ const unique = normalizeUnique(col.unique);
10427
+ if (!unique) continue;
10428
+ if (col.idField) {
10429
+ warnings.push(`Column "${col.id}" is the id field; "unique" is redundant there.`);
10430
+ }
10431
+ for (const scopeId of unique.scope ?? []) {
10432
+ if (!columnIds.has(scopeId)) {
10433
+ errors.push(`Column "${col.id}" has unique.scope referencing unknown column "${scopeId}".`);
10434
+ }
10435
+ }
10436
+ }
9720
10437
  return {
9721
10438
  errors,
9722
10439
  warnings
@@ -10176,8 +10893,8 @@ var buildStatus = /* @__PURE__ */ __name(async (registry2, loadedConfigs, enumRe
10176
10893
  }, "buildStatus");
10177
10894
 
10178
10895
  // src/lib/crud/status/status.controller.ts
10179
- var import_common26 = require("@nestjs/common");
10180
- var import_swagger13 = require("@nestjs/swagger");
10896
+ var import_common27 = require("@nestjs/common");
10897
+ var import_swagger14 = require("@nestjs/swagger");
10181
10898
  function _ts_decorate10(decorators, target, key, desc2) {
10182
10899
  var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
10183
10900
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -10215,11 +10932,11 @@ var createStatusController = /* @__PURE__ */ __name((enumRegistry, translationRe
10215
10932
  }
10216
10933
  };
10217
10934
  _ts_decorate10([
10218
- (0, import_common26.Get)("status.json"),
10219
- (0, import_swagger13.ApiOperation)({
10935
+ (0, import_common27.Get)("status.json"),
10936
+ (0, import_swagger14.ApiOperation)({
10220
10937
  summary: "Crouton system status (db, resources, version)"
10221
10938
  }),
10222
- (0, import_swagger13.ApiResponse)({
10939
+ (0, import_swagger14.ApiResponse)({
10223
10940
  status: 200,
10224
10941
  description: "System status"
10225
10942
  }),
@@ -10228,8 +10945,8 @@ var createStatusController = /* @__PURE__ */ __name((enumRegistry, translationRe
10228
10945
  _ts_metadata9("design:returntype", Promise)
10229
10946
  ], StatusController.prototype, "getStatus", null);
10230
10947
  StatusController = _ts_decorate10([
10231
- (0, import_common26.Controller)(prefix ? `${prefix}/crouton` : "crouton"),
10232
- (0, import_swagger13.ApiTags)("Status"),
10948
+ (0, import_common27.Controller)(prefix ? `${prefix}/crouton` : "crouton"),
10949
+ (0, import_swagger14.ApiTags)("Status"),
10233
10950
  _ts_metadata9("design:type", Function),
10234
10951
  _ts_metadata9("design:paramtypes", [
10235
10952
  typeof DataSourceRegistry === "undefined" ? Object : DataSourceRegistry,
@@ -10344,6 +11061,7 @@ var CroutonApiModule = class _CroutonApiModule {
10344
11061
  }
10345
11062
  validConfigs.push(c);
10346
11063
  }
11064
+ warnMissingUniqueConstraints(validConfigs, dataSourceRegistry);
10347
11065
  const configRegistry = new ResourceConfigRegistry(loader, validConfigs);
10348
11066
  let translationRegistry;
10349
11067
  if (config.i18n) {
@@ -10421,7 +11139,7 @@ var CroutonApiModule = class _CroutonApiModule {
10421
11139
  }
10422
11140
  };
10423
11141
  CroutonApiModule = _ts_decorate11([
10424
- (0, import_common27.Module)({
11142
+ (0, import_common28.Module)({
10425
11143
  controllers: [],
10426
11144
  providers: [],
10427
11145
  exports: []