@azurioh/discord-kernel 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/README.md +318 -1
  2. package/dist/authz/require-level-guard.d.ts +1 -1
  3. package/dist/authz/require-level-guard.d.ts.map +1 -1
  4. package/dist/color.d.ts +42 -0
  5. package/dist/color.d.ts.map +1 -0
  6. package/dist/color.js +130 -0
  7. package/dist/color.js.map +1 -0
  8. package/dist/discord/command/all-required.d.ts +8 -0
  9. package/dist/discord/command/all-required.d.ts.map +1 -0
  10. package/dist/discord/command/all-required.js +12 -0
  11. package/dist/discord/command/all-required.js.map +1 -0
  12. package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -1
  13. package/dist/discord/command/autocomplete-dispatcher.js +2 -1
  14. package/dist/discord/command/autocomplete-dispatcher.js.map +1 -1
  15. package/dist/discord/command/autocomplete-limits.d.ts +3 -0
  16. package/dist/discord/command/autocomplete-limits.d.ts.map +1 -0
  17. package/dist/discord/command/autocomplete-limits.js +6 -0
  18. package/dist/discord/command/autocomplete-limits.js.map +1 -0
  19. package/dist/discord/command/autocomplete.d.ts +6 -1
  20. package/dist/discord/command/autocomplete.d.ts.map +1 -1
  21. package/dist/discord/command/autocomplete.js +6 -6
  22. package/dist/discord/command/autocomplete.js.map +1 -1
  23. package/dist/discord/command/command-responders.d.ts +26 -0
  24. package/dist/discord/command/command-responders.d.ts.map +1 -0
  25. package/dist/discord/command/command-responders.js +16 -0
  26. package/dist/discord/command/command-responders.js.map +1 -0
  27. package/dist/discord/command/context.d.ts +8 -17
  28. package/dist/discord/command/context.d.ts.map +1 -1
  29. package/dist/discord/command/context.js +8 -37
  30. package/dist/discord/command/context.js.map +1 -1
  31. package/dist/discord/command/cooldown-guard.d.ts +1 -1
  32. package/dist/discord/command/cooldown-guard.d.ts.map +1 -1
  33. package/dist/discord/command/create-command.d.ts +1 -8
  34. package/dist/discord/command/create-command.d.ts.map +1 -1
  35. package/dist/discord/command/create-command.js +17 -65
  36. package/dist/discord/command/create-command.js.map +1 -1
  37. package/dist/discord/command/create-context-menu-command.d.ts +3 -9
  38. package/dist/discord/command/create-context-menu-command.d.ts.map +1 -1
  39. package/dist/discord/command/create-context-menu-command.js +14 -65
  40. package/dist/discord/command/create-context-menu-command.js.map +1 -1
  41. package/dist/discord/command/errors.d.ts +1 -1
  42. package/dist/discord/command/errors.d.ts.map +1 -1
  43. package/dist/discord/command/errors.js +2 -2
  44. package/dist/discord/command/errors.js.map +1 -1
  45. package/dist/discord/command/guard.d.ts +15 -8
  46. package/dist/discord/command/guard.d.ts.map +1 -1
  47. package/dist/discord/command/guard.js +4 -4
  48. package/dist/discord/command/guard.js.map +1 -1
  49. package/dist/discord/command/options.d.ts +1 -10
  50. package/dist/discord/command/options.d.ts.map +1 -1
  51. package/dist/discord/command/options.js +127 -320
  52. package/dist/discord/command/options.js.map +1 -1
  53. package/dist/discord/command/passes-guard.d.ts +14 -0
  54. package/dist/discord/command/passes-guard.d.ts.map +1 -0
  55. package/dist/discord/command/passes-guard.js +27 -0
  56. package/dist/discord/command/passes-guard.js.map +1 -0
  57. package/dist/discord/command/permission-guard.d.ts +1 -1
  58. package/dist/discord/command/permission-guard.d.ts.map +1 -1
  59. package/dist/discord/command/render-command-failure.d.ts +23 -0
  60. package/dist/discord/command/render-command-failure.d.ts.map +1 -0
  61. package/dist/discord/command/render-command-failure.js +40 -0
  62. package/dist/discord/command/render-command-failure.js.map +1 -0
  63. package/dist/discord/command/route-key.d.ts +8 -0
  64. package/dist/discord/command/route-key.d.ts.map +1 -0
  65. package/dist/discord/command/route-key.js +13 -0
  66. package/dist/discord/command/route-key.js.map +1 -0
  67. package/dist/discord/command/router.d.ts +32 -7
  68. package/dist/discord/command/router.d.ts.map +1 -1
  69. package/dist/discord/command/router.js +48 -10
  70. package/dist/discord/command/router.js.map +1 -1
  71. package/dist/discord/command/send-embed.d.ts +14 -0
  72. package/dist/discord/command/send-embed.d.ts.map +1 -0
  73. package/dist/discord/command/send-embed.js +37 -0
  74. package/dist/discord/command/send-embed.js.map +1 -0
  75. package/dist/discord/command/types.d.ts +7 -1
  76. package/dist/discord/command/types.d.ts.map +1 -1
  77. package/dist/discord/components/component-access.d.ts +12 -0
  78. package/dist/discord/components/component-access.d.ts.map +1 -0
  79. package/dist/discord/components/component-access.js +21 -0
  80. package/dist/discord/components/component-access.js.map +1 -0
  81. package/dist/discord/components/component-router.d.ts +37 -15
  82. package/dist/discord/components/component-router.d.ts.map +1 -1
  83. package/dist/discord/components/component-router.js +44 -32
  84. package/dist/discord/components/component-router.js.map +1 -1
  85. package/dist/discord/components/interactive-message/index.d.ts +3 -1
  86. package/dist/discord/components/interactive-message/index.d.ts.map +1 -1
  87. package/dist/discord/components/interactive-message/index.js +5 -3
  88. package/dist/discord/components/interactive-message/index.js.map +1 -1
  89. package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +3 -28
  90. package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -1
  91. package/dist/discord/components/interactive-message/interactive-message-collector.js +6 -53
  92. package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -1
  93. package/dist/discord/components/interactive-message/message-edit-options.d.ts +18 -0
  94. package/dist/discord/components/interactive-message/message-edit-options.d.ts.map +1 -0
  95. package/dist/discord/components/interactive-message/message-edit-options.js +40 -0
  96. package/dist/discord/components/interactive-message/message-edit-options.js.map +1 -0
  97. package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +2 -1
  98. package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -1
  99. package/dist/discord/components/interactive-message/mount-interactive-message.js +4 -2
  100. package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -1
  101. package/dist/discord/components/interactive-message/state-store.d.ts +12 -0
  102. package/dist/discord/components/interactive-message/state-store.d.ts.map +1 -0
  103. package/dist/discord/components/interactive-message/state-store.js +14 -0
  104. package/dist/discord/components/interactive-message/state-store.js.map +1 -0
  105. package/dist/discord/components/paginator/mount-paginator.js +7 -5
  106. package/dist/discord/components/paginator/mount-paginator.js.map +1 -1
  107. package/dist/discord/components/paginator/paginator-view.d.ts +1 -1
  108. package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -1
  109. package/dist/discord/components/persistent-paginator.d.ts +1 -1
  110. package/dist/discord/components/persistent-paginator.d.ts.map +1 -1
  111. package/dist/discord/components/persistent-paginator.js +6 -6
  112. package/dist/discord/components/persistent-paginator.js.map +1 -1
  113. package/dist/discord/components/settings-editor/from-declaration-controls.d.ts +67 -0
  114. package/dist/discord/components/settings-editor/from-declaration-controls.d.ts.map +1 -0
  115. package/dist/discord/components/settings-editor/from-declaration-controls.js +245 -0
  116. package/dist/discord/components/settings-editor/from-declaration-controls.js.map +1 -0
  117. package/dist/discord/components/settings-editor/from-declaration-display.d.ts +23 -0
  118. package/dist/discord/components/settings-editor/from-declaration-display.d.ts.map +1 -0
  119. package/dist/discord/components/settings-editor/from-declaration-display.js +114 -0
  120. package/dist/discord/components/settings-editor/from-declaration-display.js.map +1 -0
  121. package/dist/discord/components/settings-editor/from-declaration-layout.d.ts +33 -0
  122. package/dist/discord/components/settings-editor/from-declaration-layout.d.ts.map +1 -0
  123. package/dist/discord/components/settings-editor/from-declaration-layout.js +178 -0
  124. package/dist/discord/components/settings-editor/from-declaration-layout.js.map +1 -0
  125. package/dist/discord/components/settings-editor/from-declaration-search.d.ts +27 -0
  126. package/dist/discord/components/settings-editor/from-declaration-search.d.ts.map +1 -0
  127. package/dist/discord/components/settings-editor/from-declaration-search.js +58 -0
  128. package/dist/discord/components/settings-editor/from-declaration-search.js.map +1 -0
  129. package/dist/discord/components/settings-editor/from-declaration-stored.d.ts +21 -0
  130. package/dist/discord/components/settings-editor/from-declaration-stored.d.ts.map +1 -0
  131. package/dist/discord/components/settings-editor/from-declaration-stored.js +37 -0
  132. package/dist/discord/components/settings-editor/from-declaration-stored.js.map +1 -0
  133. package/dist/discord/components/settings-editor/from-declaration-subject.d.ts +32 -0
  134. package/dist/discord/components/settings-editor/from-declaration-subject.d.ts.map +1 -0
  135. package/dist/discord/components/settings-editor/from-declaration-subject.js +65 -0
  136. package/dist/discord/components/settings-editor/from-declaration-subject.js.map +1 -0
  137. package/dist/discord/components/settings-editor/from-declaration-values.d.ts +57 -0
  138. package/dist/discord/components/settings-editor/from-declaration-values.d.ts.map +1 -0
  139. package/dist/discord/components/settings-editor/from-declaration-values.js +187 -0
  140. package/dist/discord/components/settings-editor/from-declaration-values.js.map +1 -0
  141. package/dist/discord/components/settings-editor/from-declaration.d.ts +39 -0
  142. package/dist/discord/components/settings-editor/from-declaration.d.ts.map +1 -0
  143. package/dist/discord/components/settings-editor/from-declaration.js +132 -0
  144. package/dist/discord/components/settings-editor/from-declaration.js.map +1 -0
  145. package/dist/discord/components/settings-editor/index.d.ts +3 -1
  146. package/dist/discord/components/settings-editor/index.d.ts.map +1 -1
  147. package/dist/discord/components/settings-editor/index.js +6 -3
  148. package/dist/discord/components/settings-editor/index.js.map +1 -1
  149. package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +16 -2
  150. package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -1
  151. package/dist/discord/components/settings-editor/mount-settings-editor.js +14 -10
  152. package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -1
  153. package/dist/discord/components/settings-editor/navigation.d.ts +20 -0
  154. package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -1
  155. package/dist/discord/components/settings-editor/navigation.js +22 -0
  156. package/dist/discord/components/settings-editor/navigation.js.map +1 -1
  157. package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +4 -3
  158. package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -1
  159. package/dist/discord/components/settings-editor/settings-editor-card.view.js +60 -34
  160. package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -1
  161. package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +2 -1
  162. package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -1
  163. package/dist/discord/components/settings-editor/settings-editor-field-modal.js +48 -72
  164. package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -1
  165. package/dist/discord/components/settings-editor/settings-editor-field-values.d.ts +6 -0
  166. package/dist/discord/components/settings-editor/settings-editor-field-values.d.ts.map +1 -0
  167. package/dist/discord/components/settings-editor/settings-editor-field-values.js +13 -0
  168. package/dist/discord/components/settings-editor/settings-editor-field-values.js.map +1 -0
  169. package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +6 -5
  170. package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -1
  171. package/dist/discord/components/settings-editor/settings-editor-fields.js +0 -10
  172. package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -1
  173. package/dist/discord/components/settings-editor/settings-editor.view.d.ts +3 -2
  174. package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -1
  175. package/dist/discord/components/settings-editor/settings-editor.view.js +4 -21
  176. package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -1
  177. package/dist/discord/events/event-guild-id.d.ts +10 -0
  178. package/dist/discord/events/event-guild-id.d.ts.map +1 -0
  179. package/dist/discord/events/event-guild-id.js +37 -0
  180. package/dist/discord/events/event-guild-id.js.map +1 -0
  181. package/dist/discord/events/event-router.d.ts +17 -3
  182. package/dist/discord/events/event-router.d.ts.map +1 -1
  183. package/dist/discord/events/event-router.js +33 -9
  184. package/dist/discord/events/event-router.js.map +1 -1
  185. package/dist/discord/i18n.d.ts +17 -2
  186. package/dist/discord/i18n.d.ts.map +1 -1
  187. package/dist/discord/i18n.js +43 -6
  188. package/dist/discord/i18n.js.map +1 -1
  189. package/dist/discord/interaction/interaction-locale.d.ts +14 -0
  190. package/dist/discord/interaction/interaction-locale.d.ts.map +1 -0
  191. package/dist/discord/interaction/interaction-locale.js +12 -0
  192. package/dist/discord/interaction/interaction-locale.js.map +1 -0
  193. package/dist/discord/interaction/locale-resolver.d.ts +27 -0
  194. package/dist/discord/interaction/locale-resolver.d.ts.map +1 -0
  195. package/dist/discord/interaction/locale-resolver.js +3 -0
  196. package/dist/discord/interaction/locale-resolver.js.map +1 -0
  197. package/dist/discord/interaction/modal.d.ts.map +1 -1
  198. package/dist/discord/interaction/modal.js +5 -40
  199. package/dist/discord/interaction/modal.js.map +1 -1
  200. package/dist/discord/interaction/reply-locale.d.ts +18 -0
  201. package/dist/discord/interaction/reply-locale.d.ts.map +1 -0
  202. package/dist/discord/interaction/reply-locale.js +25 -0
  203. package/dist/discord/interaction/reply-locale.js.map +1 -0
  204. package/dist/discord/interaction/select-bounds.d.ts +19 -0
  205. package/dist/discord/interaction/select-bounds.d.ts.map +1 -0
  206. package/dist/discord/interaction/select-bounds.js +17 -0
  207. package/dist/discord/interaction/select-bounds.js.map +1 -0
  208. package/dist/discord/interaction/select-menu.d.ts.map +1 -1
  209. package/dist/discord/interaction/select-menu.js +2 -9
  210. package/dist/discord/interaction/select-menu.js.map +1 -1
  211. package/dist/discord/placeholder-registry.d.ts +1 -1
  212. package/dist/discord/placeholder-registry.d.ts.map +1 -1
  213. package/dist/discord/presenter.d.ts +1 -1
  214. package/dist/discord/presenter.d.ts.map +1 -1
  215. package/dist/discord/settings/discord-guild-directory.d.ts +28 -0
  216. package/dist/discord/settings/discord-guild-directory.d.ts.map +1 -0
  217. package/dist/discord/settings/discord-guild-directory.js +171 -0
  218. package/dist/discord/settings/discord-guild-directory.js.map +1 -0
  219. package/dist/discord/settings/guild-locale-resolver.d.ts +25 -0
  220. package/dist/discord/settings/guild-locale-resolver.d.ts.map +1 -0
  221. package/dist/discord/settings/guild-locale-resolver.js +33 -0
  222. package/dist/discord/settings/guild-locale-resolver.js.map +1 -0
  223. package/dist/discord/settings/is-module-disabled.d.ts +16 -0
  224. package/dist/discord/settings/is-module-disabled.d.ts.map +1 -0
  225. package/dist/discord/settings/is-module-disabled.js +20 -0
  226. package/dist/discord/settings/is-module-disabled.js.map +1 -0
  227. package/dist/discord/settings/module-disabled-embed.d.ts +13 -0
  228. package/dist/discord/settings/module-disabled-embed.d.ts.map +1 -0
  229. package/dist/discord/settings/module-disabled-embed.js +16 -0
  230. package/dist/discord/settings/module-disabled-embed.js.map +1 -0
  231. package/dist/discord/settings/require-configured.d.ts +26 -0
  232. package/dist/discord/settings/require-configured.d.ts.map +1 -0
  233. package/dist/discord/settings/require-configured.js +62 -0
  234. package/dist/discord/settings/require-configured.js.map +1 -0
  235. package/dist/discord/ui/color-aliases.d.ts +31 -0
  236. package/dist/discord/ui/color-aliases.d.ts.map +1 -0
  237. package/dist/discord/ui/color-aliases.js +66 -0
  238. package/dist/discord/ui/color-aliases.js.map +1 -0
  239. package/dist/discord/ui/color-input.d.ts +5 -32
  240. package/dist/discord/ui/color-input.d.ts.map +1 -1
  241. package/dist/discord/ui/color-input.js +8 -157
  242. package/dist/discord/ui/color-input.js.map +1 -1
  243. package/dist/discord/ui/colors.d.ts +0 -2
  244. package/dist/discord/ui/colors.d.ts.map +1 -1
  245. package/dist/discord/ui/colors.js +4 -5
  246. package/dist/discord/ui/colors.js.map +1 -1
  247. package/dist/discord/ui/embed.d.ts.map +1 -1
  248. package/dist/discord/ui/embed.js +3 -8
  249. package/dist/discord/ui/embed.js.map +1 -1
  250. package/dist/discord/ui/truncate-text.d.ts +11 -0
  251. package/dist/discord/ui/truncate-text.d.ts.map +1 -0
  252. package/dist/discord/ui/truncate-text.js +19 -0
  253. package/dist/discord/ui/truncate-text.js.map +1 -0
  254. package/dist/errors/error-message.d.ts +9 -0
  255. package/dist/errors/error-message.d.ts.map +1 -0
  256. package/dist/errors/error-message.js +14 -0
  257. package/dist/errors/error-message.js.map +1 -0
  258. package/dist/i18n/business-message.d.ts +1 -1
  259. package/dist/i18n/business-message.d.ts.map +1 -1
  260. package/dist/i18n/catalog.d.ts +9 -0
  261. package/dist/i18n/catalog.d.ts.map +1 -1
  262. package/dist/i18n/catalog.js +4 -0
  263. package/dist/i18n/catalog.js.map +1 -1
  264. package/dist/i18n/locale.d.ts +2 -0
  265. package/dist/i18n/locale.d.ts.map +1 -1
  266. package/dist/i18n/locale.js +3 -1
  267. package/dist/i18n/locale.js.map +1 -1
  268. package/dist/i18n/translator.d.ts +2 -0
  269. package/dist/i18n/translator.d.ts.map +1 -1
  270. package/dist/i18n/translator.js.map +1 -1
  271. package/dist/module/module.d.ts +15 -1
  272. package/dist/module/module.d.ts.map +1 -1
  273. package/dist/resilience/retry.js +2 -2
  274. package/dist/resilience/retry.js.map +1 -1
  275. package/dist/scheduler/scheduler.d.ts.map +1 -1
  276. package/dist/scheduler/scheduler.js +2 -4
  277. package/dist/scheduler/scheduler.js.map +1 -1
  278. package/dist/settings/cache.d.ts +29 -0
  279. package/dist/settings/cache.d.ts.map +1 -0
  280. package/dist/settings/cache.js +47 -0
  281. package/dist/settings/cache.js.map +1 -0
  282. package/dist/settings/choice.d.ts +10 -0
  283. package/dist/settings/choice.d.ts.map +1 -0
  284. package/dist/settings/choice.js +3 -0
  285. package/dist/settings/choice.js.map +1 -0
  286. package/dist/settings/decode-stored.d.ts +20 -0
  287. package/dist/settings/decode-stored.d.ts.map +1 -0
  288. package/dist/settings/decode-stored.js +35 -0
  289. package/dist/settings/decode-stored.js.map +1 -0
  290. package/dist/settings/define-settings.d.ts +52 -0
  291. package/dist/settings/define-settings.d.ts.map +1 -0
  292. package/dist/settings/define-settings.js +141 -0
  293. package/dist/settings/define-settings.js.map +1 -0
  294. package/dist/settings/describe-field.d.ts +19 -0
  295. package/dist/settings/describe-field.d.ts.map +1 -0
  296. package/dist/settings/describe-field.js +131 -0
  297. package/dist/settings/describe-field.js.map +1 -0
  298. package/dist/settings/describe.d.ts +27 -0
  299. package/dist/settings/describe.d.ts.map +1 -0
  300. package/dist/settings/describe.js +62 -0
  301. package/dist/settings/describe.js.map +1 -0
  302. package/dist/settings/duration.d.ts +16 -0
  303. package/dist/settings/duration.d.ts.map +1 -0
  304. package/dist/settings/duration.js +56 -0
  305. package/dist/settings/duration.js.map +1 -0
  306. package/dist/settings/field-order.d.ts +37 -0
  307. package/dist/settings/field-order.d.ts.map +1 -0
  308. package/dist/settings/field-order.js +100 -0
  309. package/dist/settings/field-order.js.map +1 -0
  310. package/dist/settings/field-search.d.ts +10 -0
  311. package/dist/settings/field-search.d.ts.map +1 -0
  312. package/dist/settings/field-search.js +18 -0
  313. package/dist/settings/field-search.js.map +1 -0
  314. package/dist/settings/fields/builders.d.ts +41 -0
  315. package/dist/settings/fields/builders.d.ts.map +1 -0
  316. package/dist/settings/fields/builders.js +83 -0
  317. package/dist/settings/fields/builders.js.map +1 -0
  318. package/dist/settings/fields/field.d.ts +199 -0
  319. package/dist/settings/fields/field.d.ts.map +1 -0
  320. package/dist/settings/fields/field.js +33 -0
  321. package/dist/settings/fields/field.js.map +1 -0
  322. package/dist/settings/fields/zod-schema.d.ts +67 -0
  323. package/dist/settings/fields/zod-schema.d.ts.map +1 -0
  324. package/dist/settings/fields/zod-schema.js +281 -0
  325. package/dist/settings/fields/zod-schema.js.map +1 -0
  326. package/dist/settings/in-memory/in-memory-guild-directory.d.ts +24 -0
  327. package/dist/settings/in-memory/in-memory-guild-directory.d.ts.map +1 -0
  328. package/dist/settings/in-memory/in-memory-guild-directory.js +42 -0
  329. package/dist/settings/in-memory/in-memory-guild-directory.js.map +1 -0
  330. package/dist/settings/in-memory/in-memory-settings-store.d.ts +8 -0
  331. package/dist/settings/in-memory/in-memory-settings-store.d.ts.map +1 -0
  332. package/dist/settings/in-memory/in-memory-settings-store.js +43 -0
  333. package/dist/settings/in-memory/in-memory-settings-store.js.map +1 -0
  334. package/dist/settings/in-memory/in-process-notifier.d.ts +9 -0
  335. package/dist/settings/in-memory/in-process-notifier.d.ts.map +1 -0
  336. package/dist/settings/in-memory/in-process-notifier.js +32 -0
  337. package/dist/settings/in-memory/in-process-notifier.js.map +1 -0
  338. package/dist/settings/index.d.ts +22 -0
  339. package/dist/settings/index.d.ts.map +1 -0
  340. package/dist/settings/index.js +31 -0
  341. package/dist/settings/index.js.map +1 -0
  342. package/dist/settings/is-plain-object.d.ts +6 -0
  343. package/dist/settings/is-plain-object.d.ts.map +1 -0
  344. package/dist/settings/is-plain-object.js +15 -0
  345. package/dist/settings/is-plain-object.js.map +1 -0
  346. package/dist/settings/messages.d.ts +61 -0
  347. package/dist/settings/messages.d.ts.map +1 -0
  348. package/dist/settings/messages.js +155 -0
  349. package/dist/settings/messages.js.map +1 -0
  350. package/dist/settings/migrate.d.ts +35 -0
  351. package/dist/settings/migrate.d.ts.map +1 -0
  352. package/dist/settings/migrate.js +42 -0
  353. package/dist/settings/migrate.js.map +1 -0
  354. package/dist/settings/ports/guild-directory.d.ts +61 -0
  355. package/dist/settings/ports/guild-directory.d.ts.map +1 -0
  356. package/dist/settings/ports/guild-directory.js +3 -0
  357. package/dist/settings/ports/guild-directory.js.map +1 -0
  358. package/dist/settings/ports/settings-changed-notifier.d.ts +20 -0
  359. package/dist/settings/ports/settings-changed-notifier.d.ts.map +1 -0
  360. package/dist/settings/ports/settings-changed-notifier.js +3 -0
  361. package/dist/settings/ports/settings-changed-notifier.js.map +1 -0
  362. package/dist/settings/ports/settings-store.d.ts +76 -0
  363. package/dist/settings/ports/settings-store.d.ts.map +1 -0
  364. package/dist/settings/ports/settings-store.js +3 -0
  365. package/dist/settings/ports/settings-store.js.map +1 -0
  366. package/dist/settings/registry.d.ts +31 -0
  367. package/dist/settings/registry.d.ts.map +1 -0
  368. package/dist/settings/registry.js +102 -0
  369. package/dist/settings/registry.js.map +1 -0
  370. package/dist/settings/settings-declaration-error.d.ts +13 -0
  371. package/dist/settings/settings-declaration-error.d.ts.map +1 -0
  372. package/dist/settings/settings-declaration-error.js +22 -0
  373. package/dist/settings/settings-declaration-error.js.map +1 -0
  374. package/dist/settings/settings-service.d.ts +148 -0
  375. package/dist/settings/settings-service.d.ts.map +1 -0
  376. package/dist/settings/settings-service.js +279 -0
  377. package/dist/settings/settings-service.js.map +1 -0
  378. package/dist/settings/settings-validation-error.d.ts +23 -0
  379. package/dist/settings/settings-validation-error.d.ts.map +1 -0
  380. package/dist/settings/settings-validation-error.js +17 -0
  381. package/dist/settings/settings-validation-error.js.map +1 -0
  382. package/dist/settings/status.d.ts +16 -0
  383. package/dist/settings/status.d.ts.map +1 -0
  384. package/dist/settings/status.js +18 -0
  385. package/dist/settings/status.js.map +1 -0
  386. package/dist/settings/suggest.d.ts +77 -0
  387. package/dist/settings/suggest.d.ts.map +1 -0
  388. package/dist/settings/suggest.js +192 -0
  389. package/dist/settings/suggest.js.map +1 -0
  390. package/dist/settings/system/kernel-settings.d.ts +43 -0
  391. package/dist/settings/system/kernel-settings.d.ts.map +1 -0
  392. package/dist/settings/system/kernel-settings.js +53 -0
  393. package/dist/settings/system/kernel-settings.js.map +1 -0
  394. package/dist/settings/system/module-gate.d.ts +31 -0
  395. package/dist/settings/system/module-gate.d.ts.map +1 -0
  396. package/dist/settings/system/module-gate.js +34 -0
  397. package/dist/settings/system/module-gate.js.map +1 -0
  398. package/dist/settings/testing/index.d.ts +2 -0
  399. package/dist/settings/testing/index.d.ts.map +1 -0
  400. package/dist/settings/testing/index.js +6 -0
  401. package/dist/settings/testing/index.js.map +1 -0
  402. package/dist/settings/testing/settings-store-contract.d.ts +29 -0
  403. package/dist/settings/testing/settings-store-contract.d.ts.map +1 -0
  404. package/dist/settings/testing/settings-store-contract.js +192 -0
  405. package/dist/settings/testing/settings-store-contract.js.map +1 -0
  406. package/dist/settings/timed-search.d.ts +26 -0
  407. package/dist/settings/timed-search.d.ts.map +1 -0
  408. package/dist/settings/timed-search.js +48 -0
  409. package/dist/settings/timed-search.js.map +1 -0
  410. package/dist/settings/types.d.ts +37 -0
  411. package/dist/settings/types.d.ts.map +1 -0
  412. package/dist/settings/types.js +3 -0
  413. package/dist/settings/types.js.map +1 -0
  414. package/dist/settings/validate.d.ts +55 -0
  415. package/dist/settings/validate.d.ts.map +1 -0
  416. package/dist/settings/validate.js +196 -0
  417. package/dist/settings/validate.js.map +1 -0
  418. package/package.json +463 -88
  419. package/src/authz/require-level-guard.ts +1 -1
  420. package/src/color.ts +134 -0
  421. package/src/discord/command/all-required.ts +12 -0
  422. package/src/discord/command/autocomplete-dispatcher.ts +2 -1
  423. package/src/discord/command/autocomplete-limits.ts +2 -0
  424. package/src/discord/command/autocomplete.ts +8 -7
  425. package/src/discord/command/command-responders.ts +39 -0
  426. package/src/discord/command/context.ts +14 -54
  427. package/src/discord/command/cooldown-guard.ts +1 -1
  428. package/src/discord/command/create-command.ts +19 -94
  429. package/src/discord/command/create-context-menu-command.ts +19 -97
  430. package/src/discord/command/errors.ts +1 -1
  431. package/src/discord/command/guard.ts +24 -12
  432. package/src/discord/command/options.ts +202 -412
  433. package/src/discord/command/passes-guard.ts +37 -0
  434. package/src/discord/command/permission-guard.ts +1 -1
  435. package/src/discord/command/render-command-failure.ts +71 -0
  436. package/src/discord/command/route-key.ts +9 -0
  437. package/src/discord/command/router.ts +73 -12
  438. package/src/discord/command/send-embed.ts +42 -0
  439. package/src/discord/command/types.ts +7 -1
  440. package/src/discord/components/component-access.ts +20 -0
  441. package/src/discord/components/component-router.ts +73 -36
  442. package/src/discord/components/interactive-message/index.ts +5 -3
  443. package/src/discord/components/interactive-message/interactive-message-collector.ts +4 -68
  444. package/src/discord/components/interactive-message/message-edit-options.ts +47 -0
  445. package/src/discord/components/interactive-message/mount-interactive-message.ts +5 -3
  446. package/src/discord/components/interactive-message/state-store.ts +20 -0
  447. package/src/discord/components/paginator/mount-paginator.ts +3 -3
  448. package/src/discord/components/paginator/paginator-view.ts +1 -1
  449. package/src/discord/components/persistent-paginator.ts +8 -9
  450. package/src/discord/components/settings-editor/from-declaration-controls.ts +371 -0
  451. package/src/discord/components/settings-editor/from-declaration-display.ts +155 -0
  452. package/src/discord/components/settings-editor/from-declaration-layout.ts +247 -0
  453. package/src/discord/components/settings-editor/from-declaration-search.ts +77 -0
  454. package/src/discord/components/settings-editor/from-declaration-stored.ts +39 -0
  455. package/src/discord/components/settings-editor/from-declaration-subject.ts +110 -0
  456. package/src/discord/components/settings-editor/from-declaration-values.ts +240 -0
  457. package/src/discord/components/settings-editor/from-declaration.ts +218 -0
  458. package/src/discord/components/settings-editor/index.ts +5 -2
  459. package/src/discord/components/settings-editor/mount-settings-editor.ts +28 -10
  460. package/src/discord/components/settings-editor/navigation.ts +41 -0
  461. package/src/discord/components/settings-editor/settings-editor-card.view.ts +79 -36
  462. package/src/discord/components/settings-editor/settings-editor-field-modal.ts +76 -68
  463. package/src/discord/components/settings-editor/settings-editor-field-values.ts +11 -0
  464. package/src/discord/components/settings-editor/settings-editor-fields.ts +6 -11
  465. package/src/discord/components/settings-editor/settings-editor.view.ts +11 -24
  466. package/src/discord/events/event-guild-id.ts +35 -0
  467. package/src/discord/events/event-router.ts +46 -13
  468. package/src/discord/i18n.ts +47 -8
  469. package/src/discord/interaction/interaction-locale.ts +19 -0
  470. package/src/discord/interaction/locale-resolver.ts +28 -0
  471. package/src/discord/interaction/modal.ts +5 -40
  472. package/src/discord/interaction/reply-locale.ts +35 -0
  473. package/src/discord/interaction/select-bounds.ts +33 -0
  474. package/src/discord/interaction/select-menu.ts +2 -9
  475. package/src/discord/placeholder-registry.ts +1 -1
  476. package/src/discord/presenter.ts +1 -1
  477. package/src/discord/settings/discord-guild-directory.ts +214 -0
  478. package/src/discord/settings/guild-locale-resolver.ts +38 -0
  479. package/src/discord/settings/is-module-disabled.ts +22 -0
  480. package/src/discord/settings/module-disabled-embed.ts +19 -0
  481. package/src/discord/settings/require-configured.ts +68 -0
  482. package/src/discord/ui/color-aliases.ts +70 -0
  483. package/src/discord/ui/color-input.ts +8 -173
  484. package/src/discord/ui/colors.ts +2 -3
  485. package/src/discord/ui/embed.ts +3 -9
  486. package/src/discord/ui/truncate-text.ts +15 -0
  487. package/src/errors/error-message.ts +10 -0
  488. package/src/i18n/business-message.ts +1 -1
  489. package/src/i18n/catalog.ts +14 -0
  490. package/src/i18n/locale.ts +3 -0
  491. package/src/i18n/translator.ts +3 -0
  492. package/src/module/module.ts +15 -1
  493. package/src/resilience/retry.ts +1 -1
  494. package/src/scheduler/scheduler.ts +2 -5
  495. package/src/settings/cache.ts +70 -0
  496. package/src/settings/choice.ts +9 -0
  497. package/src/settings/decode-stored.ts +40 -0
  498. package/src/settings/define-settings.ts +214 -0
  499. package/src/settings/describe-field.ts +155 -0
  500. package/src/settings/describe.ts +90 -0
  501. package/src/settings/duration.ts +54 -0
  502. package/src/settings/field-order.ts +128 -0
  503. package/src/settings/field-search.ts +16 -0
  504. package/src/settings/fields/builders.ts +161 -0
  505. package/src/settings/fields/field.ts +205 -0
  506. package/src/settings/fields/zod-schema.ts +300 -0
  507. package/src/settings/in-memory/in-memory-guild-directory.ts +69 -0
  508. package/src/settings/in-memory/in-memory-settings-store.ts +46 -0
  509. package/src/settings/in-memory/in-process-notifier.ts +40 -0
  510. package/src/settings/index.ts +72 -0
  511. package/src/settings/is-plain-object.ts +11 -0
  512. package/src/settings/messages.ts +161 -0
  513. package/src/settings/migrate.ts +58 -0
  514. package/src/settings/ports/guild-directory.ts +48 -0
  515. package/src/settings/ports/settings-changed-notifier.ts +20 -0
  516. package/src/settings/ports/settings-store.ts +74 -0
  517. package/src/settings/registry.ts +137 -0
  518. package/src/settings/settings-declaration-error.ts +16 -0
  519. package/src/settings/settings-service.ts +535 -0
  520. package/src/settings/settings-validation-error.ts +41 -0
  521. package/src/settings/status.ts +25 -0
  522. package/src/settings/suggest.ts +267 -0
  523. package/src/settings/system/kernel-settings.ts +74 -0
  524. package/src/settings/system/module-gate.ts +49 -0
  525. package/src/settings/testing/index.ts +7 -0
  526. package/src/settings/testing/settings-store-contract.ts +278 -0
  527. package/src/settings/timed-search.ts +56 -0
  528. package/src/settings/types.ts +42 -0
  529. package/src/settings/validate.ts +298 -0
@@ -1,7 +1,6 @@
1
1
  import type {
2
2
  ActionRowBuilder,
3
3
  AnySelectMenuInteraction,
4
- Attachment,
5
4
  ButtonInteraction,
6
5
  ChatInputCommandInteraction,
7
6
  CollectedMessageInteraction,
@@ -11,10 +10,12 @@ import type {
11
10
  MessageActionRowComponentBuilder,
12
11
  MessageMentionOptions,
13
12
  } from "discord.js";
13
+ import { toMessageEditOptions } from "@/discord/components/interactive-message/message-edit-options";
14
+ import type { StateStore } from "@/discord/components/interactive-message/state-store";
14
15
  import type { Button, ComponentContext } from "@/discord/interaction/button";
15
16
  import type { Card } from "@/discord/ui/card";
16
- import { type MessageFile, planMessageAttachments } from "@/discord/ui/message-attachments";
17
- import { describeError } from "@/errors";
17
+ import type { MessageFile } from "@/discord/ui/message-attachments";
18
+ import { describeError } from "@/errors/describe-error";
18
19
  import type { Logger } from "@/logger";
19
20
 
20
21
  /** How long an interactive message stays live without a click. */
@@ -90,71 +91,6 @@ export type InteractiveMessagePayload<S = never> =
90
91
  components?: never;
91
92
  });
92
93
 
93
- /**
94
- * The payload as discord.js takes it on an edit, given what the message already
95
- * carries: a file already up there under a wanted name is kept rather than
96
- * uploaded a second time, so an unrelated re-render neither drops nor re-sends
97
- * it.
98
- *
99
- * A payload stating no files touches neither field, leaving the message's own
100
- * attachments alone.
101
- *
102
- * `state` is only read for a `"card"` payload — it is what `Card.apply` needs
103
- * to render the container — and is otherwise unused. No caller building an
104
- * `"embeds"` payload has ever had to pass it, and still does not: it is the
105
- * third, optional parameter existing two-argument calls already omit.
106
- */
107
- export function toMessageEditOptions<S>(
108
- payload: InteractiveMessagePayload<S>,
109
- existing: readonly Attachment[],
110
- state?: S,
111
- ): InteractionUpdateOptions {
112
- const { files, allowedMentions } = payload;
113
- const mentions = allowedMentions === undefined ? {} : { allowedMentions };
114
- const attachments = files === undefined ? {} : planMessageAttachments(existing, files);
115
-
116
- if (payload.layout === "card") {
117
- if (state === undefined) {
118
- // Only reachable once a caller actually builds a `"card"` payload
119
- // without threading the state its `Card` needs — a programming error
120
- // in that caller, not something a member's click can trigger.
121
- throw new Error(
122
- 'toMessageEditOptions: a "card" layout payload needs the state its Card was ' +
123
- "declared over to call Card.apply, and none was given.",
124
- );
125
- }
126
- return {
127
- components: [payload.card.apply(state, false), ...(payload.controls ?? [])],
128
- ...mentions,
129
- ...attachments,
130
- };
131
- }
132
-
133
- const { embeds, components } = payload;
134
- return { embeds, components, ...mentions, ...attachments };
135
- }
136
-
137
- /**
138
- * The single mutable cell shared by the view and the collector. Kept explicit
139
- * rather than captured as a closure variable so the two always read the same
140
- * reference.
141
- */
142
- export interface StateStore<S> {
143
- read(): S;
144
- write(next: S): void;
145
- }
146
-
147
- /** Hold a state the collector can advance in place. */
148
- export function createStateStore<S>(initial: S): StateStore<S> {
149
- let state = initial;
150
- return {
151
- read: () => state,
152
- write: (next) => {
153
- state = next;
154
- },
155
- };
156
- }
157
-
158
94
  /**
159
95
  * What {@link InteractiveView.disabledControls} hands back: enough of a payload
160
96
  * to neutralise the message's controls in place, in whichever layout the view
@@ -0,0 +1,47 @@
1
+ import type { Attachment, InteractionUpdateOptions } from "discord.js";
2
+ import type { InteractiveMessagePayload } from "@/discord/components/interactive-message/interactive-message-collector";
3
+ import { planMessageAttachments } from "@/discord/ui/message-attachments";
4
+
5
+ /**
6
+ * The payload as discord.js takes it on an edit, given what the message already
7
+ * carries: a file already up there under a wanted name is kept rather than
8
+ * uploaded a second time, so an unrelated re-render neither drops nor re-sends
9
+ * it.
10
+ *
11
+ * A payload stating no files touches neither field, leaving the message's own
12
+ * attachments alone.
13
+ *
14
+ * `state` is only read for a `"card"` payload — it is what `Card.apply` needs
15
+ * to render the container — and is otherwise unused. No caller building an
16
+ * `"embeds"` payload has ever had to pass it, and still does not: it is the
17
+ * third, optional parameter existing two-argument calls already omit.
18
+ */
19
+ export function toMessageEditOptions<S>(
20
+ payload: InteractiveMessagePayload<S>,
21
+ existing: readonly Attachment[],
22
+ state?: S,
23
+ ): InteractionUpdateOptions {
24
+ const { files, allowedMentions } = payload;
25
+ const mentions = allowedMentions === undefined ? {} : { allowedMentions };
26
+ const attachments = files === undefined ? {} : planMessageAttachments(existing, files);
27
+
28
+ if (payload.layout === "card") {
29
+ if (state === undefined) {
30
+ // Only reachable once a caller actually builds a `"card"` payload
31
+ // without threading the state its `Card` needs — a programming error
32
+ // in that caller, not something a member's click can trigger.
33
+ throw new Error(
34
+ 'toMessageEditOptions: a "card" layout payload needs the state its Card was ' +
35
+ "declared over to call Card.apply, and none was given.",
36
+ );
37
+ }
38
+ return {
39
+ components: [payload.card.apply(state, false), ...(payload.controls ?? [])],
40
+ ...mentions,
41
+ ...attachments,
42
+ };
43
+ }
44
+
45
+ const { embeds, components } = payload;
46
+ return { embeds, components, ...mentions, ...attachments };
47
+ }
@@ -1,14 +1,16 @@
1
1
  import { type ChatInputCommandInteraction, MessageFlags } from "discord.js";
2
2
  import {
3
3
  COLLECTOR_IDLE_MS,
4
- createStateStore,
5
4
  type InteractiveMessageHandle,
6
5
  type InteractiveSelect,
7
6
  type InteractiveView,
8
7
  mountInteractiveMessageCollector,
9
- type StateStore,
10
- toMessageEditOptions,
11
8
  } from "@/discord/components/interactive-message/interactive-message-collector";
9
+ import { toMessageEditOptions } from "@/discord/components/interactive-message/message-edit-options";
10
+ import {
11
+ createStateStore,
12
+ type StateStore,
13
+ } from "@/discord/components/interactive-message/state-store";
12
14
  import type { Button } from "@/discord/interaction/button";
13
15
  import type { Logger } from "@/logger";
14
16
 
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The single mutable cell shared by the view and the collector. Kept explicit
3
+ * rather than captured as a closure variable so the two always read the same
4
+ * reference.
5
+ */
6
+ export interface StateStore<S> {
7
+ read(): S;
8
+ write(next: S): void;
9
+ }
10
+
11
+ /** Hold a state the collector can advance in place. */
12
+ export function createStateStore<S>(initial: S): StateStore<S> {
13
+ let state = initial;
14
+ return {
15
+ read: () => state,
16
+ write: (next) => {
17
+ state = next;
18
+ },
19
+ };
20
+ }
@@ -1,10 +1,10 @@
1
1
  import type { ChatInputCommandInteraction } from "discord.js";
2
2
  import {
3
3
  COLLECTOR_IDLE_MS,
4
- createStateStore,
5
4
  mountInteractiveMessageCollector,
6
- toMessageEditOptions,
7
- } from "@/discord/components/interactive-message";
5
+ } from "@/discord/components/interactive-message/interactive-message-collector";
6
+ import { toMessageEditOptions } from "@/discord/components/interactive-message/message-edit-options";
7
+ import { createStateStore } from "@/discord/components/interactive-message/state-store";
8
8
  import { createPageState, pageCount } from "@/discord/components/paginator/page-state";
9
9
  import {
10
10
  createPaginatorButtons,
@@ -2,7 +2,7 @@ import type { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js";
2
2
  import type {
3
3
  InteractiveMessagePayload,
4
4
  InteractiveView,
5
- } from "@/discord/components/interactive-message";
5
+ } from "@/discord/components/interactive-message/interactive-message-collector";
6
6
  import { type PageState, selectPageItems } from "@/discord/components/paginator/page-state";
7
7
  import { type Button, createActionRow } from "@/discord/interaction/button";
8
8
 
@@ -1,12 +1,11 @@
1
1
  import type { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js";
2
- import {
3
- type ComponentHandler,
4
- type ComponentRuntime,
5
- createComponentHandler,
6
- openToAnyone,
7
- type RoutableInteraction,
2
+ import { openToAnyone } from "@/discord/components/component-access";
3
+ import type {
4
+ ComponentHandler,
5
+ ComponentRuntime,
6
+ RoutableInteraction,
8
7
  } from "@/discord/components/component-router";
9
- import { toMessageEditOptions } from "@/discord/components/interactive-message";
8
+ import { toMessageEditOptions } from "@/discord/components/interactive-message/message-edit-options";
10
9
  import {
11
10
  clampPage,
12
11
  isFirstPage,
@@ -186,12 +185,12 @@ export function createPersistentPaginator(
186
185
  return {
187
186
  customId,
188
187
  render: (page = FIRST_PAGE) => loadPage(page),
189
- handler: createComponentHandler({
188
+ handler: {
190
189
  customId,
191
190
  // Navigation only re-renders what the message already showed to
192
191
  // whoever can see the channel; it grants no access of its own.
193
192
  authorize: openToAnyone("paging a message reveals nothing its reader could not already see"),
194
193
  handle: handleClick,
195
- }),
194
+ },
196
195
  };
197
196
  }
@@ -0,0 +1,371 @@
1
+ import type { ChannelType } from "discord.js";
2
+ import { MAX_SELECT_OPTIONS } from "@/discord/components/settings-editor/settings-editor.view";
3
+ import {
4
+ MAX_MODAL_INPUT_LENGTH,
5
+ type SettingsEditorChoice,
6
+ type SettingsEditorGroupField,
7
+ } from "@/discord/components/settings-editor/settings-editor-fields";
8
+ import { SETTINGS_EDITOR_MESSAGES } from "@/discord/i18n";
9
+ import { CHANNEL_TYPE_BY_KIND } from "@/discord/settings/discord-guild-directory";
10
+ import type { SettingsDeclaration } from "@/settings/define-settings";
11
+ import { fieldSearch } from "@/settings/field-search";
12
+ import {
13
+ type AnyField,
14
+ FIELD_LIMITS,
15
+ type FieldSpec,
16
+ unhandledFieldKind,
17
+ } from "@/settings/fields/field";
18
+ import { SETTINGS_MESSAGES } from "@/settings/messages";
19
+ import type { ChannelKind } from "@/settings/ports/guild-directory";
20
+ import { SettingsDeclarationError } from "@/settings/settings-declaration-error";
21
+
22
+ /** One editable entry of the screen: a typed box or a picker, never a level or a group. */
23
+ type ControlEntry = SettingsEditorGroupField<string>["fields"][number];
24
+
25
+ /**
26
+ * How a control's value moves between the stored settings and the screen:
27
+ * - `text`: a string typed as is (text, colour);
28
+ * - `number`: a number typed as its decimal text (integer, number);
29
+ * - `duration`: whole seconds typed as `1h30m`;
30
+ * - `secret`: typed, prefilled with whether it is set, never with its value;
31
+ * - `lines`: a list of integers or texts typed one item per line;
32
+ * - `boolean`: picked from a yes/no choice;
33
+ * - `single`: one picked id or enum value;
34
+ * - `many`: a list of picked ids or enum values;
35
+ * - `toggles`: the keys of one toggles select that are on (`keys` are that
36
+ * select's own, at most {@link MAX_SELECT_OPTIONS});
37
+ * - `search`: one value picked from a search's first results, a number for
38
+ * an integer field;
39
+ * - `other`: the free entry beside a non-strict search's select, typed, a
40
+ * number for an integer field; it opens empty and wins over the pick.
41
+ */
42
+ export type ControlShape =
43
+ | { readonly kind: "text" | "number" | "duration" | "secret" | "boolean" | "single" | "many" }
44
+ | { readonly kind: "lines"; readonly numeric: boolean }
45
+ | { readonly kind: "toggles"; readonly keys: readonly string[] }
46
+ | { readonly kind: "search"; readonly numeric: boolean }
47
+ | { readonly kind: "other"; readonly numeric: boolean };
48
+
49
+ /** One control of the screen and the declared field it edits. */
50
+ export interface DeclarationControl {
51
+ readonly entry: ControlEntry;
52
+ /** Key of the declared field; a toggles field split over several selects shares it. */
53
+ readonly fieldKey: string;
54
+ /** Id of the declared group the field belongs to, if any. */
55
+ readonly groupId: string | undefined;
56
+ readonly shape: ControlShape;
57
+ }
58
+
59
+ type TogglesSpec = Extract<FieldSpec, { readonly kind: "toggles" }>;
60
+
61
+ /** Separates a split toggles field's key from the number of its select. */
62
+ const CHUNK_SEPARATOR = "#";
63
+
64
+ /** Key suffix of the free entry beside a non-strict searchable field's select. */
65
+ export const OTHER_VALUE_SUFFIX = "#other";
66
+
67
+ /** The options each searchable field's select offers, by field key: its search's first results. */
68
+ export type SearchOptions = ReadonlyMap<string, readonly SettingsEditorChoice[]>;
69
+
70
+ /** What every entry of one declared field states, whatever its control. */
71
+ interface EntryBase {
72
+ readonly key: string;
73
+ readonly labelKey: string;
74
+ readonly hintKey: string;
75
+ }
76
+
77
+ /**
78
+ * The controls of a declaration, in declared order: one per field, except a
79
+ * toggles field with more keys than a select offers, which gets one control
80
+ * per chunk of at most {@link MAX_SELECT_OPTIONS} keys, and a non-strict
81
+ * searchable field, which gets its select then a free entry (FR-026a). A
82
+ * searchable field whose search offered nothing is typed like any other.
83
+ *
84
+ * @param declaration - the module's settings declaration.
85
+ * @param searchOptions - what each searchable field's select offers.
86
+ * @returns every control of the screen.
87
+ * @throws SettingsDeclarationError when a list holds an item kind lists cannot
88
+ * hold, which only a declaration built without `defineSettings` can do.
89
+ */
90
+ export function declarationControls(
91
+ declaration: SettingsDeclaration,
92
+ searchOptions: SearchOptions,
93
+ ): DeclarationControl[] {
94
+ return Object.entries(declaration.fields).flatMap(([fieldKey, declared]) =>
95
+ fieldControls({
96
+ declarationId: declaration.id,
97
+ fieldKey,
98
+ declared,
99
+ options: searchOptions.get(fieldKey) ?? [],
100
+ }),
101
+ );
102
+ }
103
+
104
+ /** Whether a control is the free entry beside a non-strict search's select. */
105
+ export function isFreeEntry(control: DeclarationControl): boolean {
106
+ return control.shape.kind === "other";
107
+ }
108
+
109
+ function fieldControls(params: {
110
+ declarationId: string;
111
+ fieldKey: string;
112
+ declared: AnyField;
113
+ options: readonly SettingsEditorChoice[];
114
+ }): DeclarationControl[] {
115
+ const { fieldKey, declared, options } = params;
116
+ const groupId = declared.ui?.group;
117
+ const base: EntryBase = {
118
+ key: fieldKey,
119
+ labelKey: declared.label ?? fieldKey,
120
+ hintKey: declared.description ?? SETTINGS_EDITOR_MESSAGES.entryHint,
121
+ };
122
+ const { spec } = declared;
123
+ if (spec.kind === "toggles") {
124
+ return togglesControls({ base, groupId, spec });
125
+ }
126
+ const search = fieldSearch(spec);
127
+ if (search !== undefined && options.length > 0) {
128
+ const numeric = spec.kind === "integer";
129
+ const select: DeclarationControl = {
130
+ entry: { ...base, kind: "choice", minValues: 0, maxValues: 1, choices: options },
131
+ fieldKey,
132
+ groupId,
133
+ shape: { kind: "search", numeric },
134
+ };
135
+ if (search.strict === true) {
136
+ return [select];
137
+ }
138
+ const free = typedEntry({
139
+ base: {
140
+ key: `${fieldKey}${OTHER_VALUE_SUFFIX}`,
141
+ labelKey: SETTINGS_EDITOR_MESSAGES.otherValue,
142
+ hintKey: SETTINGS_EDITOR_MESSAGES.entryHint,
143
+ },
144
+ declared,
145
+ style: "short",
146
+ maxLength: spec.kind === "text" ? spec.maxLength : undefined,
147
+ });
148
+ const helper = { helperKey: SETTINGS_EDITOR_MESSAGES.otherValueHelper };
149
+ return [
150
+ select,
151
+ { entry: { ...free, ...helper }, fieldKey, groupId, shape: { kind: "other", numeric } },
152
+ ];
153
+ }
154
+ const single = singleControl({ ...params, base, spec });
155
+ return [{ ...single, fieldKey, groupId }];
156
+ }
157
+
158
+ /** The entry and shape of a field that is not a toggles field. */
159
+ function singleControl(params: {
160
+ declarationId: string;
161
+ fieldKey: string;
162
+ declared: AnyField;
163
+ base: EntryBase;
164
+ spec: Exclude<FieldSpec, TogglesSpec>;
165
+ }): Pick<DeclarationControl, "entry" | "shape"> {
166
+ const { base, spec, declared } = params;
167
+ switch (spec.kind) {
168
+ case "text":
169
+ return {
170
+ entry: typedEntry({
171
+ base,
172
+ declared,
173
+ style: "paragraph",
174
+ maxLength: spec.maxLength ?? FIELD_LIMITS.textMaxLength,
175
+ }),
176
+ shape: { kind: "text" },
177
+ };
178
+ case "color":
179
+ return { entry: typedEntry({ base, declared, style: "short" }), shape: { kind: "text" } };
180
+ case "integer":
181
+ case "number":
182
+ return { entry: typedEntry({ base, declared, style: "short" }), shape: { kind: "number" } };
183
+ case "duration":
184
+ return { entry: typedEntry({ base, declared, style: "short" }), shape: { kind: "duration" } };
185
+ case "secret":
186
+ return { entry: typedEntry({ base, declared, style: "short" }), shape: { kind: "secret" } };
187
+ case "boolean":
188
+ return {
189
+ entry: { ...base, kind: "choice", minValues: 0, maxValues: 1, choices: BOOLEAN_CHOICES },
190
+ shape: { kind: "boolean" },
191
+ };
192
+ case "enum":
193
+ case "channel":
194
+ case "role":
195
+ case "user":
196
+ return {
197
+ entry: pickerEntry({ base, spec, bounds: { minValues: 0, maxValues: 1 } }),
198
+ shape: { kind: "single" },
199
+ };
200
+ case "list":
201
+ return listControl({ ...params, spec });
202
+ default:
203
+ return unhandledFieldKind(spec);
204
+ }
205
+ }
206
+
207
+ /** The two options of a yes/no field, stored as `"true"` and `"false"`. */
208
+ const BOOLEAN_CHOICES: readonly SettingsEditorChoice[] = [
209
+ { value: String(true), labelKey: SETTINGS_MESSAGES.booleanTrue },
210
+ { value: String(false), labelKey: SETTINGS_MESSAGES.booleanFalse },
211
+ ];
212
+
213
+ function typedEntry(params: {
214
+ base: EntryBase;
215
+ declared: AnyField;
216
+ style: "short" | "paragraph";
217
+ maxLength?: number;
218
+ }): ControlEntry {
219
+ const { base, declared, style } = params;
220
+ const placeholder =
221
+ declared.placeholder === undefined ? {} : { placeholderKey: declared.placeholder };
222
+ return {
223
+ ...base,
224
+ kind: "text",
225
+ style,
226
+ maxLength: params.maxLength ?? MAX_MODAL_INPUT_LENGTH,
227
+ ...placeholder,
228
+ };
229
+ }
230
+
231
+ /** How many values a picker takes. */
232
+ interface PickerBounds {
233
+ readonly minValues: number;
234
+ readonly maxValues: number;
235
+ }
236
+
237
+ /** The spec of a value picked from a list: a channel, a role, a member or an enum value. */
238
+ type PickableSpec = Extract<FieldSpec, { readonly kind: "enum" | "channel" | "role" | "user" }>;
239
+
240
+ function isPickable(spec: FieldSpec): spec is PickableSpec {
241
+ return (
242
+ spec.kind === "enum" || spec.kind === "channel" || spec.kind === "role" || spec.kind === "user"
243
+ );
244
+ }
245
+
246
+ /** A picker for one channel, role, user or enum value, or a list of them. */
247
+ function pickerEntry(params: {
248
+ base: EntryBase;
249
+ spec: PickableSpec;
250
+ bounds: PickerBounds;
251
+ }): ControlEntry {
252
+ const { base, spec, bounds } = params;
253
+ switch (spec.kind) {
254
+ case "enum":
255
+ return {
256
+ ...base,
257
+ ...bounds,
258
+ kind: "choice",
259
+ choices: spec.choices.map((choice) => ({ value: choice.value, labelKey: choice.label })),
260
+ };
261
+ case "channel":
262
+ return { ...base, ...bounds, ...channelPicker(spec.types) };
263
+ case "role":
264
+ case "user":
265
+ return { ...base, ...bounds, kind: spec.kind };
266
+ default:
267
+ return unhandledFieldKind(spec);
268
+ }
269
+ }
270
+
271
+ /** Every guild channel type, for a channel field that restricts none. */
272
+ const EVERY_CHANNEL_TYPE: readonly ChannelType[] = Object.values(CHANNEL_TYPE_BY_KIND);
273
+
274
+ /** A category picker when the field takes categories only, a channel picker otherwise. */
275
+ function channelPicker(
276
+ types: readonly ChannelKind[] | undefined,
277
+ ):
278
+ | { readonly kind: "category" }
279
+ | { readonly kind: "channel"; readonly channelTypes: readonly ChannelType[] } {
280
+ if (types !== undefined && types.length > 0 && types.every(isCategory)) {
281
+ return { kind: "category" };
282
+ }
283
+ return {
284
+ kind: "channel",
285
+ channelTypes: types === undefined ? EVERY_CHANNEL_TYPE : types.map(toChannelType),
286
+ };
287
+ }
288
+
289
+ function isCategory(kind: ChannelKind): boolean {
290
+ return kind === "category";
291
+ }
292
+
293
+ function toChannelType(kind: ChannelKind): ChannelType {
294
+ return CHANNEL_TYPE_BY_KIND[kind];
295
+ }
296
+
297
+ /**
298
+ * A list of pickable items becomes a picker bounded by the list's sizes; a
299
+ * list of integers or texts, which no picker offers, a box taking one item
300
+ * per line.
301
+ */
302
+ function listControl(params: {
303
+ declarationId: string;
304
+ fieldKey: string;
305
+ declared: AnyField;
306
+ base: EntryBase;
307
+ spec: Extract<FieldSpec, { readonly kind: "list" }>;
308
+ }): Pick<DeclarationControl, "entry" | "shape"> {
309
+ const { base, spec, declared } = params;
310
+ const itemKind = spec.item.spec.kind;
311
+ if (itemKind === "integer" || itemKind === "text") {
312
+ return {
313
+ entry: typedEntry({ base, declared, style: "paragraph" }),
314
+ shape: { kind: "lines", numeric: itemKind === "integer" },
315
+ };
316
+ }
317
+ const itemSpec = spec.item.spec;
318
+ if (!isPickable(itemSpec)) {
319
+ throw new SettingsDeclarationError(
320
+ params.declarationId,
321
+ `field "${params.fieldKey}" is a list of ${itemKind}, which lists cannot hold`,
322
+ );
323
+ }
324
+ const bounds: PickerBounds = {
325
+ minValues: spec.minItems ?? 0,
326
+ maxValues: spec.maxItems ?? FIELD_LIMITS.listMaxItems,
327
+ };
328
+ return { entry: pickerEntry({ base, spec: itemSpec, bounds }), shape: { kind: "many" } };
329
+ }
330
+
331
+ /**
332
+ * A toggles field as selects of at most {@link MAX_SELECT_OPTIONS} keys each,
333
+ * every key labelled with its `keyLabels` entry. A single select keeps the
334
+ * field's key; split ones number theirs.
335
+ */
336
+ function togglesControls(params: {
337
+ base: EntryBase;
338
+ groupId: string | undefined;
339
+ spec: TogglesSpec;
340
+ }): DeclarationControl[] {
341
+ const { base, groupId, spec } = params;
342
+ const chunks = chunked(spec.keys, MAX_SELECT_OPTIONS);
343
+ return chunks.map((keys, index) => ({
344
+ entry: {
345
+ ...base,
346
+ key: chunks.length === 1 ? base.key : `${base.key}${CHUNK_SEPARATOR}${index + 1}`,
347
+ kind: "choice",
348
+ minValues: 0,
349
+ maxValues: keys.length,
350
+ choices: keys.map((key) => ({ value: key, labelKey: spec.keyLabels?.[key] ?? key })),
351
+ },
352
+ fieldKey: base.key,
353
+ groupId,
354
+ shape: { kind: "toggles", keys },
355
+ }));
356
+ }
357
+
358
+ /**
359
+ * `items` in consecutive chunks of at most `size`, in order.
360
+ *
361
+ * @param items - the items to split.
362
+ * @param size - the most items one chunk holds.
363
+ * @returns the chunks; none for no items.
364
+ */
365
+ function chunked<T>(items: readonly T[], size: number): T[][] {
366
+ const chunks: T[][] = [];
367
+ for (let start = 0; start < items.length; start += size) {
368
+ chunks.push(items.slice(start, start + size));
369
+ }
370
+ return chunks;
371
+ }