@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
@@ -0,0 +1,155 @@
1
+ import { channelMention, roleMention, userMention } from "discord.js";
2
+ import type { DeclarationControl } from "@/discord/components/settings-editor/from-declaration-controls";
3
+ import {
4
+ choiceLabelKey,
5
+ isToggledOn,
6
+ secretStateKey,
7
+ storedIds,
8
+ } from "@/discord/components/settings-editor/from-declaration-stored";
9
+ import type { DeclarationSubject } from "@/discord/components/settings-editor/from-declaration-subject";
10
+ import { truncateText } from "@/discord/ui/truncate-text";
11
+ import type { TranslateKey } from "@/i18n/translator";
12
+ import { formatDuration } from "@/settings/duration";
13
+ import { SETTINGS_MESSAGES } from "@/settings/messages";
14
+
15
+ /** The most characters a stored text shows on the screen before it is cut. */
16
+ const MAX_TEXT_SHOWN = 100;
17
+
18
+ /** Separates the items of a list, and the toggles that are on. */
19
+ const ITEM_SEPARATOR = ", ";
20
+
21
+ /** One control's entry kind, which decides how a picked id reads. */
22
+ type EntryKind = DeclarationControl["entry"]["kind"];
23
+
24
+ /**
25
+ * What a control currently holds, as the administrator reads it on the
26
+ * screen: a channel, role or member as the Discord mention that renders its
27
+ * name, an enum value or toggle as its translated label, a secret as whether
28
+ * it is set (never its value), a duration formatted, anything else as is.
29
+ * A stored id the guild no longer has reads as unavailable, a value left
30
+ * unset as "not set".
31
+ *
32
+ * @param params.control - the control shown.
33
+ * @param params.subject - the screen's current subject.
34
+ * @param params.translate - translates the texts shown in place of a value.
35
+ * @returns the text shown after the entry's "Current:" prefix. A searchable
36
+ * field shows its search's label of the value, or the value itself.
37
+ */
38
+ export function controlDisplay(params: {
39
+ control: DeclarationControl;
40
+ subject: DeclarationSubject;
41
+ translate: TranslateKey;
42
+ }): string {
43
+ const { control, subject, translate } = params;
44
+ const value = subject.values[control.fieldKey];
45
+ const { shape } = control;
46
+ // A secret reads as whether it is set, whatever it holds: never its value.
47
+ if ((value === undefined || value === null) && shape.kind !== "secret") {
48
+ return translate(SETTINGS_MESSAGES.valueNotSet);
49
+ }
50
+ switch (shape.kind) {
51
+ case "secret":
52
+ return translate(secretStateKey(value));
53
+ case "text":
54
+ return truncateText(String(value), MAX_TEXT_SHOWN);
55
+ case "number":
56
+ return String(value);
57
+ case "duration":
58
+ return typeof value === "number" ? formatDuration(value) : String(value);
59
+ case "lines":
60
+ return Array.isArray(value)
61
+ ? truncateText(value.map(String).join(ITEM_SEPARATOR), MAX_TEXT_SHOWN)
62
+ : String(value);
63
+ case "boolean":
64
+ return translate(
65
+ value === true ? SETTINGS_MESSAGES.booleanTrue : SETTINGS_MESSAGES.booleanFalse,
66
+ );
67
+ case "single":
68
+ case "many":
69
+ return pickedDisplay({ control, subject, translate, value });
70
+ case "toggles":
71
+ return togglesDisplay({ control, translate, value, keys: shape.keys });
72
+ case "search":
73
+ case "other":
74
+ return truncateText(subject.labels[control.fieldKey] ?? String(value), MAX_TEXT_SHOWN);
75
+ default:
76
+ return shape satisfies never;
77
+ }
78
+ }
79
+
80
+ /** Each picked id or enum value as it reads, joined; "not set" for an empty list. */
81
+ function pickedDisplay(params: {
82
+ control: DeclarationControl;
83
+ subject: DeclarationSubject;
84
+ translate: TranslateKey;
85
+ value: unknown;
86
+ }): string {
87
+ const { control, subject, translate, value } = params;
88
+ const ids = storedIds(value);
89
+ if (ids.length === 0) {
90
+ return translate(SETTINGS_MESSAGES.valueNotSet);
91
+ }
92
+ const missing = subject.unavailable[control.fieldKey] ?? [];
93
+ return ids
94
+ .map((id) =>
95
+ missing.includes(id)
96
+ ? translate(SETTINGS_MESSAGES.valueUnavailable)
97
+ : pickedItem({ control, translate, id }),
98
+ )
99
+ .join(ITEM_SEPARATOR);
100
+ }
101
+
102
+ /** One picked id as a mention, or one enum value as its choice label. */
103
+ function pickedItem(params: {
104
+ control: DeclarationControl;
105
+ translate: TranslateKey;
106
+ id: string;
107
+ }): string {
108
+ const { control, translate, id } = params;
109
+ const { entry } = control;
110
+ const kind: EntryKind = entry.kind;
111
+ switch (kind) {
112
+ case "channel":
113
+ case "category":
114
+ return channelMention(id);
115
+ case "role":
116
+ return roleMention(id);
117
+ case "user":
118
+ return userMention(id);
119
+ case "choice":
120
+ return translatedChoice({ control, translate, value: id });
121
+ case "text":
122
+ case "image":
123
+ return id;
124
+ default:
125
+ return kind satisfies never;
126
+ }
127
+ }
128
+
129
+ /** The translated labels of this control's toggles that are on, or "none". */
130
+ function togglesDisplay(params: {
131
+ control: DeclarationControl;
132
+ translate: TranslateKey;
133
+ value: unknown;
134
+ keys: readonly string[];
135
+ }): string {
136
+ const { control, translate, value, keys } = params;
137
+ const on = keys.filter((key) => isToggledOn({ value, key }));
138
+ if (on.length === 0) {
139
+ return translate(SETTINGS_MESSAGES.valueNone);
140
+ }
141
+ return on.map((key) => translatedChoice({ control, translate, value: key })).join(ITEM_SEPARATOR);
142
+ }
143
+
144
+ /** A choice control's value as its translated label, or the value itself when no choice offers it. */
145
+ function translatedChoice(params: {
146
+ control: DeclarationControl;
147
+ translate: TranslateKey;
148
+ value: string;
149
+ }): string {
150
+ const { control, translate, value } = params;
151
+ const { entry } = control;
152
+ const labelKey =
153
+ entry.kind === "choice" ? choiceLabelKey({ choices: entry.choices, value }) : undefined;
154
+ return labelKey === undefined ? value : translate(labelKey);
155
+ }
@@ -0,0 +1,247 @@
1
+ import {
2
+ type DeclarationControl,
3
+ isFreeEntry,
4
+ } from "@/discord/components/settings-editor/from-declaration-controls";
5
+ import type { SettingsEditorLevel } from "@/discord/components/settings-editor/navigation";
6
+ import { MAX_CARD_LEVEL_ENTRIES } from "@/discord/components/settings-editor/settings-editor-card.view";
7
+ import {
8
+ MAX_MODAL_COMPONENTS,
9
+ type SettingsEditorField,
10
+ } from "@/discord/components/settings-editor/settings-editor-fields";
11
+ import { SETTINGS_EDITOR_MESSAGES } from "@/discord/i18n";
12
+ import type { SettingsDeclaration } from "@/settings/define-settings";
13
+
14
+ /** Separates a generated entry key from its number. */
15
+ const KEY_SEPARATOR = "#";
16
+
17
+ /** Prefix of the key of every level the layout nests. */
18
+ const LEVEL_KEY_PREFIX = "level";
19
+
20
+ /** Suffix of the key of the modal a non-strict searchable field outside any group opens. */
21
+ const SEARCH_ENTRY_SUFFIX = "search";
22
+
23
+ /** The screen of a declaration: its root entries, its nested levels, and what each group entry saves. */
24
+ export interface DeclarationLayout {
25
+ readonly fields: readonly SettingsEditorField<string>[];
26
+ readonly levels: readonly SettingsEditorLevel<string>[];
27
+ /** The controls of each group entry, by the entry's key. */
28
+ readonly groups: ReadonlyMap<string, readonly DeclarationControl[]>;
29
+ }
30
+
31
+ /**
32
+ * One place on a level, in declared order: a control of its own, or a
33
+ * declared group already split into modals of at most
34
+ * {@link MAX_MODAL_COMPONENTS} controls.
35
+ */
36
+ type Section =
37
+ | { readonly kind: "control"; readonly control: DeclarationControl }
38
+ | {
39
+ readonly kind: "group";
40
+ readonly labelKey: string;
41
+ readonly hintKey: string;
42
+ readonly chunks: readonly SettingsEditorField<string>[];
43
+ };
44
+
45
+ /**
46
+ * Lay the controls of a declaration out on the screen, keeping their order
47
+ * (the display order of `@/settings/field-order`): a control outside any group is an entry of its own; a group stands
48
+ * where its first control stands, split into entries of at most
49
+ * {@link MAX_MODAL_COMPONENTS} controls (one modal each). A level holds at
50
+ * most {@link MAX_CARD_LEVEL_ENTRIES} entries: past that, a group split over
51
+ * several entries moves into a level of its own, then the entries past the
52
+ * last that fits move behind a "more settings" level.
53
+ *
54
+ * A non-strict searchable field's select and its free entry are never split:
55
+ * outside any group they form a modal of their own, named after the field;
56
+ * inside a group they share one of its modals.
57
+ *
58
+ * @param params.declaration - the module's settings declaration, for its groups.
59
+ * @param params.controls - the controls of the declaration, in declared order.
60
+ * @returns the root entries, the nested levels and each group entry's controls.
61
+ */
62
+ export function layoutDeclaration(params: {
63
+ declaration: SettingsDeclaration;
64
+ controls: readonly DeclarationControl[];
65
+ }): DeclarationLayout {
66
+ const groups = new Map<string, readonly DeclarationControl[]>();
67
+ const sections = declarationSections({ ...params, groups });
68
+ const levels: SettingsEditorLevel<string>[] = [];
69
+ const flat = sections.flatMap(sectionEntries);
70
+ const fields =
71
+ flat.length <= MAX_CARD_LEVEL_ENTRIES
72
+ ? flat
73
+ : paged({
74
+ entries: sections.flatMap((section) => foldedEntries({ section, levels })),
75
+ levels,
76
+ });
77
+ return { fields, levels, groups };
78
+ }
79
+
80
+ /** The sections of the root level, recording each group entry's controls in `groups`. */
81
+ function declarationSections(params: {
82
+ declaration: SettingsDeclaration;
83
+ controls: readonly DeclarationControl[];
84
+ groups: Map<string, readonly DeclarationControl[]>;
85
+ }): Section[] {
86
+ const { declaration, controls, groups } = params;
87
+ const sections: Section[] = [];
88
+ const seenGroups = new Set<string>();
89
+ const units = controlUnits(controls);
90
+ for (const unit of units) {
91
+ const [first] = unit;
92
+ if (first === undefined) {
93
+ continue;
94
+ }
95
+ const { groupId } = first;
96
+ if (groupId === undefined) {
97
+ sections.push(
98
+ unit.length === 1 ? { kind: "control", control: first } : searchSection({ unit, groups }),
99
+ );
100
+ } else if (!seenGroups.has(groupId)) {
101
+ seenGroups.add(groupId);
102
+ sections.push(groupSection({ declaration, units, groups, groupId }));
103
+ }
104
+ }
105
+ return sections;
106
+ }
107
+
108
+ /**
109
+ * The controls in order, a non-strict searchable field's select and free
110
+ * entry kept together as one unit, every other control a unit of its own.
111
+ */
112
+ function controlUnits(controls: readonly DeclarationControl[]): DeclarationControl[][] {
113
+ const units: DeclarationControl[][] = [];
114
+ for (const control of controls) {
115
+ const last = units.at(-1);
116
+ if (isFreeEntry(control) && last?.[0]?.fieldKey === control.fieldKey) {
117
+ last.push(control);
118
+ } else {
119
+ units.push([control]);
120
+ }
121
+ }
122
+ return units;
123
+ }
124
+
125
+ /** A non-strict searchable field outside any group: one modal holding its select and free entry. */
126
+ function searchSection(params: {
127
+ unit: readonly DeclarationControl[];
128
+ groups: Map<string, readonly DeclarationControl[]>;
129
+ }): Section {
130
+ const { unit, groups } = params;
131
+ const [{ entry, fieldKey }] = unit as [DeclarationControl];
132
+ const key = `${fieldKey}${KEY_SEPARATOR}${SEARCH_ENTRY_SUFFIX}`;
133
+ groups.set(key, unit);
134
+ const { labelKey, hintKey } = entry;
135
+ return {
136
+ kind: "group",
137
+ labelKey,
138
+ hintKey,
139
+ chunks: [{ kind: "group", key, labelKey, hintKey, fields: unit.map(controlEntry) }],
140
+ };
141
+ }
142
+
143
+ /** A declared group split into entries of at most {@link MAX_MODAL_COMPONENTS} controls, no unit split. */
144
+ function groupSection(params: {
145
+ declaration: SettingsDeclaration;
146
+ units: readonly (readonly DeclarationControl[])[];
147
+ groups: Map<string, readonly DeclarationControl[]>;
148
+ groupId: string;
149
+ }): Section {
150
+ const { declaration, units, groups, groupId } = params;
151
+ const group = declaration.groups?.[groupId];
152
+ const labelKey = group?.label ?? groupId;
153
+ const hintKey = group?.description ?? SETTINGS_EDITOR_MESSAGES.entryHint;
154
+ const members = units.filter((unit) => unit[0]?.groupId === groupId);
155
+ const chunks: SettingsEditorField<string>[] = [];
156
+ for (const [index, chunk] of packed(members, MAX_MODAL_COMPONENTS).entries()) {
157
+ const key = `${groupId}${KEY_SEPARATOR}${index + 1}`;
158
+ groups.set(key, chunk);
159
+ chunks.push({ kind: "group", key, labelKey, hintKey, fields: chunk.map(controlEntry) });
160
+ }
161
+ return { kind: "group", labelKey, hintKey, chunks };
162
+ }
163
+
164
+ /**
165
+ * `units` packed in order into chunks of at most `size` controls, a unit
166
+ * never split: one that does not fit starts the next chunk.
167
+ */
168
+ function packed(
169
+ units: readonly (readonly DeclarationControl[])[],
170
+ size: number,
171
+ ): DeclarationControl[][] {
172
+ const chunks: DeclarationControl[][] = [];
173
+ for (const unit of units) {
174
+ const last = chunks.at(-1);
175
+ if (last !== undefined && last.length + unit.length <= size) {
176
+ last.push(...unit);
177
+ } else {
178
+ chunks.push([...unit]);
179
+ }
180
+ }
181
+ return chunks;
182
+ }
183
+
184
+ function controlEntry(control: DeclarationControl): DeclarationControl["entry"] {
185
+ return control.entry;
186
+ }
187
+
188
+ /** The entries a section stands for on a level of its own. */
189
+ function sectionEntries(section: Section): readonly SettingsEditorField<string>[] {
190
+ return section.kind === "control" ? [section.control.entry] : section.chunks;
191
+ }
192
+
193
+ /** A section's entries once a group split over several entries moves into a level of its own. */
194
+ function foldedEntries(params: {
195
+ section: Section;
196
+ levels: SettingsEditorLevel<string>[];
197
+ }): readonly SettingsEditorField<string>[] {
198
+ const { section, levels } = params;
199
+ if (section.kind === "control" || section.chunks.length === 1) {
200
+ return sectionEntries(section);
201
+ }
202
+ return [
203
+ nestedLevel({
204
+ labelKey: section.labelKey,
205
+ hintKey: section.hintKey,
206
+ entries: section.chunks,
207
+ levels,
208
+ }),
209
+ ];
210
+ }
211
+
212
+ /**
213
+ * `entries` within {@link MAX_CARD_LEVEL_ENTRIES}: all of them when they fit,
214
+ * otherwise those before the last place, then a "more settings" level holding
215
+ * the rest.
216
+ */
217
+ function paged(params: {
218
+ entries: readonly SettingsEditorField<string>[];
219
+ levels: SettingsEditorLevel<string>[];
220
+ }): readonly SettingsEditorField<string>[] {
221
+ const { entries, levels } = params;
222
+ if (entries.length <= MAX_CARD_LEVEL_ENTRIES) {
223
+ return entries;
224
+ }
225
+ const kept = MAX_CARD_LEVEL_ENTRIES - 1;
226
+ const more = nestedLevel({
227
+ labelKey: SETTINGS_EDITOR_MESSAGES.moreEntries,
228
+ hintKey: SETTINGS_EDITOR_MESSAGES.moreEntriesHint,
229
+ entries: entries.slice(kept),
230
+ levels,
231
+ });
232
+ return [...entries.slice(0, kept), more];
233
+ }
234
+
235
+ /** Add a level holding `entries` (paged to fit) and return the entry that opens it. */
236
+ function nestedLevel(params: {
237
+ labelKey: string;
238
+ hintKey: string;
239
+ entries: readonly SettingsEditorField<string>[];
240
+ levels: SettingsEditorLevel<string>[];
241
+ }): SettingsEditorField<string> {
242
+ const { labelKey, hintKey, levels } = params;
243
+ const fields = paged({ entries: params.entries, levels });
244
+ const key = `${LEVEL_KEY_PREFIX}${KEY_SEPARATOR}${levels.length + 1}`;
245
+ levels.push({ key, titleKey: labelKey, fields });
246
+ return { kind: "level", key, levelKey: key, labelKey, hintKey };
247
+ }
@@ -0,0 +1,77 @@
1
+ import type { SearchOptions } from "@/discord/components/settings-editor/from-declaration-controls";
2
+ import type { DeclarationSubject } from "@/discord/components/settings-editor/from-declaration-subject";
3
+ import {
4
+ MAX_SELECT_OPTION_TEXT,
5
+ MAX_SELECT_OPTIONS,
6
+ } from "@/discord/components/settings-editor/settings-editor.view";
7
+ import type { SettingsEditorChoice } from "@/discord/components/settings-editor/settings-editor-fields";
8
+ import { SETTINGS_EDITOR_MESSAGES } from "@/discord/i18n";
9
+ import { truncateText } from "@/discord/ui/truncate-text";
10
+ import type { Choice } from "@/settings/choice";
11
+ import type { SettingsDeclaration } from "@/settings/define-settings";
12
+ import { fieldSearch } from "@/settings/field-search";
13
+ import type { RequestContext, SettingsService } from "@/settings/settings-service";
14
+
15
+ /**
16
+ * What each searchable field's select offers on the Discord screen (FR-026a):
17
+ * the first {@link MAX_SELECT_OPTIONS} results of its search for an empty
18
+ * query, since a select cannot be searched as it is typed. The field's
19
+ * current value leads the list when the results miss it, so a modal opened
20
+ * and sent back unchanged keeps it. A field whose search offered nothing
21
+ * (it failed or timed out, logged by the service) has no entry: the screen
22
+ * types it instead.
23
+ *
24
+ * @param params.declaration - the module's settings declaration.
25
+ * @param params.service - the settings service the searches run through.
26
+ * @param params.ctx - the guild, the administrator and their locale.
27
+ * @param params.subject - the screen's subject: the values searches see, and
28
+ * the current values' labels.
29
+ * @returns the options of each searchable field that has any, by field key.
30
+ */
31
+ export async function loadSearchOptions(params: {
32
+ declaration: SettingsDeclaration;
33
+ service: SettingsService;
34
+ ctx: RequestContext;
35
+ subject: DeclarationSubject;
36
+ }): Promise<SearchOptions> {
37
+ const { declaration, service, ctx, subject } = params;
38
+ const searched = await Promise.all(
39
+ Object.entries(declaration.fields)
40
+ .filter(([, declared]) => fieldSearch(declared.spec) !== undefined)
41
+ .map(async ([key]) => {
42
+ const results = await service.suggest(declaration, key, "", {
43
+ ...ctx,
44
+ values: subject.values,
45
+ });
46
+ const options = withCurrent({ results, key, subject }).map(toEditorChoice);
47
+ return [key, options.slice(0, MAX_SELECT_OPTIONS)] as const;
48
+ }),
49
+ );
50
+ return new Map(searched.filter(([, options]) => options.length > 0));
51
+ }
52
+
53
+ /** The results with the field's current value first when they miss it. */
54
+ function withCurrent(params: {
55
+ results: readonly Choice[];
56
+ key: string;
57
+ subject: DeclarationSubject;
58
+ }): readonly Choice[] {
59
+ const { results, key, subject } = params;
60
+ const value = subject.values[key];
61
+ if (results.length === 0 || (typeof value !== "string" && typeof value !== "number")) {
62
+ return results;
63
+ }
64
+ if (results.some((choice) => String(choice.value) === String(value))) {
65
+ return results;
66
+ }
67
+ return [{ name: subject.labels[key] ?? String(value), value }, ...results];
68
+ }
69
+
70
+ /** A search result as a select option: its name shown as is, cut to what an option shows. */
71
+ function toEditorChoice(choice: Choice): SettingsEditorChoice {
72
+ return {
73
+ value: String(choice.value),
74
+ labelKey: SETTINGS_EDITOR_MESSAGES.choiceLabel,
75
+ labelParams: { label: truncateText(choice.name, MAX_SELECT_OPTION_TEXT) },
76
+ };
77
+ }
@@ -0,0 +1,39 @@
1
+ import type { SettingsEditorChoice } from "@/discord/components/settings-editor/settings-editor-fields";
2
+ import { SETTINGS_MESSAGES } from "@/settings/messages";
3
+
4
+ /**
5
+ * How the settings screen reads a value the settings service returned, shared
6
+ * by what a modal opens on, what the screen shows, and which stored ids the
7
+ * guild still has.
8
+ */
9
+
10
+ /** The ids a stored picker value holds: none, one, or a list's. */
11
+ export function storedIds(value: unknown): readonly string[] {
12
+ if (typeof value === "string") {
13
+ return [value];
14
+ }
15
+ if (Array.isArray(value)) {
16
+ return value.filter((item): item is string => typeof item === "string");
17
+ }
18
+ return [];
19
+ }
20
+
21
+ /** `set` for a secret the surface reads as set, `not set` otherwise. */
22
+ export function secretStateKey(value: unknown): string {
23
+ const isSet = typeof value === "object" && value !== null && "isSet" in value && value.isSet;
24
+ return isSet === true ? SETTINGS_MESSAGES.secretSet : SETTINGS_MESSAGES.secretNotSet;
25
+ }
26
+
27
+ /** Whether `key` is on in a toggles value. */
28
+ export function isToggledOn(params: { value: unknown; key: string }): boolean {
29
+ const { value, key } = params;
30
+ return typeof value === "object" && value !== null && Reflect.get(value, key) === true;
31
+ }
32
+
33
+ /** The catalog key of the choice offering `value`, or `undefined` when none does. */
34
+ export function choiceLabelKey(params: {
35
+ choices: readonly SettingsEditorChoice[];
36
+ value: string;
37
+ }): string | undefined {
38
+ return params.choices.find((choice) => choice.value === params.value)?.labelKey;
39
+ }
@@ -0,0 +1,110 @@
1
+ import { storedIds } from "@/discord/components/settings-editor/from-declaration-stored";
2
+ import type { SettingsDeclaration } from "@/settings/define-settings";
3
+ import { fieldSearch } from "@/settings/field-search";
4
+ import type { AnyField } from "@/settings/fields/field";
5
+ import type { RequestContext, SettingsService } from "@/settings/settings-service";
6
+ import type { SettingsIssueCode } from "@/settings/settings-validation-error";
7
+
8
+ /**
9
+ * What the settings screen shows for one guild: every declared field as a
10
+ * surface reads it (a secret as whether it is set), the stored channels,
11
+ * roles and members the guild no longer has, and the label a searchable
12
+ * field's search gives its value.
13
+ */
14
+ export interface DeclarationSubject {
15
+ readonly values: Readonly<Record<string, unknown>>;
16
+ /** Per field key, the stored ids the guild no longer offers. */
17
+ readonly unavailable: Readonly<Record<string, readonly string[]>>;
18
+ /** Per searchable field key, its search's label of the value, when it has one. */
19
+ readonly labels: Readonly<Record<string, string>>;
20
+ }
21
+
22
+ /** The issues that say a stored id no longer names a usable entity of the guild. */
23
+ const UNAVAILABLE_CODES: ReadonlySet<SettingsIssueCode> = new Set(["notFound", "channelType"]);
24
+
25
+ /**
26
+ * Read the guild's settings of a declaration for the screen, and find which
27
+ * stored entities it no longer has by asking the service's own validator
28
+ * about each one, so the screen and every other surface agree on it.
29
+ * A searchable field's value is labelled through `service.label`.
30
+ *
31
+ * @param params.declaration - the module's settings declaration.
32
+ * @param params.service - the settings service the screen reads through.
33
+ * @param params.ctx - the guild, the administrator and their locale.
34
+ * @returns the screen's subject.
35
+ */
36
+ export async function loadDeclarationSubject(params: {
37
+ declaration: SettingsDeclaration;
38
+ service: SettingsService;
39
+ ctx: RequestContext;
40
+ }): Promise<DeclarationSubject> {
41
+ const { declaration, service, ctx } = params;
42
+ const values: Readonly<Record<string, unknown>> = await service.getForSurface(
43
+ declaration,
44
+ ctx.guildId,
45
+ );
46
+ const checked = await Promise.all(
47
+ Object.entries(declaration.fields).map(async ([key, declared]) => {
48
+ const missing = await unavailableIds({ ...params, key, declared, value: values[key] });
49
+ return [key, missing] as const;
50
+ }),
51
+ );
52
+ const labelled = await Promise.all(
53
+ Object.entries(declaration.fields).map(async ([key, declared]) => {
54
+ const value = values[key];
55
+ if (value === undefined || fieldSearch(declared.spec) === undefined) {
56
+ return [];
57
+ }
58
+ const label = await service.label(declaration, key, value, ctx);
59
+ return label === undefined ? [] : [[key, label] as const];
60
+ }),
61
+ );
62
+ return {
63
+ values,
64
+ unavailable: Object.fromEntries(checked.filter(hasMissingIds)),
65
+ labels: Object.fromEntries(labelled.flat()),
66
+ };
67
+ }
68
+
69
+ function hasMissingIds(entry: readonly [string, readonly string[]]): boolean {
70
+ return entry[1].length > 0;
71
+ }
72
+
73
+ /** The ids a field stores that the guild no longer offers. */
74
+ async function unavailableIds(params: {
75
+ declaration: SettingsDeclaration;
76
+ service: SettingsService;
77
+ ctx: RequestContext;
78
+ key: string;
79
+ declared: AnyField;
80
+ value: unknown;
81
+ }): Promise<readonly string[]> {
82
+ const { declaration, service, ctx, key, declared, value } = params;
83
+ const isList = declared.spec.kind === "list";
84
+ if (!isEntityKind(itemKind(declared))) {
85
+ return [];
86
+ }
87
+ const ids = storedIds(value);
88
+ const usable = await Promise.all(
89
+ ids.map(async (id) => {
90
+ const result = await service.validate(
91
+ declaration,
92
+ ctx.guildId,
93
+ { [key]: isList ? [id] : id },
94
+ ctx,
95
+ );
96
+ return result.ok || !result.issues.some((issue) => UNAVAILABLE_CODES.has(issue.code));
97
+ }),
98
+ );
99
+ return ids.filter((_, index) => usable[index] === false);
100
+ }
101
+
102
+ /** The kind of a list's items, or of the field itself when it is not a list. */
103
+ function itemKind(declared: AnyField): string {
104
+ return declared.spec.kind === "list" ? declared.spec.item.spec.kind : declared.spec.kind;
105
+ }
106
+
107
+ /** Whether a field of this kind references a channel, a role or a member of the guild. */
108
+ function isEntityKind(kind: string): boolean {
109
+ return kind === "channel" || kind === "role" || kind === "user";
110
+ }