@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,240 @@
1
+ import type {
2
+ ControlShape,
3
+ DeclarationControl,
4
+ } from "@/discord/components/settings-editor/from-declaration-controls";
5
+ import {
6
+ isToggledOn,
7
+ secretStateKey,
8
+ storedIds,
9
+ } from "@/discord/components/settings-editor/from-declaration-stored";
10
+ import type { DeclarationSubject } from "@/discord/components/settings-editor/from-declaration-subject";
11
+ import type { SettingsEditorGroupMemberSubmission } from "@/discord/components/settings-editor/settings-editor-field-modal";
12
+ import {
13
+ pickedFieldValue,
14
+ textFieldValue,
15
+ } from "@/discord/components/settings-editor/settings-editor-field-values";
16
+ import {
17
+ isPickerField,
18
+ type SettingsEditorFieldValue,
19
+ } from "@/discord/components/settings-editor/settings-editor-fields";
20
+ import type { TranslateKey } from "@/i18n/translator";
21
+ import { formatDuration } from "@/settings/duration";
22
+ import { SETTINGS_MESSAGES } from "@/settings/messages";
23
+
24
+ /** A decimal number as typed: optional sign, digits with an optional fraction, optional exponent. */
25
+ const DECIMAL_PATTERN = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
26
+
27
+ /** Separates the items of a list typed one per line. */
28
+ const LINE_BREAK = /\r?\n/;
29
+
30
+ /**
31
+ * What the modal of a control opens on: the stored value as text for a typed
32
+ * control, whether a secret is set (never its value), the picked ids for a
33
+ * picker, minus the entities the guild no longer has, which the text then
34
+ * reports as unavailable.
35
+ *
36
+ * @param params.control - the control being opened.
37
+ * @param params.subject - the screen's current subject.
38
+ * @param params.translate - translates the texts shown in place of a value.
39
+ * @returns the control's prefill.
40
+ */
41
+ export function controlValue(params: {
42
+ control: DeclarationControl;
43
+ subject: DeclarationSubject;
44
+ translate: TranslateKey;
45
+ }): SettingsEditorFieldValue {
46
+ const { control, subject, translate } = params;
47
+ const value = subject.values[control.fieldKey];
48
+ const { shape } = control;
49
+ switch (shape.kind) {
50
+ case "text":
51
+ return textFieldValue(typeof value === "string" ? value : null);
52
+ case "number":
53
+ return textFieldValue(typeof value === "number" ? String(value) : null);
54
+ case "duration":
55
+ return textFieldValue(typeof value === "number" ? formatDuration(value) : null);
56
+ case "secret":
57
+ return textFieldValue(translate(secretStateKey(value)));
58
+ case "lines":
59
+ return textFieldValue(Array.isArray(value) && value.length > 0 ? value.join("\n") : null);
60
+ case "boolean":
61
+ return pickedFieldValue(typeof value === "boolean" ? [String(value)] : []);
62
+ case "single":
63
+ case "many":
64
+ return pickedValue({ control, subject, translate });
65
+ case "toggles":
66
+ return pickedFieldValue(shape.keys.filter((key) => isToggledOn({ value, key })));
67
+ case "search":
68
+ return pickedFieldValue(
69
+ typeof value === "string" || typeof value === "number" ? [String(value)] : [],
70
+ );
71
+ case "other":
72
+ // Opens empty: the value it would repeat is already picked in the select beside it.
73
+ return textFieldValue(null);
74
+ default:
75
+ return shape satisfies never;
76
+ }
77
+ }
78
+
79
+ /** The picked ids the guild still has, with a notice when any stored one is gone. */
80
+ function pickedValue(params: {
81
+ control: DeclarationControl;
82
+ subject: DeclarationSubject;
83
+ translate: TranslateKey;
84
+ }): SettingsEditorFieldValue {
85
+ const { control, subject, translate } = params;
86
+ const stored = storedIds(subject.values[control.fieldKey]);
87
+ const missing = subject.unavailable[control.fieldKey] ?? [];
88
+ const picked = pickedFieldValue(stored.filter((id) => !missing.includes(id)));
89
+ if (missing.length === 0) {
90
+ return picked;
91
+ }
92
+ return { ...picked, text: translate(SETTINGS_MESSAGES.valueUnavailable) };
93
+ }
94
+
95
+ /**
96
+ * Whether a submission leaves a control as its modal opened: the same picked
97
+ * ids in the same order for a picker (whatever notice its text carries), the
98
+ * same text for a typed control (an empty box and no value alike).
99
+ *
100
+ * @param params.control - the control submitted.
101
+ * @param params.submission - what it came back with.
102
+ * @param params.current - what its modal opened on.
103
+ * @returns `true` when nothing was changed.
104
+ */
105
+ export function isUnchanged(params: {
106
+ control: DeclarationControl;
107
+ submission: SettingsEditorGroupMemberSubmission;
108
+ current: SettingsEditorFieldValue;
109
+ }): boolean {
110
+ const { control, submission, current } = params;
111
+ if (isPickerField(control.entry)) {
112
+ const ids = submission.ids ?? [];
113
+ return (
114
+ ids.length === current.picked.length && ids.every((id, index) => current.picked[index] === id)
115
+ );
116
+ }
117
+ return (submission.value ?? "") === (current.text ?? "");
118
+ }
119
+
120
+ /**
121
+ * Add what one control submitted to a settings patch, in the form the
122
+ * settings service takes: typed text converted to the value it denotes and
123
+ * left as text when it denotes none (the service then refuses it with the same
124
+ * issue as any surface), `null` for a cleared control, and a toggles select
125
+ * merged into the full record of its field.
126
+ *
127
+ * @param params.control - the control submitted.
128
+ * @param params.submission - what it came back with.
129
+ * @param params.subject - the screen's current subject, for the toggles the
130
+ * select does not show.
131
+ * @param params.patch - the patch built so far.
132
+ * @returns the patch with the control's value.
133
+ */
134
+ export function withSubmission(params: {
135
+ control: DeclarationControl;
136
+ submission: SettingsEditorGroupMemberSubmission;
137
+ subject: DeclarationSubject;
138
+ patch: Readonly<Record<string, unknown>>;
139
+ }): Readonly<Record<string, unknown>> {
140
+ const { control, submission, subject, patch } = params;
141
+ const { fieldKey, shape } = control;
142
+ const current = Object.hasOwn(patch, fieldKey) ? patch[fieldKey] : subject.values[fieldKey];
143
+ return { ...patch, [fieldKey]: submittedValue({ shape, submission, current }) };
144
+ }
145
+
146
+ /** What one control submitted, as the settings service takes it; `current` is its field's value so far. */
147
+ function submittedValue(params: {
148
+ shape: ControlShape;
149
+ submission: SettingsEditorGroupMemberSubmission;
150
+ current: unknown;
151
+ }): unknown {
152
+ const { shape, submission } = params;
153
+ const { value, ids } = submission;
154
+ switch (shape.kind) {
155
+ case "text":
156
+ case "duration":
157
+ case "secret":
158
+ return value;
159
+ case "number":
160
+ return value === null ? null : typedNumber(value);
161
+ case "lines":
162
+ return value === null ? null : typedLines({ text: value, numeric: shape.numeric });
163
+ case "boolean":
164
+ return ids === null || ids.length === 0 ? null : pickedBoolean(ids[0]);
165
+ case "single":
166
+ return ids === null || ids.length === 0 ? null : singleId(ids);
167
+ case "many":
168
+ return ids === null ? null : [...ids];
169
+ case "toggles":
170
+ return { ...toggleRecord(params.current), ...pickedToggles({ keys: shape.keys, ids }) };
171
+ case "search":
172
+ if (ids === null || ids.length === 0) {
173
+ return null;
174
+ }
175
+ return shape.numeric ? singleNumber(ids) : singleId(ids);
176
+ case "other":
177
+ if (value === null) {
178
+ return null;
179
+ }
180
+ return shape.numeric ? typedNumber(value) : value;
181
+ default:
182
+ return shape satisfies never;
183
+ }
184
+ }
185
+
186
+ /** The one number picked, from its option's value; anything else is left for the service to refuse. */
187
+ function singleNumber(ids: readonly string[]): unknown {
188
+ const picked = singleId(ids);
189
+ return typeof picked === "string" ? typedNumber(picked) : picked;
190
+ }
191
+
192
+ /** A toggles value as a record, or an empty one when the field holds none. */
193
+ function toggleRecord(value: unknown): Readonly<Record<string, unknown>> {
194
+ return typeof value === "object" && value !== null ? { ...value } : {};
195
+ }
196
+
197
+ /** Each key of one toggles select, on when picked. */
198
+ function pickedToggles(params: {
199
+ keys: readonly string[];
200
+ ids: readonly string[] | null;
201
+ }): Record<string, boolean> {
202
+ const picked = params.ids ?? [];
203
+ // A picked id the select does not offer is kept, for the service to refuse.
204
+ const keys = [...params.keys, ...picked.filter((id) => !params.keys.includes(id))];
205
+ return Object.fromEntries(keys.map((key) => [key, picked.includes(key)]));
206
+ }
207
+
208
+ /** The number typed, or the text itself when it is not one. */
209
+ function typedNumber(text: string): number | string {
210
+ const trimmed = text.trim();
211
+ return DECIMAL_PATTERN.test(trimmed) ? Number(trimmed) : text;
212
+ }
213
+
214
+ /** Each non-empty trimmed line as one item; `null` when no line holds one. */
215
+ function typedLines(params: { text: string; numeric: boolean }): unknown[] | null {
216
+ const lines = params.text
217
+ .split(LINE_BREAK)
218
+ .map((line) => line.trim())
219
+ .filter((line) => line !== "");
220
+ if (lines.length === 0) {
221
+ return null;
222
+ }
223
+ return params.numeric ? lines.map(typedNumber) : lines;
224
+ }
225
+
226
+ /** `true` and `false` from their options; any other id is left for the service to refuse. */
227
+ function pickedBoolean(id: string | undefined): boolean | string | undefined {
228
+ if (id === String(true)) {
229
+ return true;
230
+ }
231
+ if (id === String(false)) {
232
+ return false;
233
+ }
234
+ return id;
235
+ }
236
+
237
+ /** The one id a single picker took; several are left as a list for the service to refuse. */
238
+ function singleId(ids: readonly string[]): string | readonly string[] {
239
+ return ids.length === 1 && ids[0] !== undefined ? ids[0] : ids;
240
+ }
@@ -0,0 +1,218 @@
1
+ import {
2
+ type DeclarationControl,
3
+ declarationControls,
4
+ isFreeEntry,
5
+ type SearchOptions,
6
+ } from "@/discord/components/settings-editor/from-declaration-controls";
7
+ import { controlDisplay } from "@/discord/components/settings-editor/from-declaration-display";
8
+ import { layoutDeclaration } from "@/discord/components/settings-editor/from-declaration-layout";
9
+ import { loadSearchOptions } from "@/discord/components/settings-editor/from-declaration-search";
10
+ import {
11
+ type DeclarationSubject,
12
+ loadDeclarationSubject,
13
+ } from "@/discord/components/settings-editor/from-declaration-subject";
14
+ import {
15
+ controlValue,
16
+ isUnchanged,
17
+ withSubmission,
18
+ } from "@/discord/components/settings-editor/from-declaration-values";
19
+ import type {
20
+ SettingsEditorOptions,
21
+ SettingsEditorWrite,
22
+ } from "@/discord/components/settings-editor/mount-settings-editor";
23
+ import type {
24
+ SettingsEditorGroupMemberSubmission,
25
+ SettingsEditorGroupSubmission,
26
+ SettingsEditorSubmission,
27
+ } from "@/discord/components/settings-editor/settings-editor-field-modal";
28
+ import { NotFoundError } from "@/errors/business-error";
29
+ import type { Locale } from "@/i18n/locale";
30
+ import type { TranslateKey, Translator } from "@/i18n/translator";
31
+ import type { SettingsDeclaration } from "@/settings/define-settings";
32
+ import { displayOrder } from "@/settings/field-order";
33
+ import type { RequestContext, SettingsService } from "@/settings/settings-service";
34
+
35
+ /** What the adapter hands `mountSettingsEditor`; the caller adds its own ids, chrome, clock and logger. */
36
+ type DeclarationEditorOptions = Pick<
37
+ SettingsEditorOptions<unknown, never, string>,
38
+ "fields" | "levels" | "initial" | "currentValue" | "displayValue" | "save" | "saveGroup" | "reset"
39
+ >;
40
+
41
+ /** A settings patch, keyed by field. */
42
+ type Patch = Readonly<Record<string, unknown>>;
43
+
44
+ /** What every hook of one screen reads and writes through. */
45
+ interface AdapterScope {
46
+ readonly declaration: SettingsDeclaration;
47
+ readonly service: SettingsService;
48
+ readonly ctx: RequestContext;
49
+ readonly translate: TranslateKey;
50
+ readonly controls: ReadonlyMap<string, DeclarationControl>;
51
+ readonly groups: ReadonlyMap<string, readonly DeclarationControl[]>;
52
+ }
53
+
54
+ /**
55
+ * Build the settings-editor options of a declaration for one guild: its
56
+ * fields and levels, the subject it opens on, and the hooks that read and
57
+ * write through the settings service. The caller spreads the result into
58
+ * `mountSettingsEditor` with its own `ids`, `chrome`, `clock` and `logger`.
59
+ *
60
+ * Every write goes through `service.set` as the administrator, so the screen
61
+ * accepts and refuses exactly what every other surface does; a group's modal
62
+ * is one write carrying only the members the administrator changed.
63
+ *
64
+ * A searchable field (FR-026a) is a select of the first 25 results of its
65
+ * search for an empty query, read when the screen opens, and shows its
66
+ * search's label as its current value. A non-strict one also gets an "Other
67
+ * value" text entry in the same modal: a value typed there replaces the pick
68
+ * and is validated on save like any other. A field whose search offered
69
+ * nothing is typed instead.
70
+ *
71
+ * @param declaration - the module's settings declaration.
72
+ * @param service - the settings service the screen reads and writes through.
73
+ * @param context - the guild, the administrator, their locale and the
74
+ * translator of the texts shown in place of a value.
75
+ * @returns the editor options, the initial subject read from the service.
76
+ * @throws SettingsDeclarationError when a list holds an item kind lists cannot hold.
77
+ */
78
+ export async function settingsEditorFromDeclaration<D extends SettingsDeclaration>(
79
+ declaration: D,
80
+ service: SettingsService,
81
+ context: {
82
+ readonly guildId: string;
83
+ readonly userId: string;
84
+ readonly locale: Locale;
85
+ readonly translator: Translator;
86
+ },
87
+ ): Promise<DeclarationEditorOptions> {
88
+ const { guildId, userId, locale, translator } = context;
89
+ const ctx: RequestContext = { guildId, userId, locale };
90
+ const subject = await loadDeclarationSubject({ declaration, service, ctx });
91
+ const searchOptions = await loadSearchOptions({ declaration, service, ctx, subject });
92
+ const controls = displayedControls(declaration, searchOptions);
93
+ const layout = layoutDeclaration({ declaration, controls });
94
+ const scope: AdapterScope = {
95
+ declaration,
96
+ service,
97
+ ctx,
98
+ translate: (key) => translator.translate(locale, key),
99
+ controls: new Map(controls.map((control) => [control.entry.key, control])),
100
+ groups: layout.groups,
101
+ };
102
+ return {
103
+ fields: layout.fields,
104
+ levels: layout.levels,
105
+ initial: { subject, assets: [] },
106
+ currentValue: (subject: DeclarationSubject, key: string) =>
107
+ controlValue({ translate: scope.translate, subject, control: controlAt({ scope, key }) }),
108
+ displayValue: (subject: DeclarationSubject, key: string) => {
109
+ const control = controlAt({ scope, key });
110
+ // The free entry holds nothing of its own: its field shows beside the select.
111
+ return isFreeEntry(control)
112
+ ? null
113
+ : controlDisplay({ translate: scope.translate, subject, control });
114
+ },
115
+ save: (subject: DeclarationSubject, key: string, submission: SettingsEditorSubmission) =>
116
+ saveControl({ scope, subject, control: controlAt({ scope, key }), submission }),
117
+ saveGroup: (
118
+ subject: DeclarationSubject,
119
+ groupKey: string,
120
+ submissions: SettingsEditorGroupSubmission<string>,
121
+ ) => saveGroupControls({ scope, subject, groupKey, submissions }),
122
+ reset: async () => {
123
+ await service.reset(declaration, guildId, "all", scope.ctx);
124
+ return reload(scope);
125
+ },
126
+ };
127
+ }
128
+
129
+ /** The controls of a declaration in its display order, the one every surface shows. */
130
+ function displayedControls(
131
+ declaration: SettingsDeclaration,
132
+ searchOptions: SearchOptions,
133
+ ): DeclarationControl[] {
134
+ const rank = new Map(
135
+ displayOrder(declaration)
136
+ .entries.flatMap((entry) => (entry.kind === "field" ? [entry.key] : entry.fields))
137
+ .map((key, index) => [key, index]),
138
+ );
139
+ const place = (control: DeclarationControl): number => rank.get(control.fieldKey) ?? rank.size;
140
+ return declarationControls(declaration, searchOptions).sort(
141
+ (left, right) => place(left) - place(right),
142
+ );
143
+ }
144
+
145
+ /** The screen's subject, read back from the service. */
146
+ async function reload(
147
+ scope: AdapterScope,
148
+ ): Promise<SettingsEditorWrite<DeclarationSubject, never>> {
149
+ const { declaration, service, ctx } = scope;
150
+ return { subject: await loadDeclarationSubject({ declaration, service, ctx }), assets: [] };
151
+ }
152
+
153
+ /** Write `patch` in one `service.set` (nothing when it is empty), then read the screen back. */
154
+ async function write(params: {
155
+ scope: AdapterScope;
156
+ patch: Patch;
157
+ }): Promise<SettingsEditorWrite<DeclarationSubject, never>> {
158
+ const { scope, patch } = params;
159
+ if (Object.keys(patch).length > 0) {
160
+ await scope.service.set(scope.declaration, scope.ctx.guildId, patch, scope.ctx);
161
+ }
162
+ return reload(scope);
163
+ }
164
+
165
+ function controlAt(params: { scope: AdapterScope; key: string }): DeclarationControl {
166
+ const { scope, key } = params;
167
+ const control = scope.controls.get(key);
168
+ if (control === undefined) {
169
+ throw new NotFoundError(`Settings screen of "${scope.declaration.id}" has no field "${key}"`);
170
+ }
171
+ return control;
172
+ }
173
+
174
+ /**
175
+ * Save one entry as submitted. A secret opens on whether it is set, not on
176
+ * its value: sent back untouched, that text must not replace the secret.
177
+ */
178
+ function saveControl(params: {
179
+ scope: AdapterScope;
180
+ subject: DeclarationSubject;
181
+ control: DeclarationControl;
182
+ submission: SettingsEditorSubmission;
183
+ }): Promise<SettingsEditorWrite<DeclarationSubject, never>> {
184
+ const { scope, subject, control, submission } = params;
185
+ const untouchedSecret =
186
+ control.shape.kind === "secret" &&
187
+ isUnchanged({
188
+ control,
189
+ submission,
190
+ current: controlValue({ translate: scope.translate, subject, control }),
191
+ });
192
+ const patch = untouchedSecret ? {} : withSubmission({ control, submission, subject, patch: {} });
193
+ return write({ scope, patch });
194
+ }
195
+
196
+ /**
197
+ * Save a group's modal in one write carrying only the members changed from
198
+ * what the modal opened on: a picker whose stored entity is gone, or a
199
+ * secret showing whether it is set, left untouched keeps its stored value.
200
+ */
201
+ function saveGroupControls(params: {
202
+ scope: AdapterScope;
203
+ subject: DeclarationSubject;
204
+ groupKey: string;
205
+ submissions: SettingsEditorGroupSubmission<string>;
206
+ }): Promise<SettingsEditorWrite<DeclarationSubject, never>> {
207
+ const { scope, subject, groupKey, submissions } = params;
208
+ let patch: Patch = {};
209
+ for (const control of scope.groups.get(groupKey) ?? []) {
210
+ const submission: SettingsEditorGroupMemberSubmission | undefined =
211
+ submissions[control.entry.key];
212
+ const current = controlValue({ translate: scope.translate, subject, control });
213
+ if (submission !== undefined && !isUnchanged({ control, submission, current })) {
214
+ patch = withSubmission({ control, submission, subject, patch });
215
+ }
216
+ }
217
+ return write({ scope, patch });
218
+ }
@@ -1,3 +1,4 @@
1
+ export { settingsEditorFromDeclaration } from "@/discord/components/settings-editor/from-declaration";
1
2
  export {
2
3
  mountSettingsEditor,
3
4
  type SettingsEditorOptions,
@@ -27,15 +28,17 @@ export type {
27
28
  SettingsEditorGroupSubmission,
28
29
  SettingsEditorSubmission,
29
30
  } from "@/discord/components/settings-editor/settings-editor-field-modal";
31
+ export {
32
+ pickedFieldValue,
33
+ textFieldValue,
34
+ } from "@/discord/components/settings-editor/settings-editor-field-values";
30
35
  export {
31
36
  MAX_MODAL_COMPONENTS,
32
37
  MAX_MODAL_INPUT_LENGTH,
33
- pickedFieldValue,
34
38
  type SettingsEditorChoice,
35
39
  type SettingsEditorField,
36
40
  type SettingsEditorFieldValue,
37
41
  type SettingsEditorGroupField,
38
42
  type SettingsEditorPickerField,
39
43
  type SettingsEditorValueField,
40
- textFieldValue,
41
44
  } from "@/discord/components/settings-editor/settings-editor-fields";
@@ -4,14 +4,14 @@ import type {
4
4
  ModalMessageModalSubmitInteraction,
5
5
  } from "discord.js";
6
6
  import type { Clock } from "@/clock";
7
- import {
8
- type InteractiveMessageHandle,
9
- type InteractiveSelect,
10
- type InteractiveView,
11
- mountInteractiveMessage,
12
- type StateStore,
13
- toMessageEditOptions,
14
- } from "@/discord/components/interactive-message";
7
+ import type {
8
+ InteractiveMessageHandle,
9
+ InteractiveSelect,
10
+ InteractiveView,
11
+ } from "@/discord/components/interactive-message/interactive-message-collector";
12
+ import { toMessageEditOptions } from "@/discord/components/interactive-message/message-edit-options";
13
+ import { mountInteractiveMessage } from "@/discord/components/interactive-message/mount-interactive-message";
14
+ import type { StateStore } from "@/discord/components/interactive-message/state-store";
15
15
  import {
16
16
  popLevel,
17
17
  pushLevel,
@@ -50,8 +50,11 @@ import {
50
50
  } from "@/discord/components/settings-editor/settings-editor-fields";
51
51
  import { SETTINGS_EDITOR_MESSAGES } from "@/discord/i18n";
52
52
  import { type Button, type ComponentContext, createButton } from "@/discord/interaction/button";
53
- import { BusinessError, describeError } from "@/errors";
54
- import { type Locale, resolveBusinessMessage, type Translator } from "@/i18n";
53
+ import { BusinessError } from "@/errors/business-error";
54
+ import { describeError } from "@/errors/describe-error";
55
+ import { resolveBusinessMessage } from "@/i18n/business-message";
56
+ import type { Locale } from "@/i18n/locale";
57
+ import type { Translator } from "@/i18n/translator";
55
58
  import type { Logger } from "@/logger";
56
59
 
57
60
  /**
@@ -83,6 +86,18 @@ export interface SettingsEditorOptions<S, A, F extends string> {
83
86
  readonly initial: SettingsEditorWrite<S, A>;
84
87
  /** What the modal for `field` opens on. */
85
88
  currentValue(subject: S, field: F): SettingsEditorFieldValue;
89
+ /**
90
+ * What `field` currently holds, already translated, shown on the card under
91
+ * the entry's hint as "Current: …" so an administrator reads it without
92
+ * opening the modal. Rendered from the state's subject, so it follows every
93
+ * write. `null` shows no line for that field; a group shows one
94
+ * `label: value` per member answering one; a level shows nothing. A long
95
+ * value is cut with an ellipsis.
96
+ *
97
+ * Read by the card layout only: the embeds layout ignores it. Left out, the
98
+ * card renders exactly as it did without it.
99
+ */
100
+ displayValue?(subject: S, field: F): string | null;
86
101
  /**
87
102
  * Persist one field — its typed value, its upload, or what a picker's select
88
103
  * came back with, whichever `submission` carries.
@@ -574,6 +589,9 @@ export async function mountSettingsEditor<S, A, F extends string>(
574
589
  translator,
575
590
  locale,
576
591
  dressing,
592
+ options.displayValue === undefined
593
+ ? undefined
594
+ : (subject, field) => options.displayValue?.(subject, field) ?? null,
577
595
  );
578
596
  } else {
579
597
  view = createEditorView(
@@ -65,3 +65,44 @@ export function pushLevel(path: SettingsEditorPath, key: string): SettingsEditor
65
65
  export function popLevel(path: SettingsEditorPath): SettingsEditorPath {
66
66
  return canReturn(path) ? path.slice(0, -1) : path;
67
67
  }
68
+
69
+ /** Separates the levels in a screen's heading trail. */
70
+ const BREADCRUMB_SEPARATOR = " › ";
71
+
72
+ /** Every level a screen declares, root first, and how to find one by key. */
73
+ export interface SettingsEditorLevels<F extends string> {
74
+ readonly all: readonly SettingsEditorLevel<F>[];
75
+ /**
76
+ * The level `key` names. A path entry naming no declared level cannot happen
77
+ * — only a level field puts one there — but falling back to the root keeps a
78
+ * corrupted state showing a usable screen rather than an empty one.
79
+ */
80
+ at(key: string): SettingsEditorLevel<F>;
81
+ }
82
+
83
+ /**
84
+ * Index a screen's levels. The root is a level like any other, named after the
85
+ * screen itself, so rendering never special-cases "no depth declared".
86
+ */
87
+ export function indexLevels<F extends string>(
88
+ root: SettingsEditorLevel<F>,
89
+ levels: readonly SettingsEditorLevel<F>[] | undefined,
90
+ ): SettingsEditorLevels<F> {
91
+ const all = [root, ...(levels ?? [])];
92
+ return {
93
+ all,
94
+ at: (key) => all.find((level) => level.key === key) ?? root,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Where the reader is, as a trail rather than only the current name: a level
100
+ * called "Roles" says nothing on its own about which type it belongs to.
101
+ */
102
+ export function levelTrail<F extends string>(
103
+ path: SettingsEditorPath,
104
+ levels: SettingsEditorLevels<F>,
105
+ titleOf: (level: SettingsEditorLevel<F>) => string,
106
+ ): string {
107
+ return path.map((key) => titleOf(levels.at(key))).join(BREADCRUMB_SEPARATOR);
108
+ }