nextjs-cms 0.5.9 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist/api/axios/axiosInstance.d.ts +1 -1
  2. package/dist/api/axios/axiosInstance.js +8 -8
  3. package/dist/api/index.d.ts +855 -855
  4. package/dist/api/index.d.ts.map +1 -1
  5. package/dist/api/index.js +12 -12
  6. package/dist/api/lib/serverActions.d.ts +239 -239
  7. package/dist/api/lib/serverActions.d.ts.map +1 -1
  8. package/dist/api/lib/serverActions.js +834 -834
  9. package/dist/api/root.d.ts +828 -828
  10. package/dist/api/root.js +30 -30
  11. package/dist/api/routers/accountSettings.d.ts +60 -60
  12. package/dist/api/routers/accountSettings.js +108 -108
  13. package/dist/api/routers/admins.d.ts +105 -105
  14. package/dist/api/routers/admins.js +219 -219
  15. package/dist/api/routers/auth.d.ts +47 -47
  16. package/dist/api/routers/auth.js +25 -25
  17. package/dist/api/routers/categorySection.d.ts +103 -103
  18. package/dist/api/routers/categorySection.js +38 -38
  19. package/dist/api/routers/cmsSettings.d.ts +48 -48
  20. package/dist/api/routers/cmsSettings.js +51 -51
  21. package/dist/api/routers/cpanel.d.ts +83 -83
  22. package/dist/api/routers/cpanel.js +216 -216
  23. package/dist/api/routers/files.d.ts +47 -47
  24. package/dist/api/routers/files.js +23 -23
  25. package/dist/api/routers/gallery.d.ts +35 -35
  26. package/dist/api/routers/gallery.js +62 -62
  27. package/dist/api/routers/googleAnalytics.d.ts +30 -30
  28. package/dist/api/routers/googleAnalytics.js +7 -7
  29. package/dist/api/routers/hasItemsSection.d.ts +139 -139
  30. package/dist/api/routers/hasItemsSection.js +34 -34
  31. package/dist/api/routers/navigation.d.ts +51 -51
  32. package/dist/api/routers/navigation.js +11 -11
  33. package/dist/api/routers/simpleSection.d.ts +57 -57
  34. package/dist/api/routers/simpleSection.js +12 -12
  35. package/dist/api/trpc.d.ts +106 -106
  36. package/dist/api/trpc.js +72 -72
  37. package/dist/auth/axios/axiosInstance.d.ts +1 -1
  38. package/dist/auth/axios/axiosInstance.js +8 -8
  39. package/dist/auth/csrf.d.ts +29 -29
  40. package/dist/auth/csrf.js +76 -76
  41. package/dist/auth/hooks/index.d.ts +3 -3
  42. package/dist/auth/hooks/index.d.ts.map +1 -1
  43. package/dist/auth/hooks/index.js +3 -3
  44. package/dist/auth/hooks/useAxiosPrivate.d.ts +4 -4
  45. package/dist/auth/hooks/useAxiosPrivate.js +74 -74
  46. package/dist/auth/hooks/useRefreshToken.d.ts +6 -6
  47. package/dist/auth/hooks/useRefreshToken.js +79 -79
  48. package/dist/auth/index.d.ts +22 -22
  49. package/dist/auth/index.js +44 -44
  50. package/dist/auth/jwt.d.ts +5 -5
  51. package/dist/auth/jwt.js +25 -25
  52. package/dist/auth/lib/actions.d.ts +32 -32
  53. package/dist/auth/lib/actions.d.ts.map +1 -1
  54. package/dist/auth/lib/actions.js +209 -209
  55. package/dist/auth/lib/client.d.ts +3 -3
  56. package/dist/auth/lib/client.js +46 -46
  57. package/dist/auth/lib/index.d.ts +2 -2
  58. package/dist/auth/lib/index.d.ts.map +1 -1
  59. package/dist/auth/lib/index.js +2 -2
  60. package/dist/auth/react.d.ts +105 -105
  61. package/dist/auth/react.d.ts.map +1 -1
  62. package/dist/auth/react.js +347 -347
  63. package/dist/auth/trpc.d.ts +5 -5
  64. package/dist/auth/trpc.d.ts.map +1 -1
  65. package/dist/auth/trpc.js +81 -81
  66. package/dist/core/config/config-loader.d.ts +91 -91
  67. package/dist/core/config/config-loader.js +230 -230
  68. package/dist/core/config/index.d.ts +2 -2
  69. package/dist/core/config/index.d.ts.map +1 -1
  70. package/dist/core/config/index.js +1 -1
  71. package/dist/core/config/loader.d.ts +1 -1
  72. package/dist/core/config/loader.js +42 -42
  73. package/dist/core/db/index.d.ts +1 -1
  74. package/dist/core/db/index.d.ts.map +1 -1
  75. package/dist/core/db/index.js +1 -1
  76. package/dist/core/db/table-checker/DbTable.d.ts +5 -5
  77. package/dist/core/db/table-checker/DbTable.js +5 -5
  78. package/dist/core/db/table-checker/MysqlTable.d.ts +33 -33
  79. package/dist/core/db/table-checker/MysqlTable.d.ts.map +1 -1
  80. package/dist/core/db/table-checker/MysqlTable.js +94 -94
  81. package/dist/core/db/table-checker/index.d.ts +1 -1
  82. package/dist/core/db/table-checker/index.d.ts.map +1 -1
  83. package/dist/core/db/table-checker/index.js +1 -1
  84. package/dist/core/factories/FieldFactory.d.ts +123 -123
  85. package/dist/core/factories/FieldFactory.d.ts.map +1 -1
  86. package/dist/core/factories/FieldFactory.js +411 -411
  87. package/dist/core/factories/SectionFactory.d.ts +109 -109
  88. package/dist/core/factories/SectionFactory.d.ts.map +1 -1
  89. package/dist/core/factories/SectionFactory.js +415 -415
  90. package/dist/core/factories/index.d.ts +2 -2
  91. package/dist/core/factories/index.d.ts.map +1 -1
  92. package/dist/core/factories/index.js +2 -2
  93. package/dist/core/fields/checkbox.d.ts +62 -62
  94. package/dist/core/fields/checkbox.d.ts.map +1 -1
  95. package/dist/core/fields/checkbox.js +62 -62
  96. package/dist/core/fields/color.d.ts +83 -83
  97. package/dist/core/fields/color.d.ts.map +1 -1
  98. package/dist/core/fields/color.js +91 -91
  99. package/dist/core/fields/date.d.ts +99 -99
  100. package/dist/core/fields/date.d.ts.map +1 -1
  101. package/dist/core/fields/date.js +108 -108
  102. package/dist/core/fields/document.d.ts +179 -179
  103. package/dist/core/fields/document.d.ts.map +1 -1
  104. package/dist/core/fields/document.js +277 -277
  105. package/dist/core/fields/field-group.d.ts +17 -17
  106. package/dist/core/fields/field-group.d.ts.map +1 -1
  107. package/dist/core/fields/field-group.js +6 -6
  108. package/dist/core/fields/field.d.ts +125 -125
  109. package/dist/core/fields/field.d.ts.map +1 -1
  110. package/dist/core/fields/field.js +148 -148
  111. package/dist/core/fields/fileField.d.ts +14 -14
  112. package/dist/core/fields/fileField.d.ts.map +1 -1
  113. package/dist/core/fields/fileField.js +5 -5
  114. package/dist/core/fields/index.d.ts +64 -64
  115. package/dist/core/fields/index.d.ts.map +1 -1
  116. package/dist/core/fields/index.js +18 -18
  117. package/dist/core/fields/map.d.ts +166 -166
  118. package/dist/core/fields/map.d.ts.map +1 -1
  119. package/dist/core/fields/map.js +152 -152
  120. package/dist/core/fields/number.d.ts +185 -185
  121. package/dist/core/fields/number.d.ts.map +1 -1
  122. package/dist/core/fields/number.js +241 -241
  123. package/dist/core/fields/password.d.ts +108 -108
  124. package/dist/core/fields/password.d.ts.map +1 -1
  125. package/dist/core/fields/password.js +133 -133
  126. package/dist/core/fields/photo.d.ts +288 -288
  127. package/dist/core/fields/photo.d.ts.map +1 -1
  128. package/dist/core/fields/photo.js +410 -410
  129. package/dist/core/fields/richText.d.ts +294 -294
  130. package/dist/core/fields/richText.d.ts.map +1 -1
  131. package/dist/core/fields/richText.js +338 -338
  132. package/dist/core/fields/select.d.ts +365 -365
  133. package/dist/core/fields/select.d.ts.map +1 -1
  134. package/dist/core/fields/select.js +499 -499
  135. package/dist/core/fields/selectMultiple.d.ts +235 -235
  136. package/dist/core/fields/selectMultiple.d.ts.map +1 -1
  137. package/dist/core/fields/selectMultiple.js +417 -417
  138. package/dist/core/fields/tags.d.ts +130 -130
  139. package/dist/core/fields/tags.d.ts.map +1 -1
  140. package/dist/core/fields/tags.js +105 -105
  141. package/dist/core/fields/text.d.ts +135 -135
  142. package/dist/core/fields/text.d.ts.map +1 -1
  143. package/dist/core/fields/text.js +157 -157
  144. package/dist/core/fields/textArea.d.ts +106 -106
  145. package/dist/core/fields/textArea.d.ts.map +1 -1
  146. package/dist/core/fields/textArea.js +126 -126
  147. package/dist/core/fields/video.d.ts +147 -147
  148. package/dist/core/fields/video.d.ts.map +1 -1
  149. package/dist/core/fields/video.js +248 -248
  150. package/dist/core/helpers/entity.d.ts +7 -7
  151. package/dist/core/helpers/entity.js +27 -27
  152. package/dist/core/helpers/index.d.ts +4 -4
  153. package/dist/core/helpers/index.d.ts.map +1 -1
  154. package/dist/core/helpers/index.js +3 -3
  155. package/dist/core/index.d.ts +7 -7
  156. package/dist/core/index.d.ts.map +1 -1
  157. package/dist/core/index.js +7 -7
  158. package/dist/core/sections/category.d.ts +282 -282
  159. package/dist/core/sections/category.d.ts.map +1 -1
  160. package/dist/core/sections/category.js +147 -147
  161. package/dist/core/sections/hasItems.d.ts +631 -631
  162. package/dist/core/sections/hasItems.d.ts.map +1 -1
  163. package/dist/core/sections/hasItems.js +144 -144
  164. package/dist/core/sections/index.d.ts +4 -4
  165. package/dist/core/sections/index.d.ts.map +1 -1
  166. package/dist/core/sections/index.js +4 -4
  167. package/dist/core/sections/section.d.ts +225 -225
  168. package/dist/core/sections/section.d.ts.map +1 -1
  169. package/dist/core/sections/section.js +341 -341
  170. package/dist/core/sections/simple.d.ts +98 -98
  171. package/dist/core/sections/simple.d.ts.map +1 -1
  172. package/dist/core/sections/simple.js +95 -95
  173. package/dist/core/security/dom.d.ts +10 -10
  174. package/dist/core/security/dom.js +92 -92
  175. package/dist/core/submit/ItemEditSubmit.d.ts +75 -75
  176. package/dist/core/submit/ItemEditSubmit.js +186 -186
  177. package/dist/core/submit/NewItemSubmit.d.ts +13 -13
  178. package/dist/core/submit/NewItemSubmit.js +93 -93
  179. package/dist/core/submit/SimpleSectionSubmit.d.ts +12 -12
  180. package/dist/core/submit/SimpleSectionSubmit.js +93 -93
  181. package/dist/core/submit/index.d.ts +4 -4
  182. package/dist/core/submit/index.js +4 -4
  183. package/dist/core/submit/submit.d.ts +115 -115
  184. package/dist/core/submit/submit.js +479 -479
  185. package/dist/core/types/index.d.ts +279 -279
  186. package/dist/core/types/index.d.ts.map +1 -1
  187. package/dist/core/types/index.js +1 -1
  188. package/dist/db/client.d.ts +8 -8
  189. package/dist/db/client.d.ts.map +1 -1
  190. package/dist/db/client.js +19 -19
  191. package/dist/db/config.d.ts +5 -5
  192. package/dist/db/config.js +22 -22
  193. package/dist/db/drizzle.config.d.ts +5 -5
  194. package/dist/db/drizzle.config.js +18 -18
  195. package/dist/db/index.d.ts +2 -2
  196. package/dist/db/index.js +3 -3
  197. package/dist/db/schema.d.ts +638 -638
  198. package/dist/db/schema.js +73 -73
  199. package/dist/index.d.ts +7 -7
  200. package/dist/index.d.ts.map +1 -1
  201. package/dist/index.js +7 -7
  202. package/dist/translations/index.d.ts +2 -2
  203. package/dist/translations/index.js +15 -15
  204. package/dist/utils/CpanelApi.d.ts +24 -24
  205. package/dist/utils/CpanelApi.js +64 -64
  206. package/dist/utils/constants.d.ts +13 -13
  207. package/dist/utils/constants.js +61 -61
  208. package/dist/utils/index.d.ts +4 -4
  209. package/dist/utils/index.d.ts.map +1 -1
  210. package/dist/utils/index.js +4 -4
  211. package/dist/utils/utils.d.ts +59 -59
  212. package/dist/utils/utils.js +132 -132
  213. package/dist/validators/checkbox.d.ts +3 -3
  214. package/dist/validators/checkbox.d.ts.map +1 -1
  215. package/dist/validators/checkbox.js +12 -12
  216. package/dist/validators/color.d.ts +3 -3
  217. package/dist/validators/color.d.ts.map +1 -1
  218. package/dist/validators/color.js +7 -7
  219. package/dist/validators/date.d.ts +3 -3
  220. package/dist/validators/date.d.ts.map +1 -1
  221. package/dist/validators/date.js +5 -5
  222. package/dist/validators/document.d.ts +3 -3
  223. package/dist/validators/document.d.ts.map +1 -1
  224. package/dist/validators/document.js +57 -57
  225. package/dist/validators/index.d.ts +14 -14
  226. package/dist/validators/index.d.ts.map +1 -1
  227. package/dist/validators/index.js +14 -14
  228. package/dist/validators/map.d.ts +3 -3
  229. package/dist/validators/map.d.ts.map +1 -1
  230. package/dist/validators/map.js +5 -5
  231. package/dist/validators/number.d.ts +3 -3
  232. package/dist/validators/number.d.ts.map +1 -1
  233. package/dist/validators/number.js +20 -20
  234. package/dist/validators/password.d.ts +3 -3
  235. package/dist/validators/password.d.ts.map +1 -1
  236. package/dist/validators/password.js +11 -11
  237. package/dist/validators/photo.d.ts +3 -3
  238. package/dist/validators/photo.d.ts.map +1 -1
  239. package/dist/validators/photo.js +100 -100
  240. package/dist/validators/richText.d.ts +3 -3
  241. package/dist/validators/richText.d.ts.map +1 -1
  242. package/dist/validators/richText.js +8 -8
  243. package/dist/validators/select-multiple.d.ts +9 -9
  244. package/dist/validators/select-multiple.d.ts.map +1 -1
  245. package/dist/validators/select-multiple.js +20 -20
  246. package/dist/validators/select.d.ts +3 -3
  247. package/dist/validators/select.d.ts.map +1 -1
  248. package/dist/validators/select.js +5 -5
  249. package/dist/validators/text.d.ts +3 -3
  250. package/dist/validators/text.d.ts.map +1 -1
  251. package/dist/validators/text.js +7 -7
  252. package/dist/validators/textarea.d.ts +3 -3
  253. package/dist/validators/textarea.d.ts.map +1 -1
  254. package/dist/validators/textarea.js +7 -7
  255. package/dist/validators/video.d.ts +3 -3
  256. package/dist/validators/video.d.ts.map +1 -1
  257. package/dist/validators/video.js +57 -57
  258. package/package.json +2 -3
@@ -1,499 +1,499 @@
1
- import { Field, baseFieldConfigSchema } from "./field.js";
2
- import { entityKind } from "../helpers.js";
3
- import { MysqlTableChecker } from "../db.js";
4
- import { db } from "../../db/client.js";
5
- import { sql } from 'drizzle-orm';
6
- import chalk from 'chalk';
7
- import * as z from 'zod';
8
- export const selectOptionSchema = z.strictObject({
9
- value: z.union([z.string(), z.number()]).describe('Stored value for this option'),
10
- label: z.string().describe('Human-readable label for the option'),
11
- });
12
- export const selectFieldDestinationDbSchema = z.strictObject({
13
- table: z.string().min(1).describe('Destination table where select values will be stored'),
14
- /**
15
- * The identifier of the item that has this select field as a field
16
- */
17
- itemIdentifier: z.string().min(1).describe('Destination item identifier column'),
18
- /**
19
- * The identifier of the value of the select option that is coming from the select options
20
- */
21
- selectIdentifier: z.string().min(1).describe('Destination select identifier column'),
22
- });
23
- const selectFieldSharedExtrasSchema = z.strictObject({
24
- /**
25
- * A destination table where the select values will be saved
26
- * When this is set, the value of the field will only be saved in the destination table as one row per each value
27
- */
28
- destinationDb: selectFieldDestinationDbSchema.optional(),
29
- checkValueExists: z.boolean().optional(),
30
- });
31
- const selectFieldDbConfigSchema = selectFieldSharedExtrasSchema.extend({
32
- options: z.never().optional(),
33
- db: z
34
- .strictObject({
35
- table: z.string().min(1).describe('Database table to read select options from'),
36
- identifier: z.string().min(1).describe('Identifier column for the option'),
37
- label: z.string().min(1).describe('Label column for the option'),
38
- orderBy: z.string().optional().describe('Optional order by column'),
39
- })
40
- .describe('Database configuration for select options'),
41
- section: z.never().optional(),
42
- });
43
- const selectFieldSectionConfigSchema = selectFieldSharedExtrasSchema.extend({
44
- options: z.array(selectOptionSchema).optional(),
45
- db: z.never().optional(),
46
- section: z.custom().describe('Section to derive select options from'),
47
- });
48
- const selectFieldStaticConfigSchema = selectFieldSharedExtrasSchema.extend({
49
- options: z.array(selectOptionSchema).min(1).describe('List of static select options'),
50
- db: z.never().optional(),
51
- section: z.never().optional(),
52
- });
53
- export const selectFieldSpecificConfigSchema = z.union([
54
- selectFieldDbConfigSchema,
55
- selectFieldSectionConfigSchema,
56
- selectFieldStaticConfigSchema,
57
- ]);
58
- export class SelectField extends Field {
59
- static [entityKind] = 'SelectField';
60
- checkValueExists;
61
- optionsType;
62
- db;
63
- destinationDb = undefined;
64
- value = undefined;
65
- // private _values: string[] | number[] | undefined
66
- section;
67
- _itemIdentifier;
68
- options;
69
- _allOptions;
70
- constructor(config) {
71
- super(config, 'select');
72
- this.checkValueExists = config.checkValueExists ?? false;
73
- if (config.section) {
74
- this.destinationDb = config.destinationDb;
75
- this.section = config.section;
76
- }
77
- /**
78
- * If options are static, set them
79
- */
80
- if (config.options && config.options?.length > 0) {
81
- this.options = config.options;
82
- this.optionsType = 'static';
83
- this.db = null;
84
- }
85
- else if (config.db) {
86
- /**
87
- * Else, check if the db config is set
88
- */
89
- this.db = config.db;
90
- this.optionsType = 'db';
91
- }
92
- else if (config.section) {
93
- /**
94
- * Else, check if the section config is set
95
- */
96
- /**
97
- * Runtime validation: Ensure the section is a HasItemsSection or CategorySection
98
- * TypeScript ensures type safety at compile time, but runtime data (from JSON/APIs)
99
- * can violate types, so we validate here for safety.
100
- */
101
- const sectionType = config.section.type;
102
- if (sectionType !== 'has_items' && sectionType !== 'category') {
103
- const message = `[SelectMultipleField: ${this.label}]: Section must be a HasItemsSection or CategorySection, but got type: ${sectionType ?? 'undefined'}`;
104
- console.error(chalk.red.bold(message));
105
- console.error(chalk.yellow('Please make sure the section is a HasItemsSection or CategorySection'));
106
- throw new Error(message);
107
- }
108
- /**
109
- * Build the section and set the options type to section
110
- */
111
- const section = config.section.build();
112
- this.optionsType = 'section';
113
- this.db = {
114
- table: section.db.table,
115
- identifier: section.db.identifier.name,
116
- label: section.headingField.name,
117
- orderBy: section.db.orderByField ? section.db.orderByField.name : section.db.identifier.name,
118
- };
119
- }
120
- else {
121
- throw new Error('Select field requires either db, section or an options array.');
122
- }
123
- }
124
- /**
125
- * @param value The value of a single select field,
126
- * or values array of multiple select fields if the select is selecting from
127
- * a category section with depth higher than 1.
128
- */
129
- setValue(value) {
130
- /**
131
- * Set the value
132
- */
133
- if (value) {
134
- /**
135
- * Value can be a string of comma separated values if destinationDb is not set
136
- */
137
- if (typeof value === 'string') {
138
- /**
139
- * Split the values by comma and set the value as an array of objects
140
- */
141
- const values = value.toString().split(',');
142
- values.map((value) => {
143
- if (this.checkValueExists) {
144
- /**
145
- * Check if the value exists in the options and return the value and text
146
- */
147
- const option = this.options?.find((option) => option.value.toString() === value.toString());
148
- if (option) {
149
- if (!this.value) {
150
- this.value = [];
151
- }
152
- this.value.push(option);
153
- }
154
- }
155
- else {
156
- if (!this.value) {
157
- this.value = [];
158
- }
159
- this.value.push({
160
- value: value,
161
- label: '', // TODO: Fix this
162
- });
163
- }
164
- });
165
- }
166
- else {
167
- /**
168
- * Value is an array of objects
169
- */
170
- this.value = value;
171
- }
172
- }
173
- else {
174
- this.value = undefined;
175
- }
176
- }
177
- hasSqlNameAndValue() {
178
- return this.destinationDb === undefined;
179
- }
180
- hasDepth() {
181
- return this.section !== undefined && this.section.type === 'category' && (this.section.depth ?? 1) > 1;
182
- }
183
- /**
184
- * Get the value of the field
185
- */
186
- getValue() {
187
- /**
188
- * Value is always a string | number
189
- */
190
- return this.value;
191
- }
192
- /**
193
- * Get the value of the field when submitting to the database
194
- */
195
- getSubmitValue() {
196
- if (!this.hasSqlNameAndValue())
197
- return;
198
- /**
199
- * Set the value as a comma separated string of the values
200
- */
201
- return this.value?.map((item) => item.value).join(',');
202
- }
203
- exportForClient() {
204
- return {
205
- ...super.exportForClient(),
206
- options: this.options,
207
- section: this.section
208
- ? {
209
- name: this.section.name,
210
- depth: this.section.type === 'category' ? this.section.depth : 1,
211
- }
212
- : undefined,
213
- /**
214
- * Add the values with the level if present
215
- */
216
- value: this.value?.map((value, index) => ({ ...value, level: index + 1 })),
217
- };
218
- }
219
- async postSubmit(itemId) {
220
- if (!this.destinationDb)
221
- return;
222
- /**
223
- * First, check if the select is selecting from a category section with depth higher than 1
224
- */
225
- // if (!this.hasDepth()) return
226
- this._itemIdentifier = itemId;
227
- try {
228
- /**
229
- * Delete the existing values
230
- */
231
- await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
232
- /**
233
- * Prepare the sql statement
234
- */
235
- const sqlChunks = [];
236
- sqlChunks.push(sql `INSERT INTO \`${sql.raw(this.destinationDb.table)}\` (\`${sql.raw(this.destinationDb.selectIdentifier)}\`, \`${sql.raw(this.destinationDb.itemIdentifier)}\`) VALUES `);
237
- /**
238
- * Loop through the values and add them to the sql statement values
239
- */
240
- if (this.value && this.value.length > 0) {
241
- const count = this.value.length;
242
- this.value.forEach((value, index) => {
243
- sqlChunks.push(sql `(${value.value}, ${this._itemIdentifier})`);
244
- if (index < count - 1) {
245
- /**
246
- * Add a comma to separate the values in the sql statement
247
- */
248
- sqlChunks.push(sql `, `);
249
- }
250
- });
251
- }
252
- /**
253
- * Join the sql chunks
254
- */
255
- const finalSql = sql.join(sqlChunks);
256
- /**
257
- * Execute the sql statement
258
- */
259
- await db.execute(finalSql);
260
- }
261
- catch (error) {
262
- console.error(error);
263
- throw new Error(`${this.label}: Error saving values to the destination table`);
264
- }
265
- }
266
- async postSubmitRollback() {
267
- if (!this.destinationDb)
268
- return;
269
- /**
270
- * First, check if the select is selecting from a category section with depth higher than 1
271
- */
272
- // if (!this.hasDepth()) return
273
- if (!this._itemIdentifier) {
274
- throw new Error(`${this.label}: Item identifier is not set. Make sure to set the item identifier by calling postSubmit() before calling postSubmitRollback()`);
275
- }
276
- try {
277
- await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
278
- }
279
- catch (error) {
280
- throw new Error(`${this.label}: Error deleting values from the destination table`);
281
- }
282
- }
283
- /**
284
- * Check the destination db config is set
285
- * @private
286
- */
287
- checkDestinationDBConfig() {
288
- if (this.destinationDb?.table.trim().length === 0 ||
289
- this.destinationDb?.selectIdentifier.trim().length === 0 ||
290
- this.destinationDb?.itemIdentifier.trim().length === 0) {
291
- throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
292
- }
293
- }
294
- /**
295
- * Check if destination table and columns exist
296
- * @private
297
- */
298
- async checkDestinationDBTableAndColumns() {
299
- if (!this.destinationDb) {
300
- throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
301
- }
302
- const columns = await MysqlTableChecker.getColumns(this.destinationDb.table);
303
- if (!columns.includes(this.destinationDb.selectIdentifier) ||
304
- !columns.includes(this.destinationDb.itemIdentifier)) {
305
- throw new Error(`Field ${this.label}: Table ${this.destinationDb.table} and/or columns: ${this.destinationDb.selectIdentifier}, ${this.destinationDb.itemIdentifier} do not exist in the database!`);
306
- }
307
- }
308
- /**
309
- * Build the field to be used in a form
310
- */
311
- async build() {
312
- if (this.destinationDb) {
313
- this.checkDestinationDBConfig();
314
- await this.checkDestinationDBTableAndColumns();
315
- }
316
- /**
317
- * If options are already set, return
318
- */
319
- if (this.options && this.optionsType === 'static')
320
- return;
321
- /**
322
- * Initialize the select db and options
323
- */
324
- this.checkDBConfig();
325
- await this.checkDBTableAndColumns();
326
- /**
327
- * Fetch the options
328
- */
329
- await this.fetchOptions();
330
- }
331
- async fetchOptions() {
332
- /**
333
- * Let's get the options for the select input
334
- */
335
- const sqlChunks = [];
336
- // Initialize the select statement
337
- sqlChunks.push(sql `select * from \`${sql.raw(this.db.table)}\``);
338
- /**
339
- * Check if the select is from a category section with depth higher than 1
340
- */
341
- if (this.hasDepth()) {
342
- /**
343
- * Add the where clause to the select statement
344
- */
345
- sqlChunks.push(sql ` WHERE parent_id IS NULL AND level = 1`);
346
- }
347
- /**
348
- * Add order by clause
349
- */
350
- sqlChunks.push(sql ` ORDER BY \`${sql.raw(this.db.orderBy ?? this.db.identifier)}\` DESC`);
351
- /**
352
- * Get the options from the table
353
- */
354
- const selectOptionsRows = await db.execute(sql.join(sqlChunks));
355
- const rows = selectOptionsRows[0];
356
- const inputSelectTableIdentifierField = this.db.identifier;
357
- const inputSelectTableHeadingField = this.db.label;
358
- /**
359
- * Set the options
360
- */
361
- this.options = rows.map((row) => ({
362
- value: row[inputSelectTableIdentifierField],
363
- label: row[inputSelectTableHeadingField],
364
- }));
365
- this._allOptions = this.options;
366
- if (this.hasDepth()) {
367
- /**
368
- * Get all the options
369
- */
370
- const selectOptionsRows1 = await db.execute(sql `select * from \`${sql.raw(this.db.table)}\` WHERE parent_id IS NOT NULL AND level > 1`);
371
- const rows1 = selectOptionsRows1[0];
372
- const tempRows = rows1.map((row) => ({
373
- value: row[inputSelectTableIdentifierField],
374
- label: row[inputSelectTableHeadingField],
375
- }));
376
- this._allOptions = this._allOptions.concat(tempRows);
377
- }
378
- }
379
- /**
380
- * Check if table and columns exist
381
- * @private
382
- */
383
- async checkDBTableAndColumns() {
384
- const columns = await MysqlTableChecker.getColumns(this.db.table);
385
- if (!columns.includes(this.db.identifier) || !columns.includes(this.db.label)) {
386
- throw new Error(`Field ${this.label}: Table ${this.db.table} and/or columns: ${this.db.identifier}, ${this.db.label} do not exist in the database!`);
387
- }
388
- }
389
- /**
390
- * Check the db config is set
391
- * @private
392
- */
393
- checkDBConfig() {
394
- if (this.db.table.trim().length === 0 ||
395
- this.db.identifier.trim().length === 0 ||
396
- this.db.label.trim().length === 0) {
397
- throw new Error('Table, identifier and label are required');
398
- }
399
- }
400
- checkRequired() {
401
- /**
402
- * Check if the field is required
403
- */
404
- if (this.required) {
405
- if (!this.value || this.value.toString().trim().length === 0) {
406
- throw new Error(`Field ${this.label} is required`);
407
- }
408
- }
409
- }
410
- /**
411
- * Prepare the field for submission
412
- */
413
- async prepareForSubmission() {
414
- if (this.optionsType !== 'static') {
415
- this.checkDBConfig();
416
- await this.checkDBTableAndColumns();
417
- /**
418
- * Fetch the options
419
- */
420
- await this.fetchOptions();
421
- }
422
- if (this.destinationDb) {
423
- this.checkDestinationDBConfig();
424
- await this.checkDestinationDBTableAndColumns();
425
- }
426
- /**
427
- * If checkValueExists is set to true, check if the value exists in the options.
428
- * This is important to prevent submitting a value that does not exist in the table. (e.g. tampering with the form)
429
- * Default is false.
430
- */
431
- if (this.checkValueExists && this.value && this.value.length > 0) {
432
- /**
433
- * Check if the values exist in the table
434
- * This is now safe because we have verified the table and columns exist in the database
435
- */
436
- this.value.forEach((value) => {
437
- if (!this._allOptions?.find((option) => option.value.toString() === value.value.toString())) {
438
- throw new Error(`Field ${this.label}: Option ${value.label} does not exist in the options`);
439
- }
440
- });
441
- }
442
- }
443
- }
444
- const selectFieldDbOptionsSchema = z.strictObject({
445
- ...baseFieldConfigSchema.shape,
446
- ...selectFieldDbConfigSchema.shape,
447
- });
448
- const selectFieldSectionOptionsSchema = z.strictObject({
449
- ...baseFieldConfigSchema.shape,
450
- ...selectFieldSectionConfigSchema.shape,
451
- });
452
- const selectFieldStaticOptionsSchema = z.strictObject({
453
- ...baseFieldConfigSchema.shape,
454
- ...selectFieldStaticConfigSchema.shape,
455
- });
456
- export const selectFieldOptionsSchema = z.union([
457
- selectFieldDbOptionsSchema,
458
- selectFieldSectionOptionsSchema,
459
- selectFieldStaticOptionsSchema,
460
- ]);
461
- const selectFieldConfigSchema = z.intersection(selectFieldOptionsSchema, z.strictObject({
462
- type: z.literal('select').describe('The type of the field'),
463
- optionsType: z.enum(['static', 'db', 'section']).describe('Source of select options'),
464
- build: z.function().output(z.instanceof(SelectField)).describe('Build a SelectField instance from this config'),
465
- }));
466
- /**
467
- * Helper function to create a select field configuration
468
- * Returns a config object with a build() method that can be serialized and used anywhere.
469
- */
470
- export function selectField(field) {
471
- const result = selectFieldOptionsSchema.safeParse(field);
472
- if (!result.success) {
473
- throw new Error(`[Field: ${field.name}]: ${z.prettifyError(result.error)}`);
474
- }
475
- const parsedField = result.data;
476
- let optionsType;
477
- if (parsedField.options && parsedField.options.length > 0) {
478
- optionsType = 'static';
479
- }
480
- else if (parsedField.db) {
481
- optionsType = 'db';
482
- }
483
- else if (parsedField.section) {
484
- optionsType = 'section';
485
- }
486
- else {
487
- throw new Error(`[Field: ${parsedField.name}]: You have to assign options for the select field, you can assign one of the following sources: [db, section, options]`);
488
- }
489
- const config = {
490
- ...parsedField,
491
- type: 'select',
492
- optionsType,
493
- build() {
494
- // Use the original field config directly (it doesn't have build() method)
495
- return new SelectField(parsedField);
496
- },
497
- };
498
- return config;
499
- }
1
+ import { Field, baseFieldConfigSchema } from './field.js';
2
+ import { entityKind } from '../helpers/index.js';
3
+ import { MysqlTableChecker } from '../db/index.js';
4
+ import { db } from '../../db/client.js';
5
+ import { sql } from 'drizzle-orm';
6
+ import chalk from 'chalk';
7
+ import * as z from 'zod';
8
+ export const selectOptionSchema = z.strictObject({
9
+ value: z.union([z.string(), z.number()]).describe('Stored value for this option'),
10
+ label: z.string().describe('Human-readable label for the option'),
11
+ });
12
+ export const selectFieldDestinationDbSchema = z.strictObject({
13
+ table: z.string().min(1).describe('Destination table where select values will be stored'),
14
+ /**
15
+ * The identifier of the item that has this select field as a field
16
+ */
17
+ itemIdentifier: z.string().min(1).describe('Destination item identifier column'),
18
+ /**
19
+ * The identifier of the value of the select option that is coming from the select options
20
+ */
21
+ selectIdentifier: z.string().min(1).describe('Destination select identifier column'),
22
+ });
23
+ const selectFieldSharedExtrasSchema = z.strictObject({
24
+ /**
25
+ * A destination table where the select values will be saved
26
+ * When this is set, the value of the field will only be saved in the destination table as one row per each value
27
+ */
28
+ destinationDb: selectFieldDestinationDbSchema.optional(),
29
+ checkValueExists: z.boolean().optional(),
30
+ });
31
+ const selectFieldDbConfigSchema = selectFieldSharedExtrasSchema.extend({
32
+ options: z.never().optional(),
33
+ db: z
34
+ .strictObject({
35
+ table: z.string().min(1).describe('Database table to read select options from'),
36
+ identifier: z.string().min(1).describe('Identifier column for the option'),
37
+ label: z.string().min(1).describe('Label column for the option'),
38
+ orderBy: z.string().optional().describe('Optional order by column'),
39
+ })
40
+ .describe('Database configuration for select options'),
41
+ section: z.never().optional(),
42
+ });
43
+ const selectFieldSectionConfigSchema = selectFieldSharedExtrasSchema.extend({
44
+ options: z.array(selectOptionSchema).optional(),
45
+ db: z.never().optional(),
46
+ section: z.custom().describe('Section to derive select options from'),
47
+ });
48
+ const selectFieldStaticConfigSchema = selectFieldSharedExtrasSchema.extend({
49
+ options: z.array(selectOptionSchema).min(1).describe('List of static select options'),
50
+ db: z.never().optional(),
51
+ section: z.never().optional(),
52
+ });
53
+ export const selectFieldSpecificConfigSchema = z.union([
54
+ selectFieldDbConfigSchema,
55
+ selectFieldSectionConfigSchema,
56
+ selectFieldStaticConfigSchema,
57
+ ]);
58
+ export class SelectField extends Field {
59
+ static [entityKind] = 'SelectField';
60
+ checkValueExists;
61
+ optionsType;
62
+ db;
63
+ destinationDb = undefined;
64
+ value = undefined;
65
+ // private _values: string[] | number[] | undefined
66
+ section;
67
+ _itemIdentifier;
68
+ options;
69
+ _allOptions;
70
+ constructor(config) {
71
+ super(config, 'select');
72
+ this.checkValueExists = config.checkValueExists ?? false;
73
+ if (config.section) {
74
+ this.destinationDb = config.destinationDb;
75
+ this.section = config.section;
76
+ }
77
+ /**
78
+ * If options are static, set them
79
+ */
80
+ if (config.options && config.options?.length > 0) {
81
+ this.options = config.options;
82
+ this.optionsType = 'static';
83
+ this.db = null;
84
+ }
85
+ else if (config.db) {
86
+ /**
87
+ * Else, check if the db config is set
88
+ */
89
+ this.db = config.db;
90
+ this.optionsType = 'db';
91
+ }
92
+ else if (config.section) {
93
+ /**
94
+ * Else, check if the section config is set
95
+ */
96
+ /**
97
+ * Runtime validation: Ensure the section is a HasItemsSection or CategorySection
98
+ * TypeScript ensures type safety at compile time, but runtime data (from JSON/APIs)
99
+ * can violate types, so we validate here for safety.
100
+ */
101
+ const sectionType = config.section.type;
102
+ if (sectionType !== 'has_items' && sectionType !== 'category') {
103
+ const message = `[SelectMultipleField: ${this.label}]: Section must be a HasItemsSection or CategorySection, but got type: ${sectionType ?? 'undefined'}`;
104
+ console.error(chalk.red.bold(message));
105
+ console.error(chalk.yellow('Please make sure the section is a HasItemsSection or CategorySection'));
106
+ throw new Error(message);
107
+ }
108
+ /**
109
+ * Build the section and set the options type to section
110
+ */
111
+ const section = config.section.build();
112
+ this.optionsType = 'section';
113
+ this.db = {
114
+ table: section.db.table,
115
+ identifier: section.db.identifier.name,
116
+ label: section.headingField.name,
117
+ orderBy: section.db.orderByField ? section.db.orderByField.name : section.db.identifier.name,
118
+ };
119
+ }
120
+ else {
121
+ throw new Error('Select field requires either db, section or an options array.');
122
+ }
123
+ }
124
+ /**
125
+ * @param value The value of a single select field,
126
+ * or values array of multiple select fields if the select is selecting from
127
+ * a category section with depth higher than 1.
128
+ */
129
+ setValue(value) {
130
+ /**
131
+ * Set the value
132
+ */
133
+ if (value) {
134
+ /**
135
+ * Value can be a string of comma separated values if destinationDb is not set
136
+ */
137
+ if (typeof value === 'string') {
138
+ /**
139
+ * Split the values by comma and set the value as an array of objects
140
+ */
141
+ const values = value.toString().split(',');
142
+ values.map((value) => {
143
+ if (this.checkValueExists) {
144
+ /**
145
+ * Check if the value exists in the options and return the value and text
146
+ */
147
+ const option = this.options?.find((option) => option.value.toString() === value.toString());
148
+ if (option) {
149
+ if (!this.value) {
150
+ this.value = [];
151
+ }
152
+ this.value.push(option);
153
+ }
154
+ }
155
+ else {
156
+ if (!this.value) {
157
+ this.value = [];
158
+ }
159
+ this.value.push({
160
+ value: value,
161
+ label: '', // TODO: Fix this
162
+ });
163
+ }
164
+ });
165
+ }
166
+ else {
167
+ /**
168
+ * Value is an array of objects
169
+ */
170
+ this.value = value;
171
+ }
172
+ }
173
+ else {
174
+ this.value = undefined;
175
+ }
176
+ }
177
+ hasSqlNameAndValue() {
178
+ return this.destinationDb === undefined;
179
+ }
180
+ hasDepth() {
181
+ return this.section !== undefined && this.section.type === 'category' && (this.section.depth ?? 1) > 1;
182
+ }
183
+ /**
184
+ * Get the value of the field
185
+ */
186
+ getValue() {
187
+ /**
188
+ * Value is always a string | number
189
+ */
190
+ return this.value;
191
+ }
192
+ /**
193
+ * Get the value of the field when submitting to the database
194
+ */
195
+ getSubmitValue() {
196
+ if (!this.hasSqlNameAndValue())
197
+ return;
198
+ /**
199
+ * Set the value as a comma separated string of the values
200
+ */
201
+ return this.value?.map((item) => item.value).join(',');
202
+ }
203
+ exportForClient() {
204
+ return {
205
+ ...super.exportForClient(),
206
+ options: this.options,
207
+ section: this.section
208
+ ? {
209
+ name: this.section.name,
210
+ depth: this.section.type === 'category' ? this.section.depth : 1,
211
+ }
212
+ : undefined,
213
+ /**
214
+ * Add the values with the level if present
215
+ */
216
+ value: this.value?.map((value, index) => ({ ...value, level: index + 1 })),
217
+ };
218
+ }
219
+ async postSubmit(itemId) {
220
+ if (!this.destinationDb)
221
+ return;
222
+ /**
223
+ * First, check if the select is selecting from a category section with depth higher than 1
224
+ */
225
+ // if (!this.hasDepth()) return
226
+ this._itemIdentifier = itemId;
227
+ try {
228
+ /**
229
+ * Delete the existing values
230
+ */
231
+ await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
232
+ /**
233
+ * Prepare the sql statement
234
+ */
235
+ const sqlChunks = [];
236
+ sqlChunks.push(sql `INSERT INTO \`${sql.raw(this.destinationDb.table)}\` (\`${sql.raw(this.destinationDb.selectIdentifier)}\`, \`${sql.raw(this.destinationDb.itemIdentifier)}\`) VALUES `);
237
+ /**
238
+ * Loop through the values and add them to the sql statement values
239
+ */
240
+ if (this.value && this.value.length > 0) {
241
+ const count = this.value.length;
242
+ this.value.forEach((value, index) => {
243
+ sqlChunks.push(sql `(${value.value}, ${this._itemIdentifier})`);
244
+ if (index < count - 1) {
245
+ /**
246
+ * Add a comma to separate the values in the sql statement
247
+ */
248
+ sqlChunks.push(sql `, `);
249
+ }
250
+ });
251
+ }
252
+ /**
253
+ * Join the sql chunks
254
+ */
255
+ const finalSql = sql.join(sqlChunks);
256
+ /**
257
+ * Execute the sql statement
258
+ */
259
+ await db.execute(finalSql);
260
+ }
261
+ catch (error) {
262
+ console.error(error);
263
+ throw new Error(`${this.label}: Error saving values to the destination table`);
264
+ }
265
+ }
266
+ async postSubmitRollback() {
267
+ if (!this.destinationDb)
268
+ return;
269
+ /**
270
+ * First, check if the select is selecting from a category section with depth higher than 1
271
+ */
272
+ // if (!this.hasDepth()) return
273
+ if (!this._itemIdentifier) {
274
+ throw new Error(`${this.label}: Item identifier is not set. Make sure to set the item identifier by calling postSubmit() before calling postSubmitRollback()`);
275
+ }
276
+ try {
277
+ await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
278
+ }
279
+ catch (error) {
280
+ throw new Error(`${this.label}: Error deleting values from the destination table`);
281
+ }
282
+ }
283
+ /**
284
+ * Check the destination db config is set
285
+ * @private
286
+ */
287
+ checkDestinationDBConfig() {
288
+ if (this.destinationDb?.table.trim().length === 0 ||
289
+ this.destinationDb?.selectIdentifier.trim().length === 0 ||
290
+ this.destinationDb?.itemIdentifier.trim().length === 0) {
291
+ throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
292
+ }
293
+ }
294
+ /**
295
+ * Check if destination table and columns exist
296
+ * @private
297
+ */
298
+ async checkDestinationDBTableAndColumns() {
299
+ if (!this.destinationDb) {
300
+ throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
301
+ }
302
+ const columns = await MysqlTableChecker.getColumns(this.destinationDb.table);
303
+ if (!columns.includes(this.destinationDb.selectIdentifier) ||
304
+ !columns.includes(this.destinationDb.itemIdentifier)) {
305
+ throw new Error(`Field ${this.label}: Table ${this.destinationDb.table} and/or columns: ${this.destinationDb.selectIdentifier}, ${this.destinationDb.itemIdentifier} do not exist in the database!`);
306
+ }
307
+ }
308
+ /**
309
+ * Build the field to be used in a form
310
+ */
311
+ async build() {
312
+ if (this.destinationDb) {
313
+ this.checkDestinationDBConfig();
314
+ await this.checkDestinationDBTableAndColumns();
315
+ }
316
+ /**
317
+ * If options are already set, return
318
+ */
319
+ if (this.options && this.optionsType === 'static')
320
+ return;
321
+ /**
322
+ * Initialize the select db and options
323
+ */
324
+ this.checkDBConfig();
325
+ await this.checkDBTableAndColumns();
326
+ /**
327
+ * Fetch the options
328
+ */
329
+ await this.fetchOptions();
330
+ }
331
+ async fetchOptions() {
332
+ /**
333
+ * Let's get the options for the select input
334
+ */
335
+ const sqlChunks = [];
336
+ // Initialize the select statement
337
+ sqlChunks.push(sql `select * from \`${sql.raw(this.db.table)}\``);
338
+ /**
339
+ * Check if the select is from a category section with depth higher than 1
340
+ */
341
+ if (this.hasDepth()) {
342
+ /**
343
+ * Add the where clause to the select statement
344
+ */
345
+ sqlChunks.push(sql ` WHERE parent_id IS NULL AND level = 1`);
346
+ }
347
+ /**
348
+ * Add order by clause
349
+ */
350
+ sqlChunks.push(sql ` ORDER BY \`${sql.raw(this.db.orderBy ?? this.db.identifier)}\` DESC`);
351
+ /**
352
+ * Get the options from the table
353
+ */
354
+ const selectOptionsRows = await db.execute(sql.join(sqlChunks));
355
+ const rows = selectOptionsRows[0];
356
+ const inputSelectTableIdentifierField = this.db.identifier;
357
+ const inputSelectTableHeadingField = this.db.label;
358
+ /**
359
+ * Set the options
360
+ */
361
+ this.options = rows.map((row) => ({
362
+ value: row[inputSelectTableIdentifierField],
363
+ label: row[inputSelectTableHeadingField],
364
+ }));
365
+ this._allOptions = this.options;
366
+ if (this.hasDepth()) {
367
+ /**
368
+ * Get all the options
369
+ */
370
+ const selectOptionsRows1 = await db.execute(sql `select * from \`${sql.raw(this.db.table)}\` WHERE parent_id IS NOT NULL AND level > 1`);
371
+ const rows1 = selectOptionsRows1[0];
372
+ const tempRows = rows1.map((row) => ({
373
+ value: row[inputSelectTableIdentifierField],
374
+ label: row[inputSelectTableHeadingField],
375
+ }));
376
+ this._allOptions = this._allOptions.concat(tempRows);
377
+ }
378
+ }
379
+ /**
380
+ * Check if table and columns exist
381
+ * @private
382
+ */
383
+ async checkDBTableAndColumns() {
384
+ const columns = await MysqlTableChecker.getColumns(this.db.table);
385
+ if (!columns.includes(this.db.identifier) || !columns.includes(this.db.label)) {
386
+ throw new Error(`Field ${this.label}: Table ${this.db.table} and/or columns: ${this.db.identifier}, ${this.db.label} do not exist in the database!`);
387
+ }
388
+ }
389
+ /**
390
+ * Check the db config is set
391
+ * @private
392
+ */
393
+ checkDBConfig() {
394
+ if (this.db.table.trim().length === 0 ||
395
+ this.db.identifier.trim().length === 0 ||
396
+ this.db.label.trim().length === 0) {
397
+ throw new Error('Table, identifier and label are required');
398
+ }
399
+ }
400
+ checkRequired() {
401
+ /**
402
+ * Check if the field is required
403
+ */
404
+ if (this.required) {
405
+ if (!this.value || this.value.toString().trim().length === 0) {
406
+ throw new Error(`Field ${this.label} is required`);
407
+ }
408
+ }
409
+ }
410
+ /**
411
+ * Prepare the field for submission
412
+ */
413
+ async prepareForSubmission() {
414
+ if (this.optionsType !== 'static') {
415
+ this.checkDBConfig();
416
+ await this.checkDBTableAndColumns();
417
+ /**
418
+ * Fetch the options
419
+ */
420
+ await this.fetchOptions();
421
+ }
422
+ if (this.destinationDb) {
423
+ this.checkDestinationDBConfig();
424
+ await this.checkDestinationDBTableAndColumns();
425
+ }
426
+ /**
427
+ * If checkValueExists is set to true, check if the value exists in the options.
428
+ * This is important to prevent submitting a value that does not exist in the table. (e.g. tampering with the form)
429
+ * Default is false.
430
+ */
431
+ if (this.checkValueExists && this.value && this.value.length > 0) {
432
+ /**
433
+ * Check if the values exist in the table
434
+ * This is now safe because we have verified the table and columns exist in the database
435
+ */
436
+ this.value.forEach((value) => {
437
+ if (!this._allOptions?.find((option) => option.value.toString() === value.value.toString())) {
438
+ throw new Error(`Field ${this.label}: Option ${value.label} does not exist in the options`);
439
+ }
440
+ });
441
+ }
442
+ }
443
+ }
444
+ const selectFieldDbOptionsSchema = z.strictObject({
445
+ ...baseFieldConfigSchema.shape,
446
+ ...selectFieldDbConfigSchema.shape,
447
+ });
448
+ const selectFieldSectionOptionsSchema = z.strictObject({
449
+ ...baseFieldConfigSchema.shape,
450
+ ...selectFieldSectionConfigSchema.shape,
451
+ });
452
+ const selectFieldStaticOptionsSchema = z.strictObject({
453
+ ...baseFieldConfigSchema.shape,
454
+ ...selectFieldStaticConfigSchema.shape,
455
+ });
456
+ export const selectFieldOptionsSchema = z.union([
457
+ selectFieldDbOptionsSchema,
458
+ selectFieldSectionOptionsSchema,
459
+ selectFieldStaticOptionsSchema,
460
+ ]);
461
+ const selectFieldConfigSchema = z.intersection(selectFieldOptionsSchema, z.strictObject({
462
+ type: z.literal('select').describe('The type of the field'),
463
+ optionsType: z.enum(['static', 'db', 'section']).describe('Source of select options'),
464
+ build: z.function().output(z.instanceof(SelectField)).describe('Build a SelectField instance from this config'),
465
+ }));
466
+ /**
467
+ * Helper function to create a select field configuration
468
+ * Returns a config object with a build() method that can be serialized and used anywhere.
469
+ */
470
+ export function selectField(field) {
471
+ const result = selectFieldOptionsSchema.safeParse(field);
472
+ if (!result.success) {
473
+ throw new Error(`[Field: ${field.name}]: ${z.prettifyError(result.error)}`);
474
+ }
475
+ const parsedField = result.data;
476
+ let optionsType;
477
+ if (parsedField.options && parsedField.options.length > 0) {
478
+ optionsType = 'static';
479
+ }
480
+ else if (parsedField.db) {
481
+ optionsType = 'db';
482
+ }
483
+ else if (parsedField.section) {
484
+ optionsType = 'section';
485
+ }
486
+ else {
487
+ throw new Error(`[Field: ${parsedField.name}]: You have to assign options for the select field, you can assign one of the following sources: [db, section, options]`);
488
+ }
489
+ const config = {
490
+ ...parsedField,
491
+ type: 'select',
492
+ optionsType,
493
+ build() {
494
+ // Use the original field config directly (it doesn't have build() method)
495
+ return new SelectField(parsedField);
496
+ },
497
+ };
498
+ return config;
499
+ }