@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.
- package/README.md +318 -1
- package/dist/authz/require-level-guard.d.ts +1 -1
- package/dist/authz/require-level-guard.d.ts.map +1 -1
- package/dist/color.d.ts +42 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +130 -0
- package/dist/color.js.map +1 -0
- package/dist/discord/command/all-required.d.ts +8 -0
- package/dist/discord/command/all-required.d.ts.map +1 -0
- package/dist/discord/command/all-required.js +12 -0
- package/dist/discord/command/all-required.js.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -1
- package/dist/discord/command/autocomplete-dispatcher.js +2 -1
- package/dist/discord/command/autocomplete-dispatcher.js.map +1 -1
- package/dist/discord/command/autocomplete-limits.d.ts +3 -0
- package/dist/discord/command/autocomplete-limits.d.ts.map +1 -0
- package/dist/discord/command/autocomplete-limits.js +6 -0
- package/dist/discord/command/autocomplete-limits.js.map +1 -0
- package/dist/discord/command/autocomplete.d.ts +6 -1
- package/dist/discord/command/autocomplete.d.ts.map +1 -1
- package/dist/discord/command/autocomplete.js +6 -6
- package/dist/discord/command/autocomplete.js.map +1 -1
- package/dist/discord/command/command-responders.d.ts +26 -0
- package/dist/discord/command/command-responders.d.ts.map +1 -0
- package/dist/discord/command/command-responders.js +16 -0
- package/dist/discord/command/command-responders.js.map +1 -0
- package/dist/discord/command/context.d.ts +8 -17
- package/dist/discord/command/context.d.ts.map +1 -1
- package/dist/discord/command/context.js +8 -37
- package/dist/discord/command/context.js.map +1 -1
- package/dist/discord/command/cooldown-guard.d.ts +1 -1
- package/dist/discord/command/cooldown-guard.d.ts.map +1 -1
- package/dist/discord/command/create-command.d.ts +1 -8
- package/dist/discord/command/create-command.d.ts.map +1 -1
- package/dist/discord/command/create-command.js +17 -65
- package/dist/discord/command/create-command.js.map +1 -1
- package/dist/discord/command/create-context-menu-command.d.ts +3 -9
- package/dist/discord/command/create-context-menu-command.d.ts.map +1 -1
- package/dist/discord/command/create-context-menu-command.js +14 -65
- package/dist/discord/command/create-context-menu-command.js.map +1 -1
- package/dist/discord/command/errors.d.ts +1 -1
- package/dist/discord/command/errors.d.ts.map +1 -1
- package/dist/discord/command/errors.js +2 -2
- package/dist/discord/command/errors.js.map +1 -1
- package/dist/discord/command/guard.d.ts +15 -8
- package/dist/discord/command/guard.d.ts.map +1 -1
- package/dist/discord/command/guard.js +4 -4
- package/dist/discord/command/guard.js.map +1 -1
- package/dist/discord/command/options.d.ts +1 -10
- package/dist/discord/command/options.d.ts.map +1 -1
- package/dist/discord/command/options.js +127 -320
- package/dist/discord/command/options.js.map +1 -1
- package/dist/discord/command/passes-guard.d.ts +14 -0
- package/dist/discord/command/passes-guard.d.ts.map +1 -0
- package/dist/discord/command/passes-guard.js +27 -0
- package/dist/discord/command/passes-guard.js.map +1 -0
- package/dist/discord/command/permission-guard.d.ts +1 -1
- package/dist/discord/command/permission-guard.d.ts.map +1 -1
- package/dist/discord/command/render-command-failure.d.ts +23 -0
- package/dist/discord/command/render-command-failure.d.ts.map +1 -0
- package/dist/discord/command/render-command-failure.js +40 -0
- package/dist/discord/command/render-command-failure.js.map +1 -0
- package/dist/discord/command/route-key.d.ts +8 -0
- package/dist/discord/command/route-key.d.ts.map +1 -0
- package/dist/discord/command/route-key.js +13 -0
- package/dist/discord/command/route-key.js.map +1 -0
- package/dist/discord/command/router.d.ts +32 -7
- package/dist/discord/command/router.d.ts.map +1 -1
- package/dist/discord/command/router.js +48 -10
- package/dist/discord/command/router.js.map +1 -1
- package/dist/discord/command/send-embed.d.ts +14 -0
- package/dist/discord/command/send-embed.d.ts.map +1 -0
- package/dist/discord/command/send-embed.js +37 -0
- package/dist/discord/command/send-embed.js.map +1 -0
- package/dist/discord/command/types.d.ts +7 -1
- package/dist/discord/command/types.d.ts.map +1 -1
- package/dist/discord/components/component-access.d.ts +12 -0
- package/dist/discord/components/component-access.d.ts.map +1 -0
- package/dist/discord/components/component-access.js +21 -0
- package/dist/discord/components/component-access.js.map +1 -0
- package/dist/discord/components/component-router.d.ts +37 -15
- package/dist/discord/components/component-router.d.ts.map +1 -1
- package/dist/discord/components/component-router.js +44 -32
- package/dist/discord/components/component-router.js.map +1 -1
- package/dist/discord/components/interactive-message/index.d.ts +3 -1
- package/dist/discord/components/interactive-message/index.d.ts.map +1 -1
- package/dist/discord/components/interactive-message/index.js +5 -3
- package/dist/discord/components/interactive-message/index.js.map +1 -1
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +3 -28
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -1
- package/dist/discord/components/interactive-message/interactive-message-collector.js +6 -53
- package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -1
- package/dist/discord/components/interactive-message/message-edit-options.d.ts +18 -0
- package/dist/discord/components/interactive-message/message-edit-options.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/message-edit-options.js +40 -0
- package/dist/discord/components/interactive-message/message-edit-options.js.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +2 -1
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -1
- package/dist/discord/components/interactive-message/mount-interactive-message.js +4 -2
- package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -1
- package/dist/discord/components/interactive-message/state-store.d.ts +12 -0
- package/dist/discord/components/interactive-message/state-store.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/state-store.js +14 -0
- package/dist/discord/components/interactive-message/state-store.js.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.js +7 -5
- package/dist/discord/components/paginator/mount-paginator.js.map +1 -1
- package/dist/discord/components/paginator/paginator-view.d.ts +1 -1
- package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -1
- package/dist/discord/components/persistent-paginator.d.ts +1 -1
- package/dist/discord/components/persistent-paginator.d.ts.map +1 -1
- package/dist/discord/components/persistent-paginator.js +6 -6
- package/dist/discord/components/persistent-paginator.js.map +1 -1
- package/dist/discord/components/settings-editor/from-declaration-controls.d.ts +67 -0
- package/dist/discord/components/settings-editor/from-declaration-controls.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-controls.js +245 -0
- package/dist/discord/components/settings-editor/from-declaration-controls.js.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-display.d.ts +23 -0
- package/dist/discord/components/settings-editor/from-declaration-display.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-display.js +114 -0
- package/dist/discord/components/settings-editor/from-declaration-display.js.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-layout.d.ts +33 -0
- package/dist/discord/components/settings-editor/from-declaration-layout.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-layout.js +178 -0
- package/dist/discord/components/settings-editor/from-declaration-layout.js.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-search.d.ts +27 -0
- package/dist/discord/components/settings-editor/from-declaration-search.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-search.js +58 -0
- package/dist/discord/components/settings-editor/from-declaration-search.js.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-stored.d.ts +21 -0
- package/dist/discord/components/settings-editor/from-declaration-stored.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-stored.js +37 -0
- package/dist/discord/components/settings-editor/from-declaration-stored.js.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-subject.d.ts +32 -0
- package/dist/discord/components/settings-editor/from-declaration-subject.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-subject.js +65 -0
- package/dist/discord/components/settings-editor/from-declaration-subject.js.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-values.d.ts +57 -0
- package/dist/discord/components/settings-editor/from-declaration-values.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration-values.js +187 -0
- package/dist/discord/components/settings-editor/from-declaration-values.js.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration.d.ts +39 -0
- package/dist/discord/components/settings-editor/from-declaration.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/from-declaration.js +132 -0
- package/dist/discord/components/settings-editor/from-declaration.js.map +1 -0
- package/dist/discord/components/settings-editor/index.d.ts +3 -1
- package/dist/discord/components/settings-editor/index.d.ts.map +1 -1
- package/dist/discord/components/settings-editor/index.js +6 -3
- package/dist/discord/components/settings-editor/index.js.map +1 -1
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +16 -2
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -1
- package/dist/discord/components/settings-editor/mount-settings-editor.js +14 -10
- package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -1
- package/dist/discord/components/settings-editor/navigation.d.ts +20 -0
- package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -1
- package/dist/discord/components/settings-editor/navigation.js +22 -0
- package/dist/discord/components/settings-editor/navigation.js.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +4 -3
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor-card.view.js +60 -34
- package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +2 -1
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js +48 -72
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor-field-values.d.ts +6 -0
- package/dist/discord/components/settings-editor/settings-editor-field-values.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-values.js +13 -0
- package/dist/discord/components/settings-editor/settings-editor-field-values.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +6 -5
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor-fields.js +0 -10
- package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts +3 -2
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -1
- package/dist/discord/components/settings-editor/settings-editor.view.js +4 -21
- package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -1
- package/dist/discord/events/event-guild-id.d.ts +10 -0
- package/dist/discord/events/event-guild-id.d.ts.map +1 -0
- package/dist/discord/events/event-guild-id.js +37 -0
- package/dist/discord/events/event-guild-id.js.map +1 -0
- package/dist/discord/events/event-router.d.ts +17 -3
- package/dist/discord/events/event-router.d.ts.map +1 -1
- package/dist/discord/events/event-router.js +33 -9
- package/dist/discord/events/event-router.js.map +1 -1
- package/dist/discord/i18n.d.ts +17 -2
- package/dist/discord/i18n.d.ts.map +1 -1
- package/dist/discord/i18n.js +43 -6
- package/dist/discord/i18n.js.map +1 -1
- package/dist/discord/interaction/interaction-locale.d.ts +14 -0
- package/dist/discord/interaction/interaction-locale.d.ts.map +1 -0
- package/dist/discord/interaction/interaction-locale.js +12 -0
- package/dist/discord/interaction/interaction-locale.js.map +1 -0
- package/dist/discord/interaction/locale-resolver.d.ts +27 -0
- package/dist/discord/interaction/locale-resolver.d.ts.map +1 -0
- package/dist/discord/interaction/locale-resolver.js +3 -0
- package/dist/discord/interaction/locale-resolver.js.map +1 -0
- package/dist/discord/interaction/modal.d.ts.map +1 -1
- package/dist/discord/interaction/modal.js +5 -40
- package/dist/discord/interaction/modal.js.map +1 -1
- package/dist/discord/interaction/reply-locale.d.ts +18 -0
- package/dist/discord/interaction/reply-locale.d.ts.map +1 -0
- package/dist/discord/interaction/reply-locale.js +25 -0
- package/dist/discord/interaction/reply-locale.js.map +1 -0
- package/dist/discord/interaction/select-bounds.d.ts +19 -0
- package/dist/discord/interaction/select-bounds.d.ts.map +1 -0
- package/dist/discord/interaction/select-bounds.js +17 -0
- package/dist/discord/interaction/select-bounds.js.map +1 -0
- package/dist/discord/interaction/select-menu.d.ts.map +1 -1
- package/dist/discord/interaction/select-menu.js +2 -9
- package/dist/discord/interaction/select-menu.js.map +1 -1
- package/dist/discord/placeholder-registry.d.ts +1 -1
- package/dist/discord/placeholder-registry.d.ts.map +1 -1
- package/dist/discord/presenter.d.ts +1 -1
- package/dist/discord/presenter.d.ts.map +1 -1
- package/dist/discord/settings/discord-guild-directory.d.ts +28 -0
- package/dist/discord/settings/discord-guild-directory.d.ts.map +1 -0
- package/dist/discord/settings/discord-guild-directory.js +171 -0
- package/dist/discord/settings/discord-guild-directory.js.map +1 -0
- package/dist/discord/settings/guild-locale-resolver.d.ts +25 -0
- package/dist/discord/settings/guild-locale-resolver.d.ts.map +1 -0
- package/dist/discord/settings/guild-locale-resolver.js +33 -0
- package/dist/discord/settings/guild-locale-resolver.js.map +1 -0
- package/dist/discord/settings/is-module-disabled.d.ts +16 -0
- package/dist/discord/settings/is-module-disabled.d.ts.map +1 -0
- package/dist/discord/settings/is-module-disabled.js +20 -0
- package/dist/discord/settings/is-module-disabled.js.map +1 -0
- package/dist/discord/settings/module-disabled-embed.d.ts +13 -0
- package/dist/discord/settings/module-disabled-embed.d.ts.map +1 -0
- package/dist/discord/settings/module-disabled-embed.js +16 -0
- package/dist/discord/settings/module-disabled-embed.js.map +1 -0
- package/dist/discord/settings/require-configured.d.ts +26 -0
- package/dist/discord/settings/require-configured.d.ts.map +1 -0
- package/dist/discord/settings/require-configured.js +62 -0
- package/dist/discord/settings/require-configured.js.map +1 -0
- package/dist/discord/ui/color-aliases.d.ts +31 -0
- package/dist/discord/ui/color-aliases.d.ts.map +1 -0
- package/dist/discord/ui/color-aliases.js +66 -0
- package/dist/discord/ui/color-aliases.js.map +1 -0
- package/dist/discord/ui/color-input.d.ts +5 -32
- package/dist/discord/ui/color-input.d.ts.map +1 -1
- package/dist/discord/ui/color-input.js +8 -157
- package/dist/discord/ui/color-input.js.map +1 -1
- package/dist/discord/ui/colors.d.ts +0 -2
- package/dist/discord/ui/colors.d.ts.map +1 -1
- package/dist/discord/ui/colors.js +4 -5
- package/dist/discord/ui/colors.js.map +1 -1
- package/dist/discord/ui/embed.d.ts.map +1 -1
- package/dist/discord/ui/embed.js +3 -8
- package/dist/discord/ui/embed.js.map +1 -1
- package/dist/discord/ui/truncate-text.d.ts +11 -0
- package/dist/discord/ui/truncate-text.d.ts.map +1 -0
- package/dist/discord/ui/truncate-text.js +19 -0
- package/dist/discord/ui/truncate-text.js.map +1 -0
- package/dist/errors/error-message.d.ts +9 -0
- package/dist/errors/error-message.d.ts.map +1 -0
- package/dist/errors/error-message.js +14 -0
- package/dist/errors/error-message.js.map +1 -0
- package/dist/i18n/business-message.d.ts +1 -1
- package/dist/i18n/business-message.d.ts.map +1 -1
- package/dist/i18n/catalog.d.ts +9 -0
- package/dist/i18n/catalog.d.ts.map +1 -1
- package/dist/i18n/catalog.js +4 -0
- package/dist/i18n/catalog.js.map +1 -1
- package/dist/i18n/locale.d.ts +2 -0
- package/dist/i18n/locale.d.ts.map +1 -1
- package/dist/i18n/locale.js +3 -1
- package/dist/i18n/locale.js.map +1 -1
- package/dist/i18n/translator.d.ts +2 -0
- package/dist/i18n/translator.d.ts.map +1 -1
- package/dist/i18n/translator.js.map +1 -1
- package/dist/module/module.d.ts +15 -1
- package/dist/module/module.d.ts.map +1 -1
- package/dist/resilience/retry.js +2 -2
- package/dist/resilience/retry.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/scheduler.js +2 -4
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/settings/cache.d.ts +29 -0
- package/dist/settings/cache.d.ts.map +1 -0
- package/dist/settings/cache.js +47 -0
- package/dist/settings/cache.js.map +1 -0
- package/dist/settings/choice.d.ts +10 -0
- package/dist/settings/choice.d.ts.map +1 -0
- package/dist/settings/choice.js +3 -0
- package/dist/settings/choice.js.map +1 -0
- package/dist/settings/decode-stored.d.ts +20 -0
- package/dist/settings/decode-stored.d.ts.map +1 -0
- package/dist/settings/decode-stored.js +35 -0
- package/dist/settings/decode-stored.js.map +1 -0
- package/dist/settings/define-settings.d.ts +52 -0
- package/dist/settings/define-settings.d.ts.map +1 -0
- package/dist/settings/define-settings.js +141 -0
- package/dist/settings/define-settings.js.map +1 -0
- package/dist/settings/describe-field.d.ts +19 -0
- package/dist/settings/describe-field.d.ts.map +1 -0
- package/dist/settings/describe-field.js +131 -0
- package/dist/settings/describe-field.js.map +1 -0
- package/dist/settings/describe.d.ts +27 -0
- package/dist/settings/describe.d.ts.map +1 -0
- package/dist/settings/describe.js +62 -0
- package/dist/settings/describe.js.map +1 -0
- package/dist/settings/duration.d.ts +16 -0
- package/dist/settings/duration.d.ts.map +1 -0
- package/dist/settings/duration.js +56 -0
- package/dist/settings/duration.js.map +1 -0
- package/dist/settings/field-order.d.ts +37 -0
- package/dist/settings/field-order.d.ts.map +1 -0
- package/dist/settings/field-order.js +100 -0
- package/dist/settings/field-order.js.map +1 -0
- package/dist/settings/field-search.d.ts +10 -0
- package/dist/settings/field-search.d.ts.map +1 -0
- package/dist/settings/field-search.js +18 -0
- package/dist/settings/field-search.js.map +1 -0
- package/dist/settings/fields/builders.d.ts +41 -0
- package/dist/settings/fields/builders.d.ts.map +1 -0
- package/dist/settings/fields/builders.js +83 -0
- package/dist/settings/fields/builders.js.map +1 -0
- package/dist/settings/fields/field.d.ts +199 -0
- package/dist/settings/fields/field.d.ts.map +1 -0
- package/dist/settings/fields/field.js +33 -0
- package/dist/settings/fields/field.js.map +1 -0
- package/dist/settings/fields/zod-schema.d.ts +67 -0
- package/dist/settings/fields/zod-schema.d.ts.map +1 -0
- package/dist/settings/fields/zod-schema.js +281 -0
- package/dist/settings/fields/zod-schema.js.map +1 -0
- package/dist/settings/in-memory/in-memory-guild-directory.d.ts +24 -0
- package/dist/settings/in-memory/in-memory-guild-directory.d.ts.map +1 -0
- package/dist/settings/in-memory/in-memory-guild-directory.js +42 -0
- package/dist/settings/in-memory/in-memory-guild-directory.js.map +1 -0
- package/dist/settings/in-memory/in-memory-settings-store.d.ts +8 -0
- package/dist/settings/in-memory/in-memory-settings-store.d.ts.map +1 -0
- package/dist/settings/in-memory/in-memory-settings-store.js +43 -0
- package/dist/settings/in-memory/in-memory-settings-store.js.map +1 -0
- package/dist/settings/in-memory/in-process-notifier.d.ts +9 -0
- package/dist/settings/in-memory/in-process-notifier.d.ts.map +1 -0
- package/dist/settings/in-memory/in-process-notifier.js +32 -0
- package/dist/settings/in-memory/in-process-notifier.js.map +1 -0
- package/dist/settings/index.d.ts +22 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +31 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/is-plain-object.d.ts +6 -0
- package/dist/settings/is-plain-object.d.ts.map +1 -0
- package/dist/settings/is-plain-object.js +15 -0
- package/dist/settings/is-plain-object.js.map +1 -0
- package/dist/settings/messages.d.ts +61 -0
- package/dist/settings/messages.d.ts.map +1 -0
- package/dist/settings/messages.js +155 -0
- package/dist/settings/messages.js.map +1 -0
- package/dist/settings/migrate.d.ts +35 -0
- package/dist/settings/migrate.d.ts.map +1 -0
- package/dist/settings/migrate.js +42 -0
- package/dist/settings/migrate.js.map +1 -0
- package/dist/settings/ports/guild-directory.d.ts +61 -0
- package/dist/settings/ports/guild-directory.d.ts.map +1 -0
- package/dist/settings/ports/guild-directory.js +3 -0
- package/dist/settings/ports/guild-directory.js.map +1 -0
- package/dist/settings/ports/settings-changed-notifier.d.ts +20 -0
- package/dist/settings/ports/settings-changed-notifier.d.ts.map +1 -0
- package/dist/settings/ports/settings-changed-notifier.js +3 -0
- package/dist/settings/ports/settings-changed-notifier.js.map +1 -0
- package/dist/settings/ports/settings-store.d.ts +76 -0
- package/dist/settings/ports/settings-store.d.ts.map +1 -0
- package/dist/settings/ports/settings-store.js +3 -0
- package/dist/settings/ports/settings-store.js.map +1 -0
- package/dist/settings/registry.d.ts +31 -0
- package/dist/settings/registry.d.ts.map +1 -0
- package/dist/settings/registry.js +102 -0
- package/dist/settings/registry.js.map +1 -0
- package/dist/settings/settings-declaration-error.d.ts +13 -0
- package/dist/settings/settings-declaration-error.d.ts.map +1 -0
- package/dist/settings/settings-declaration-error.js +22 -0
- package/dist/settings/settings-declaration-error.js.map +1 -0
- package/dist/settings/settings-service.d.ts +148 -0
- package/dist/settings/settings-service.d.ts.map +1 -0
- package/dist/settings/settings-service.js +279 -0
- package/dist/settings/settings-service.js.map +1 -0
- package/dist/settings/settings-validation-error.d.ts +23 -0
- package/dist/settings/settings-validation-error.d.ts.map +1 -0
- package/dist/settings/settings-validation-error.js +17 -0
- package/dist/settings/settings-validation-error.js.map +1 -0
- package/dist/settings/status.d.ts +16 -0
- package/dist/settings/status.d.ts.map +1 -0
- package/dist/settings/status.js +18 -0
- package/dist/settings/status.js.map +1 -0
- package/dist/settings/suggest.d.ts +77 -0
- package/dist/settings/suggest.d.ts.map +1 -0
- package/dist/settings/suggest.js +192 -0
- package/dist/settings/suggest.js.map +1 -0
- package/dist/settings/system/kernel-settings.d.ts +43 -0
- package/dist/settings/system/kernel-settings.d.ts.map +1 -0
- package/dist/settings/system/kernel-settings.js +53 -0
- package/dist/settings/system/kernel-settings.js.map +1 -0
- package/dist/settings/system/module-gate.d.ts +31 -0
- package/dist/settings/system/module-gate.d.ts.map +1 -0
- package/dist/settings/system/module-gate.js +34 -0
- package/dist/settings/system/module-gate.js.map +1 -0
- package/dist/settings/testing/index.d.ts +2 -0
- package/dist/settings/testing/index.d.ts.map +1 -0
- package/dist/settings/testing/index.js +6 -0
- package/dist/settings/testing/index.js.map +1 -0
- package/dist/settings/testing/settings-store-contract.d.ts +29 -0
- package/dist/settings/testing/settings-store-contract.d.ts.map +1 -0
- package/dist/settings/testing/settings-store-contract.js +192 -0
- package/dist/settings/testing/settings-store-contract.js.map +1 -0
- package/dist/settings/timed-search.d.ts +26 -0
- package/dist/settings/timed-search.d.ts.map +1 -0
- package/dist/settings/timed-search.js +48 -0
- package/dist/settings/timed-search.js.map +1 -0
- package/dist/settings/types.d.ts +37 -0
- package/dist/settings/types.d.ts.map +1 -0
- package/dist/settings/types.js +3 -0
- package/dist/settings/types.js.map +1 -0
- package/dist/settings/validate.d.ts +55 -0
- package/dist/settings/validate.d.ts.map +1 -0
- package/dist/settings/validate.js +196 -0
- package/dist/settings/validate.js.map +1 -0
- package/package.json +463 -88
- package/src/authz/require-level-guard.ts +1 -1
- package/src/color.ts +134 -0
- package/src/discord/command/all-required.ts +12 -0
- package/src/discord/command/autocomplete-dispatcher.ts +2 -1
- package/src/discord/command/autocomplete-limits.ts +2 -0
- package/src/discord/command/autocomplete.ts +8 -7
- package/src/discord/command/command-responders.ts +39 -0
- package/src/discord/command/context.ts +14 -54
- package/src/discord/command/cooldown-guard.ts +1 -1
- package/src/discord/command/create-command.ts +19 -94
- package/src/discord/command/create-context-menu-command.ts +19 -97
- package/src/discord/command/errors.ts +1 -1
- package/src/discord/command/guard.ts +24 -12
- package/src/discord/command/options.ts +202 -412
- package/src/discord/command/passes-guard.ts +37 -0
- package/src/discord/command/permission-guard.ts +1 -1
- package/src/discord/command/render-command-failure.ts +71 -0
- package/src/discord/command/route-key.ts +9 -0
- package/src/discord/command/router.ts +73 -12
- package/src/discord/command/send-embed.ts +42 -0
- package/src/discord/command/types.ts +7 -1
- package/src/discord/components/component-access.ts +20 -0
- package/src/discord/components/component-router.ts +73 -36
- package/src/discord/components/interactive-message/index.ts +5 -3
- package/src/discord/components/interactive-message/interactive-message-collector.ts +4 -68
- package/src/discord/components/interactive-message/message-edit-options.ts +47 -0
- package/src/discord/components/interactive-message/mount-interactive-message.ts +5 -3
- package/src/discord/components/interactive-message/state-store.ts +20 -0
- package/src/discord/components/paginator/mount-paginator.ts +3 -3
- package/src/discord/components/paginator/paginator-view.ts +1 -1
- package/src/discord/components/persistent-paginator.ts +8 -9
- package/src/discord/components/settings-editor/from-declaration-controls.ts +371 -0
- package/src/discord/components/settings-editor/from-declaration-display.ts +155 -0
- package/src/discord/components/settings-editor/from-declaration-layout.ts +247 -0
- package/src/discord/components/settings-editor/from-declaration-search.ts +77 -0
- package/src/discord/components/settings-editor/from-declaration-stored.ts +39 -0
- package/src/discord/components/settings-editor/from-declaration-subject.ts +110 -0
- package/src/discord/components/settings-editor/from-declaration-values.ts +240 -0
- package/src/discord/components/settings-editor/from-declaration.ts +218 -0
- package/src/discord/components/settings-editor/index.ts +5 -2
- package/src/discord/components/settings-editor/mount-settings-editor.ts +28 -10
- package/src/discord/components/settings-editor/navigation.ts +41 -0
- package/src/discord/components/settings-editor/settings-editor-card.view.ts +79 -36
- package/src/discord/components/settings-editor/settings-editor-field-modal.ts +76 -68
- package/src/discord/components/settings-editor/settings-editor-field-values.ts +11 -0
- package/src/discord/components/settings-editor/settings-editor-fields.ts +6 -11
- package/src/discord/components/settings-editor/settings-editor.view.ts +11 -24
- package/src/discord/events/event-guild-id.ts +35 -0
- package/src/discord/events/event-router.ts +46 -13
- package/src/discord/i18n.ts +47 -8
- package/src/discord/interaction/interaction-locale.ts +19 -0
- package/src/discord/interaction/locale-resolver.ts +28 -0
- package/src/discord/interaction/modal.ts +5 -40
- package/src/discord/interaction/reply-locale.ts +35 -0
- package/src/discord/interaction/select-bounds.ts +33 -0
- package/src/discord/interaction/select-menu.ts +2 -9
- package/src/discord/placeholder-registry.ts +1 -1
- package/src/discord/presenter.ts +1 -1
- package/src/discord/settings/discord-guild-directory.ts +214 -0
- package/src/discord/settings/guild-locale-resolver.ts +38 -0
- package/src/discord/settings/is-module-disabled.ts +22 -0
- package/src/discord/settings/module-disabled-embed.ts +19 -0
- package/src/discord/settings/require-configured.ts +68 -0
- package/src/discord/ui/color-aliases.ts +70 -0
- package/src/discord/ui/color-input.ts +8 -173
- package/src/discord/ui/colors.ts +2 -3
- package/src/discord/ui/embed.ts +3 -9
- package/src/discord/ui/truncate-text.ts +15 -0
- package/src/errors/error-message.ts +10 -0
- package/src/i18n/business-message.ts +1 -1
- package/src/i18n/catalog.ts +14 -0
- package/src/i18n/locale.ts +3 -0
- package/src/i18n/translator.ts +3 -0
- package/src/module/module.ts +15 -1
- package/src/resilience/retry.ts +1 -1
- package/src/scheduler/scheduler.ts +2 -5
- package/src/settings/cache.ts +70 -0
- package/src/settings/choice.ts +9 -0
- package/src/settings/decode-stored.ts +40 -0
- package/src/settings/define-settings.ts +214 -0
- package/src/settings/describe-field.ts +155 -0
- package/src/settings/describe.ts +90 -0
- package/src/settings/duration.ts +54 -0
- package/src/settings/field-order.ts +128 -0
- package/src/settings/field-search.ts +16 -0
- package/src/settings/fields/builders.ts +161 -0
- package/src/settings/fields/field.ts +205 -0
- package/src/settings/fields/zod-schema.ts +300 -0
- package/src/settings/in-memory/in-memory-guild-directory.ts +69 -0
- package/src/settings/in-memory/in-memory-settings-store.ts +46 -0
- package/src/settings/in-memory/in-process-notifier.ts +40 -0
- package/src/settings/index.ts +72 -0
- package/src/settings/is-plain-object.ts +11 -0
- package/src/settings/messages.ts +161 -0
- package/src/settings/migrate.ts +58 -0
- package/src/settings/ports/guild-directory.ts +48 -0
- package/src/settings/ports/settings-changed-notifier.ts +20 -0
- package/src/settings/ports/settings-store.ts +74 -0
- package/src/settings/registry.ts +137 -0
- package/src/settings/settings-declaration-error.ts +16 -0
- package/src/settings/settings-service.ts +535 -0
- package/src/settings/settings-validation-error.ts +41 -0
- package/src/settings/status.ts +25 -0
- package/src/settings/suggest.ts +267 -0
- package/src/settings/system/kernel-settings.ts +74 -0
- package/src/settings/system/module-gate.ts +49 -0
- package/src/settings/testing/index.ts +7 -0
- package/src/settings/testing/settings-store-contract.ts +278 -0
- package/src/settings/timed-search.ts +56 -0
- package/src/settings/types.ts +42 -0
- package/src/settings/validate.ts +298 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Clock } from "@/clock";
|
|
2
|
+
import type { SettingsChangedNotifier } from "@/settings/ports/settings-changed-notifier";
|
|
3
|
+
|
|
4
|
+
/** How long a cached read stays fresh without an invalidation: the multi-process safety net (R15). */
|
|
5
|
+
export const SETTINGS_CACHE_TTL_MS = 60_000;
|
|
6
|
+
|
|
7
|
+
/** Reads of one module's settings for one guild, kept in process. */
|
|
8
|
+
export interface SettingsCache {
|
|
9
|
+
/**
|
|
10
|
+
* The cached value of `(guildId, moduleId)` while fresh, otherwise the result
|
|
11
|
+
* of `load`, kept for {@link SETTINGS_CACHE_TTL_MS}. A failed load is not
|
|
12
|
+
* kept, and neither is a load that an invalidation overtook.
|
|
13
|
+
*/
|
|
14
|
+
read<T>(guildId: string, moduleId: string, load: () => Promise<T>): Promise<T>;
|
|
15
|
+
/** Forget `(guildId, moduleId)`, so the next read loads again. */
|
|
16
|
+
invalidate(guildId: string, moduleId: string): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Entry {
|
|
20
|
+
readonly value: unknown;
|
|
21
|
+
readonly expiresAt: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create the settings read cache. It drops an entry when the notifier reports
|
|
26
|
+
* a change of it, whichever process made the change, and after
|
|
27
|
+
* {@link SETTINGS_CACHE_TTL_MS} in any case.
|
|
28
|
+
*
|
|
29
|
+
* @param deps.clock - tells when an entry expires.
|
|
30
|
+
* @param deps.notifier - the settings change events to invalidate on.
|
|
31
|
+
* @returns an empty cache, subscribed to the notifier for its whole life.
|
|
32
|
+
*/
|
|
33
|
+
export function createSettingsCache(deps: {
|
|
34
|
+
clock: Clock;
|
|
35
|
+
notifier: SettingsChangedNotifier;
|
|
36
|
+
}): SettingsCache {
|
|
37
|
+
const { clock } = deps;
|
|
38
|
+
const entries = new Map<string, Entry>();
|
|
39
|
+
/** Bumped by each invalidation, so a load that started before one is not kept. */
|
|
40
|
+
const generations = new Map<string, number>();
|
|
41
|
+
|
|
42
|
+
function invalidate(guildId: string, moduleId: string): void {
|
|
43
|
+
const key = cacheKey(guildId, moduleId);
|
|
44
|
+
entries.delete(key);
|
|
45
|
+
generations.set(key, (generations.get(key) ?? 0) + 1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deps.notifier.subscribe((event) => invalidate(event.guildId, event.moduleId));
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
async read<T>(guildId: string, moduleId: string, load: () => Promise<T>): Promise<T> {
|
|
52
|
+
const key = cacheKey(guildId, moduleId);
|
|
53
|
+
const entry = entries.get(key);
|
|
54
|
+
if (entry !== undefined && clock.now().getTime() < entry.expiresAt) {
|
|
55
|
+
return entry.value as T;
|
|
56
|
+
}
|
|
57
|
+
const generation = generations.get(key);
|
|
58
|
+
const value = await load();
|
|
59
|
+
if (generations.get(key) === generation) {
|
|
60
|
+
entries.set(key, { value, expiresAt: clock.now().getTime() + SETTINGS_CACHE_TTL_MS });
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
},
|
|
64
|
+
invalidate,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cacheKey(guildId: string, moduleId: string): string {
|
|
69
|
+
return JSON.stringify([guildId, moduleId]);
|
|
70
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single suggestion offered to a user while they pick a value: the label shown
|
|
3
|
+
* and the value stored. Vendor-free so the settings core and the command
|
|
4
|
+
* autocomplete share it without the core depending on discord.js.
|
|
5
|
+
*/
|
|
6
|
+
export interface Choice {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly value: string | number;
|
|
9
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SettingsDeclaration } from "@/settings/define-settings";
|
|
2
|
+
import { parseFieldValue, pruneStoredValue } from "@/settings/fields/zod-schema";
|
|
3
|
+
import { isPlainObject } from "@/settings/is-plain-object";
|
|
4
|
+
|
|
5
|
+
/** Stored values sorted into what a guild can read and what it cannot. */
|
|
6
|
+
export interface DecodedStored {
|
|
7
|
+
/** The declared fields whose stored value is valid, in stored form. */
|
|
8
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
9
|
+
/** The declared fields whose stored value fails validation, in declared order. */
|
|
10
|
+
readonly invalidKeys: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check stored values against a declaration, field by field, without any guild
|
|
15
|
+
* lookup: a key the declaration no longer declares is ignored, an undeclared
|
|
16
|
+
* toggle is dropped (FR-017), and a value that fails its field is left out and
|
|
17
|
+
* reported. An unset field is absent from both outputs.
|
|
18
|
+
*
|
|
19
|
+
* @param declaration - the module's current settings declaration.
|
|
20
|
+
* @param raw - the stored values, or a migration's result: any value; anything
|
|
21
|
+
* but a plain object holds no field.
|
|
22
|
+
*/
|
|
23
|
+
export function decodeStored(declaration: SettingsDeclaration, raw: unknown): DecodedStored {
|
|
24
|
+
const stored = isPlainObject(raw) ? raw : {};
|
|
25
|
+
const values: Record<string, unknown> = {};
|
|
26
|
+
const invalidKeys: string[] = [];
|
|
27
|
+
for (const [key, declared] of Object.entries(declaration.fields)) {
|
|
28
|
+
if (!Object.hasOwn(stored, key)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const value = pruneStoredValue({ field: declared, value: stored[key] });
|
|
32
|
+
const parsed = parseFieldValue({ field: declared, value });
|
|
33
|
+
if (parsed.ok) {
|
|
34
|
+
values[key] = parsed.value;
|
|
35
|
+
} else {
|
|
36
|
+
invalidKeys.push(key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { values, invalidKeys };
|
|
40
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyField,
|
|
3
|
+
FIELD_LIMITS,
|
|
4
|
+
type FieldSpec,
|
|
5
|
+
LIST_ITEM_KINDS,
|
|
6
|
+
type Suggestions,
|
|
7
|
+
unhandledFieldKind,
|
|
8
|
+
} from "@/settings/fields/field";
|
|
9
|
+
import { parseFieldValue } from "@/settings/fields/zod-schema";
|
|
10
|
+
import { SettingsDeclarationError } from "@/settings/settings-declaration-error";
|
|
11
|
+
|
|
12
|
+
/** A group of fields. Texts are catalog keys. */
|
|
13
|
+
export interface SettingsGroup {
|
|
14
|
+
readonly label: string;
|
|
15
|
+
readonly description?: string;
|
|
16
|
+
readonly order?: number;
|
|
17
|
+
/** Opaque access declaration: stored, never enforced here (FR-032). */
|
|
18
|
+
readonly access?: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Module-level presentation hints (FR-020). */
|
|
22
|
+
export interface SettingsUi {
|
|
23
|
+
readonly icon?: string;
|
|
24
|
+
/** Group ids in their preferred order. */
|
|
25
|
+
readonly groupOrder?: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Reshapes values stored under an older declaration version. */
|
|
29
|
+
export type SettingsMigration = (fromVersion: number, raw: unknown) => unknown;
|
|
30
|
+
|
|
31
|
+
/** What a module writes to declare its settings. */
|
|
32
|
+
export interface SettingsDeclarationInput {
|
|
33
|
+
/** Unique across modules; lowercase kebab-case. */
|
|
34
|
+
readonly id: string;
|
|
35
|
+
/** Starts at 1; raised when stored data must be reshaped. */
|
|
36
|
+
readonly version: number;
|
|
37
|
+
readonly labels: { readonly title: string; readonly description?: string };
|
|
38
|
+
readonly fields: Readonly<Record<string, AnyField>>;
|
|
39
|
+
readonly groups?: Readonly<Record<string, SettingsGroup>>;
|
|
40
|
+
/** Required when `version > 1`. */
|
|
41
|
+
readonly migrate?: SettingsMigration;
|
|
42
|
+
readonly ui?: SettingsUi;
|
|
43
|
+
/** Opaque access declaration: stored, never enforced here (FR-032). */
|
|
44
|
+
readonly access?: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A checked settings declaration, typed from what the module wrote. */
|
|
48
|
+
export type SettingsDeclaration<D extends SettingsDeclarationInput = SettingsDeclarationInput> = D;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Declare a module's settings. The declaration is returned unchanged — hints
|
|
52
|
+
* and access slots included — after the checks that need no guild.
|
|
53
|
+
*
|
|
54
|
+
* @param input - the module's declaration; keys and enum values stay literal.
|
|
55
|
+
* @returns the same declaration, typed for `SettingsValues`.
|
|
56
|
+
* @throws SettingsDeclarationError when a field has no label, a default fails
|
|
57
|
+
* its own field, a secret has a default or examples, an enum or static
|
|
58
|
+
* suggestion list has not 1 to 25 choices, a field names an undeclared group, a
|
|
59
|
+
* text allows more than 4000 characters, a list more than 25 items or an item
|
|
60
|
+
* kind lists cannot hold, a toggles field repeats a key or labels an undeclared
|
|
61
|
+
* one, or the version is above 1 without a migration.
|
|
62
|
+
*/
|
|
63
|
+
export function defineSettings<const D extends SettingsDeclarationInput>(
|
|
64
|
+
input: D,
|
|
65
|
+
): SettingsDeclaration<D> {
|
|
66
|
+
checkVersion(input);
|
|
67
|
+
for (const [key, declared] of Object.entries(input.fields)) {
|
|
68
|
+
checkField({ declaration: input, key, declared });
|
|
69
|
+
}
|
|
70
|
+
return input;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function checkVersion(input: SettingsDeclarationInput): void {
|
|
74
|
+
if (!Number.isInteger(input.version) || input.version < 1) {
|
|
75
|
+
throw new SettingsDeclarationError(input.id, "version must be a positive integer");
|
|
76
|
+
}
|
|
77
|
+
if (input.version > 1 && input.migrate === undefined) {
|
|
78
|
+
throw new SettingsDeclarationError(
|
|
79
|
+
input.id,
|
|
80
|
+
`version ${input.version} needs a migration from earlier versions`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Where a field check failed: the declaration and the field key. */
|
|
86
|
+
interface FieldLocation {
|
|
87
|
+
readonly declarationId: string;
|
|
88
|
+
readonly key: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function fieldError(params: { at: FieldLocation; reason: string }): SettingsDeclarationError {
|
|
92
|
+
return new SettingsDeclarationError(
|
|
93
|
+
params.at.declarationId,
|
|
94
|
+
`field "${params.at.key}" ${params.reason}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function checkField(params: {
|
|
99
|
+
declaration: SettingsDeclarationInput;
|
|
100
|
+
key: string;
|
|
101
|
+
declared: AnyField;
|
|
102
|
+
}): void {
|
|
103
|
+
const { declaration, key, declared } = params;
|
|
104
|
+
const at = { declarationId: declaration.id, key };
|
|
105
|
+
if (declared.label === undefined) {
|
|
106
|
+
throw fieldError({ at, reason: "has no label" });
|
|
107
|
+
}
|
|
108
|
+
const group = declared.ui?.group;
|
|
109
|
+
if (group !== undefined && declaration.groups?.[group] === undefined) {
|
|
110
|
+
throw fieldError({ at, reason: `names the undeclared group "${group}"` });
|
|
111
|
+
}
|
|
112
|
+
if (declared.spec.kind === "secret") {
|
|
113
|
+
checkSecret({ at, declared });
|
|
114
|
+
}
|
|
115
|
+
checkSpec({ at, spec: declared.spec });
|
|
116
|
+
if (declared.default !== undefined && !isValidDefault(declared)) {
|
|
117
|
+
throw fieldError({ at, reason: "has a default that fails its own validation" });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isValidDefault(declared: AnyField): boolean {
|
|
122
|
+
return parseFieldValue({ field: declared, value: declared.default }).ok;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function checkSecret(params: { at: FieldLocation; declared: AnyField }): void {
|
|
126
|
+
const { at, declared } = params;
|
|
127
|
+
if (declared.default !== undefined) {
|
|
128
|
+
throw fieldError({ at, reason: "is a secret and cannot have a default" });
|
|
129
|
+
}
|
|
130
|
+
if (declared.ui?.examples !== undefined) {
|
|
131
|
+
throw fieldError({ at, reason: "is a secret and cannot have examples" });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Constraints of one kind, recursing into a list's item. */
|
|
136
|
+
function checkSpec(params: { at: FieldLocation; spec: FieldSpec }): void {
|
|
137
|
+
const { at, spec } = params;
|
|
138
|
+
switch (spec.kind) {
|
|
139
|
+
case "enum":
|
|
140
|
+
checkChoiceCount({ at, count: spec.choices.length });
|
|
141
|
+
return;
|
|
142
|
+
case "integer":
|
|
143
|
+
checkStaticSuggestions({ at, suggest: spec.suggest });
|
|
144
|
+
return;
|
|
145
|
+
case "text":
|
|
146
|
+
if ((spec.maxLength ?? 0) > FIELD_LIMITS.textMaxLength) {
|
|
147
|
+
throw fieldError({
|
|
148
|
+
at,
|
|
149
|
+
reason: `allows more than ${FIELD_LIMITS.textMaxLength} characters`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
checkStaticSuggestions({ at, suggest: spec.suggest });
|
|
153
|
+
return;
|
|
154
|
+
case "list":
|
|
155
|
+
checkList({ at, spec });
|
|
156
|
+
return;
|
|
157
|
+
case "toggles":
|
|
158
|
+
checkToggles({ at, spec });
|
|
159
|
+
return;
|
|
160
|
+
case "channel":
|
|
161
|
+
case "role":
|
|
162
|
+
case "user":
|
|
163
|
+
case "color":
|
|
164
|
+
case "duration":
|
|
165
|
+
case "number":
|
|
166
|
+
case "boolean":
|
|
167
|
+
case "secret":
|
|
168
|
+
return;
|
|
169
|
+
default:
|
|
170
|
+
unhandledFieldKind(spec);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function checkList(params: { at: FieldLocation; spec: FieldSpec & { kind: "list" } }): void {
|
|
175
|
+
const { at, spec } = params;
|
|
176
|
+
if ((spec.maxItems ?? 0) > FIELD_LIMITS.listMaxItems) {
|
|
177
|
+
throw fieldError({ at, reason: `allows more than ${FIELD_LIMITS.listMaxItems} items` });
|
|
178
|
+
}
|
|
179
|
+
const itemKind = spec.item.spec.kind;
|
|
180
|
+
if (!LIST_ITEM_KINDS.includes(itemKind)) {
|
|
181
|
+
throw fieldError({ at, reason: `is a list of ${itemKind}, which lists cannot hold` });
|
|
182
|
+
}
|
|
183
|
+
checkSpec({ at, spec: spec.item.spec });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function checkToggles(params: { at: FieldLocation; spec: FieldSpec & { kind: "toggles" } }): void {
|
|
187
|
+
const { at, spec } = params;
|
|
188
|
+
if (new Set(spec.keys).size !== spec.keys.length) {
|
|
189
|
+
throw fieldError({ at, reason: "declares a toggle key more than once" });
|
|
190
|
+
}
|
|
191
|
+
for (const key of Object.keys(spec.keyLabels ?? {})) {
|
|
192
|
+
if (!spec.keys.includes(key)) {
|
|
193
|
+
throw fieldError({ at, reason: `labels the undeclared toggle key "${key}"` });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function checkStaticSuggestions(params: {
|
|
199
|
+
at: FieldLocation;
|
|
200
|
+
suggest: Suggestions<unknown> | undefined;
|
|
201
|
+
}): void {
|
|
202
|
+
if (params.suggest !== undefined && "choices" in params.suggest) {
|
|
203
|
+
checkChoiceCount({ at: params.at, count: params.suggest.choices.length });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function checkChoiceCount(params: { at: FieldLocation; count: number }): void {
|
|
208
|
+
if (params.count < 1 || params.count > FIELD_LIMITS.staticChoices) {
|
|
209
|
+
throw fieldError({
|
|
210
|
+
at: params.at,
|
|
211
|
+
reason: `must have 1 to ${FIELD_LIMITS.staticChoices} choices`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { TranslateKey } from "@/i18n/translator";
|
|
2
|
+
import {
|
|
3
|
+
type AnyField,
|
|
4
|
+
type FieldChoice,
|
|
5
|
+
type Suggestions,
|
|
6
|
+
unhandledFieldKind,
|
|
7
|
+
} from "@/settings/fields/field";
|
|
8
|
+
import type { JsonSchemaNode } from "@/settings/fields/zod-schema";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Add a field's translated texts and hints to the JSON Schema of its stored
|
|
12
|
+
* value: `title`, `description`, `default` and `examples` under their standard
|
|
13
|
+
* keywords, labelled choices as `oneOf`, and the kernel hints under `x-kernel`.
|
|
14
|
+
* A secret is write-only and never carries a default or an example.
|
|
15
|
+
*
|
|
16
|
+
* @param params.field - the field, or list item, being described.
|
|
17
|
+
* @param params.schema - the JSON Schema of its stored value, annotated in place.
|
|
18
|
+
* @param params.translate - translates the field's catalog keys.
|
|
19
|
+
*/
|
|
20
|
+
export function annotateField(params: {
|
|
21
|
+
field: AnyField;
|
|
22
|
+
schema: JsonSchemaNode;
|
|
23
|
+
translate: TranslateKey;
|
|
24
|
+
}): void {
|
|
25
|
+
const { field, schema, translate } = params;
|
|
26
|
+
if (field.label !== undefined) {
|
|
27
|
+
schema.title = translate(field.label);
|
|
28
|
+
}
|
|
29
|
+
if (field.description !== undefined) {
|
|
30
|
+
schema.description = translate(field.description);
|
|
31
|
+
}
|
|
32
|
+
if (field.spec.kind === "secret") {
|
|
33
|
+
schema.writeOnly = true;
|
|
34
|
+
} else {
|
|
35
|
+
if (field.default !== undefined) {
|
|
36
|
+
schema.default = field.default;
|
|
37
|
+
}
|
|
38
|
+
if (field.ui?.examples !== undefined) {
|
|
39
|
+
schema.examples = field.ui.examples;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (field.spec.kind === "enum") {
|
|
43
|
+
delete schema.enum;
|
|
44
|
+
schema.oneOf = labelledChoices({ choices: field.spec.choices, translate });
|
|
45
|
+
}
|
|
46
|
+
if (field.spec.kind === "toggles") {
|
|
47
|
+
annotateToggles({ field, keyLabels: field.spec.keyLabels, schema, translate });
|
|
48
|
+
}
|
|
49
|
+
schema["x-kernel"] = kernelHints({ field, translate });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Each toggle gets its translated label and its own default. */
|
|
53
|
+
function annotateToggles(params: {
|
|
54
|
+
field: AnyField;
|
|
55
|
+
keyLabels: Readonly<Partial<Record<string, string>>> | undefined;
|
|
56
|
+
schema: JsonSchemaNode;
|
|
57
|
+
translate: TranslateKey;
|
|
58
|
+
}): void {
|
|
59
|
+
const { field, keyLabels, schema, translate } = params;
|
|
60
|
+
const defaults = field.default as Readonly<Record<string, boolean>>;
|
|
61
|
+
const properties = schema.properties as Record<string, JsonSchemaNode>;
|
|
62
|
+
for (const [key, toggle] of Object.entries(properties)) {
|
|
63
|
+
const label = keyLabels?.[key];
|
|
64
|
+
if (label !== undefined) {
|
|
65
|
+
toggle.title = translate(label);
|
|
66
|
+
}
|
|
67
|
+
toggle.default = defaults[key];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The `x-kernel` keyword of a field: its kind and every declared hint. */
|
|
72
|
+
function kernelHints(params: { field: AnyField; translate: TranslateKey }): JsonSchemaNode {
|
|
73
|
+
const { field, translate } = params;
|
|
74
|
+
const hints: JsonSchemaNode = { kind: field.spec.kind, ...specHints(field) };
|
|
75
|
+
const { ui } = field;
|
|
76
|
+
if (ui?.group !== undefined) {
|
|
77
|
+
hints.group = ui.group;
|
|
78
|
+
}
|
|
79
|
+
if (ui?.order !== undefined) {
|
|
80
|
+
hints.order = ui.order;
|
|
81
|
+
}
|
|
82
|
+
if (field.unit !== undefined) {
|
|
83
|
+
hints.unit = translate(field.unit);
|
|
84
|
+
}
|
|
85
|
+
if (ui?.hint !== undefined) {
|
|
86
|
+
hints.hint = ui.hint;
|
|
87
|
+
}
|
|
88
|
+
if (ui?.advanced !== undefined) {
|
|
89
|
+
hints.advanced = ui.advanced;
|
|
90
|
+
}
|
|
91
|
+
return { ...hints, ...suggestionHints({ field, translate }) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Kind-specific hints: accepted channel types. */
|
|
95
|
+
function specHints(field: AnyField): JsonSchemaNode {
|
|
96
|
+
const { spec } = field;
|
|
97
|
+
if (spec.kind === "channel" && spec.types !== undefined) {
|
|
98
|
+
return { channelTypes: spec.types };
|
|
99
|
+
}
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Where a surface finds values to offer: `guild` for Discord entities,
|
|
105
|
+
* `static` for fixed choices, `dynamic` for a server-side search.
|
|
106
|
+
*/
|
|
107
|
+
function suggestionHints(params: { field: AnyField; translate: TranslateKey }): JsonSchemaNode {
|
|
108
|
+
const { spec } = params.field;
|
|
109
|
+
switch (spec.kind) {
|
|
110
|
+
case "channel":
|
|
111
|
+
case "role":
|
|
112
|
+
case "user":
|
|
113
|
+
return { suggest: "guild" };
|
|
114
|
+
case "enum":
|
|
115
|
+
return { suggest: "static" };
|
|
116
|
+
case "integer":
|
|
117
|
+
case "text":
|
|
118
|
+
return spec.suggest === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: declaredSuggestions({ suggest: spec.suggest, translate: params.translate });
|
|
121
|
+
case "color":
|
|
122
|
+
case "duration":
|
|
123
|
+
case "number":
|
|
124
|
+
case "boolean":
|
|
125
|
+
case "secret":
|
|
126
|
+
case "list":
|
|
127
|
+
case "toggles":
|
|
128
|
+
return {};
|
|
129
|
+
default:
|
|
130
|
+
return unhandledFieldKind(spec);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Static suggestions carry their translated choices; a strict search says so. */
|
|
135
|
+
function declaredSuggestions(params: {
|
|
136
|
+
suggest: Suggestions<string> | Suggestions<number>;
|
|
137
|
+
translate: TranslateKey;
|
|
138
|
+
}): JsonSchemaNode {
|
|
139
|
+
const { suggest, translate } = params;
|
|
140
|
+
if ("choices" in suggest) {
|
|
141
|
+
return {
|
|
142
|
+
suggest: "static",
|
|
143
|
+
choices: labelledChoices({ choices: suggest.choices, translate }),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return suggest.strict === true ? { suggest: "dynamic", strict: true } : { suggest: "dynamic" };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function labelledChoices(params: {
|
|
150
|
+
choices: readonly FieldChoice<unknown>[];
|
|
151
|
+
translate: TranslateKey;
|
|
152
|
+
}): JsonSchemaNode[] {
|
|
153
|
+
const { choices, translate } = params;
|
|
154
|
+
return choices.map((choice) => ({ const: choice.value, title: translate(choice.label) }));
|
|
155
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { resolveLocale, SOURCE_LOCALE } from "@/i18n/locale";
|
|
2
|
+
import type { TranslateKey, Translator } from "@/i18n/translator";
|
|
3
|
+
import type { SettingsDeclaration, SettingsGroup } from "@/settings/define-settings";
|
|
4
|
+
import { annotateField } from "@/settings/describe-field";
|
|
5
|
+
import { displayOrder } from "@/settings/field-order";
|
|
6
|
+
import { fieldsJsonSchema, type JsonSchemaNode } from "@/settings/fields/zod-schema";
|
|
7
|
+
|
|
8
|
+
/** URN prefix of a described module: `urn:discord-kernel:settings:<id>:v<version>`. */
|
|
9
|
+
const SCHEMA_ID_PREFIX = "urn:discord-kernel:settings";
|
|
10
|
+
|
|
11
|
+
/** A JSON value, as a JSON Schema document holds. */
|
|
12
|
+
export type JsonValue =
|
|
13
|
+
| string
|
|
14
|
+
| number
|
|
15
|
+
| boolean
|
|
16
|
+
| null
|
|
17
|
+
| readonly JsonValue[]
|
|
18
|
+
| { readonly [key: string]: JsonValue };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A JSON Schema draft 2020-12 document describing one module's settings, every
|
|
22
|
+
* text already translated, with kernel hints under the `x-kernel` keyword.
|
|
23
|
+
*/
|
|
24
|
+
export type SettingsSchema = { readonly [keyword: string]: JsonValue };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Describe a module's settings for surfaces other than Discord (FR-030).
|
|
28
|
+
*
|
|
29
|
+
* @param params.declaration - the module's settings declaration.
|
|
30
|
+
* @param params.locale - any locale string; an unsupported one falls back to English.
|
|
31
|
+
* @param params.translator - translates the declaration's catalog keys.
|
|
32
|
+
* @returns the translated JSON Schema document of the module.
|
|
33
|
+
*/
|
|
34
|
+
export function describeSettings(params: {
|
|
35
|
+
declaration: SettingsDeclaration;
|
|
36
|
+
locale: string;
|
|
37
|
+
translator: Translator;
|
|
38
|
+
}): SettingsSchema {
|
|
39
|
+
const { declaration, translator } = params;
|
|
40
|
+
const locale = resolveLocale([params.locale], SOURCE_LOCALE);
|
|
41
|
+
const translate: TranslateKey = (key) => translator.translate(locale, key);
|
|
42
|
+
const { $schema, ...fields } = fieldsJsonSchema({
|
|
43
|
+
fields: declaration.fields,
|
|
44
|
+
annotate: (field, schema) => annotateField({ field, schema, translate }),
|
|
45
|
+
});
|
|
46
|
+
const document: JsonSchemaNode = {
|
|
47
|
+
$schema,
|
|
48
|
+
$id: `${SCHEMA_ID_PREFIX}:${declaration.id}:v${declaration.version}`,
|
|
49
|
+
title: translate(declaration.labels.title),
|
|
50
|
+
};
|
|
51
|
+
if (declaration.labels.description !== undefined) {
|
|
52
|
+
document.description = translate(declaration.labels.description);
|
|
53
|
+
}
|
|
54
|
+
const moduleHints: JsonSchemaNode = { module: declaration.id, version: declaration.version };
|
|
55
|
+
if (declaration.ui?.icon !== undefined) {
|
|
56
|
+
moduleHints.icon = declaration.ui.icon;
|
|
57
|
+
}
|
|
58
|
+
moduleHints.locale = locale;
|
|
59
|
+
moduleHints.groups = describeGroups({ declaration, translate });
|
|
60
|
+
// Zod emits JSON only, and every annotation comes from the declaration's JSON values.
|
|
61
|
+
return { ...document, ...fields, "x-kernel": moduleHints } as SettingsSchema;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The declared groups, translated, in the display order every surface shares. */
|
|
65
|
+
function describeGroups(params: {
|
|
66
|
+
declaration: SettingsDeclaration;
|
|
67
|
+
translate: TranslateKey;
|
|
68
|
+
}): JsonSchemaNode[] {
|
|
69
|
+
const { declaration, translate } = params;
|
|
70
|
+
return displayOrder(declaration).groups.flatMap((id) => {
|
|
71
|
+
const group = declaration.groups?.[id];
|
|
72
|
+
return group === undefined ? [] : [describeGroup({ id, group, translate })];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function describeGroup(params: {
|
|
77
|
+
id: string;
|
|
78
|
+
group: SettingsGroup;
|
|
79
|
+
translate: TranslateKey;
|
|
80
|
+
}): JsonSchemaNode {
|
|
81
|
+
const { id, group, translate } = params;
|
|
82
|
+
const described: JsonSchemaNode = { id, title: translate(group.label) };
|
|
83
|
+
if (group.description !== undefined) {
|
|
84
|
+
described.description = translate(group.description);
|
|
85
|
+
}
|
|
86
|
+
if (group.order !== undefined) {
|
|
87
|
+
described.order = group.order;
|
|
88
|
+
}
|
|
89
|
+
return described;
|
|
90
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Units in their only accepted order, largest first, each with its length in
|
|
3
|
+
* seconds. Fixing the order makes every duration have one spelling.
|
|
4
|
+
*/
|
|
5
|
+
const DURATION_PATTERN = /^(?:(\d+)d)?\s*(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?$/;
|
|
6
|
+
const UNIT_SECONDS = [86_400, 3600, 60, 1] as const;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse a human duration such as `"90s"`, `"15m"`, `"2h"`, `"7d"` or `"1h30m"`
|
|
10
|
+
* into whole seconds. Units go from days to seconds, each at most once; case
|
|
11
|
+
* and whitespace between parts are ignored. Returns `undefined` for anything
|
|
12
|
+
* else, including a bare number and a total beyond the safe integer range.
|
|
13
|
+
*/
|
|
14
|
+
export function parseDuration(input: string): number | undefined {
|
|
15
|
+
const trimmed = input.trim().toLowerCase();
|
|
16
|
+
if (trimmed === "") {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const match = DURATION_PATTERN.exec(trimmed);
|
|
20
|
+
if (match === null) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
let seconds = 0;
|
|
24
|
+
for (const [index, unit] of UNIT_SECONDS.entries()) {
|
|
25
|
+
const amount = match[index + 1];
|
|
26
|
+
if (amount !== undefined) {
|
|
27
|
+
seconds += Number(amount) * unit;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Number.isSafeInteger(seconds) ? seconds : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Unit suffixes, in the order of {@link UNIT_SECONDS}. */
|
|
34
|
+
const UNIT_SUFFIXES = ["d", "h", "m", "s"] as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Write whole seconds the way {@link parseDuration} reads them back, largest
|
|
38
|
+
* unit first and zero parts left out: `5400` becomes `"1h30m"`, `0` `"0s"`.
|
|
39
|
+
*
|
|
40
|
+
* @param seconds - a non-negative whole number of seconds.
|
|
41
|
+
* @returns the shortest spelling `parseDuration` turns back into `seconds`.
|
|
42
|
+
*/
|
|
43
|
+
export function formatDuration(seconds: number): string {
|
|
44
|
+
let remaining = seconds;
|
|
45
|
+
let text = "";
|
|
46
|
+
for (const [index, unit] of UNIT_SECONDS.entries()) {
|
|
47
|
+
const amount = Math.floor(remaining / unit);
|
|
48
|
+
remaining -= amount * unit;
|
|
49
|
+
if (amount > 0) {
|
|
50
|
+
text += `${amount}${UNIT_SUFFIXES[index]}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return text === "" ? "0s" : text;
|
|
54
|
+
}
|