@karibsen/eponyme 0.1.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/LICENSE +21 -0
- package/README.md +609 -0
- package/dist/eponyme.d.mts +15 -0
- package/dist/eponyme.mjs +38 -0
- package/dist/module.d.mts +26 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +154 -0
- package/dist/runtime/assets/dashboard.css +1 -0
- package/dist/runtime/components/editor/EponymeCollectionPage.d.vue.ts +9 -0
- package/dist/runtime/components/editor/EponymeCollectionPage.vue +238 -0
- package/dist/runtime/components/editor/EponymeCollectionPage.vue.d.ts +9 -0
- package/dist/runtime/components/editor/EponymeCopy.d.vue.ts +7 -0
- package/dist/runtime/components/editor/EponymeCopy.vue +28 -0
- package/dist/runtime/components/editor/EponymeCopy.vue.d.ts +7 -0
- package/dist/runtime/components/editor/EponymeFolderNav.d.vue.ts +7 -0
- package/dist/runtime/components/editor/EponymeFolderNav.vue +146 -0
- package/dist/runtime/components/editor/EponymeFolderNav.vue.d.ts +7 -0
- package/dist/runtime/components/editor/EponymeFolderPage.d.vue.ts +7 -0
- package/dist/runtime/components/editor/EponymeFolderPage.vue +62 -0
- package/dist/runtime/components/editor/EponymeFolderPage.vue.d.ts +7 -0
- package/dist/runtime/components/editor/EponymeForm.d.vue.ts +9 -0
- package/dist/runtime/components/editor/EponymeForm.vue +309 -0
- package/dist/runtime/components/editor/EponymeForm.vue.d.ts +9 -0
- package/dist/runtime/components/editor/EponymeFormPage.d.vue.ts +8 -0
- package/dist/runtime/components/editor/EponymeFormPage.vue +288 -0
- package/dist/runtime/components/editor/EponymeFormPage.vue.d.ts +8 -0
- package/dist/runtime/components/editor/EponymeHistoryDialog.d.vue.ts +14 -0
- package/dist/runtime/components/editor/EponymeHistoryDialog.vue +97 -0
- package/dist/runtime/components/editor/EponymeHistoryDialog.vue.d.ts +14 -0
- package/dist/runtime/components/editor/EponymeNavigationLink.d.vue.ts +14 -0
- package/dist/runtime/components/editor/EponymeNavigationLink.vue +80 -0
- package/dist/runtime/components/editor/EponymeNavigationLink.vue.d.ts +14 -0
- package/dist/runtime/components/editor/EponymePageNavigation.d.vue.ts +8 -0
- package/dist/runtime/components/editor/EponymePageNavigation.vue +58 -0
- package/dist/runtime/components/editor/EponymePageNavigation.vue.d.ts +8 -0
- package/dist/runtime/components/editor/EponymePreviewPanel.d.vue.ts +16 -0
- package/dist/runtime/components/editor/EponymePreviewPanel.vue +100 -0
- package/dist/runtime/components/editor/EponymePreviewPanel.vue.d.ts +16 -0
- package/dist/runtime/components/editor/EponymeSidebar.d.vue.ts +6 -0
- package/dist/runtime/components/editor/EponymeSidebar.vue +244 -0
- package/dist/runtime/components/editor/EponymeSidebar.vue.d.ts +6 -0
- package/dist/runtime/components/editor/EponymeSidebarTree.d.vue.ts +18 -0
- package/dist/runtime/components/editor/EponymeSidebarTree.vue +84 -0
- package/dist/runtime/components/editor/EponymeSidebarTree.vue.d.ts +18 -0
- package/dist/runtime/components/fields/ArrayField.d.vue.ts +19 -0
- package/dist/runtime/components/fields/ArrayField.vue +198 -0
- package/dist/runtime/components/fields/ArrayField.vue.d.ts +19 -0
- package/dist/runtime/components/fields/BooleanField.d.vue.ts +16 -0
- package/dist/runtime/components/fields/BooleanField.vue +36 -0
- package/dist/runtime/components/fields/BooleanField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/CheckboxGroupField.d.vue.ts +20 -0
- package/dist/runtime/components/fields/CheckboxGroupField.vue +52 -0
- package/dist/runtime/components/fields/CheckboxGroupField.vue.d.ts +20 -0
- package/dist/runtime/components/fields/ColorField.d.vue.ts +19 -0
- package/dist/runtime/components/fields/ColorField.vue +39 -0
- package/dist/runtime/components/fields/ColorField.vue.d.ts +19 -0
- package/dist/runtime/components/fields/DateField.d.vue.ts +18 -0
- package/dist/runtime/components/fields/DateField.vue +39 -0
- package/dist/runtime/components/fields/DateField.vue.d.ts +18 -0
- package/dist/runtime/components/fields/FieldRenderer.d.vue.ts +24 -0
- package/dist/runtime/components/fields/FieldRenderer.vue +135 -0
- package/dist/runtime/components/fields/FieldRenderer.vue.d.ts +24 -0
- package/dist/runtime/components/fields/NumberField.d.vue.ts +20 -0
- package/dist/runtime/components/fields/NumberField.vue +58 -0
- package/dist/runtime/components/fields/NumberField.vue.d.ts +20 -0
- package/dist/runtime/components/fields/RadioField.d.vue.ts +20 -0
- package/dist/runtime/components/fields/RadioField.vue +44 -0
- package/dist/runtime/components/fields/RadioField.vue.d.ts +20 -0
- package/dist/runtime/components/fields/RichTextField.d.vue.ts +20 -0
- package/dist/runtime/components/fields/RichTextField.vue +176 -0
- package/dist/runtime/components/fields/RichTextField.vue.d.ts +20 -0
- package/dist/runtime/components/fields/SectionField.d.vue.ts +20 -0
- package/dist/runtime/components/fields/SectionField.vue +127 -0
- package/dist/runtime/components/fields/SectionField.vue.d.ts +20 -0
- package/dist/runtime/components/fields/SelectField.d.vue.ts +21 -0
- package/dist/runtime/components/fields/SelectField.vue +42 -0
- package/dist/runtime/components/fields/SelectField.vue.d.ts +21 -0
- package/dist/runtime/components/fields/SlugField.d.vue.ts +20 -0
- package/dist/runtime/components/fields/SlugField.vue +35 -0
- package/dist/runtime/components/fields/SlugField.vue.d.ts +20 -0
- package/dist/runtime/components/fields/TabsField.d.vue.ts +19 -0
- package/dist/runtime/components/fields/TabsField.vue +109 -0
- package/dist/runtime/components/fields/TabsField.vue.d.ts +19 -0
- package/dist/runtime/components/fields/TextField.d.vue.ts +22 -0
- package/dist/runtime/components/fields/TextField.vue +56 -0
- package/dist/runtime/components/fields/TextField.vue.d.ts +22 -0
- package/dist/runtime/components/fields/TextareaField.d.vue.ts +20 -0
- package/dist/runtime/components/fields/TextareaField.vue +47 -0
- package/dist/runtime/components/fields/TextareaField.vue.d.ts +20 -0
- package/dist/runtime/components/fields/UrlField.d.vue.ts +17 -0
- package/dist/runtime/components/fields/UrlField.vue +35 -0
- package/dist/runtime/components/fields/UrlField.vue.d.ts +17 -0
- package/dist/runtime/components/ui/EPAccordion.d.vue.ts +26 -0
- package/dist/runtime/components/ui/EPAccordion.vue +64 -0
- package/dist/runtime/components/ui/EPAccordion.vue.d.ts +26 -0
- package/dist/runtime/components/ui/EPAvatar.d.vue.ts +9 -0
- package/dist/runtime/components/ui/EPAvatar.vue +25 -0
- package/dist/runtime/components/ui/EPAvatar.vue.d.ts +9 -0
- package/dist/runtime/components/ui/EPBadge.d.vue.ts +16 -0
- package/dist/runtime/components/ui/EPBadge.vue +17 -0
- package/dist/runtime/components/ui/EPBadge.vue.d.ts +16 -0
- package/dist/runtime/components/ui/EPButton.d.vue.ts +22 -0
- package/dist/runtime/components/ui/EPButton.vue +55 -0
- package/dist/runtime/components/ui/EPButton.vue.d.ts +22 -0
- package/dist/runtime/components/ui/EPCheckbox.d.vue.ts +11 -0
- package/dist/runtime/components/ui/EPCheckbox.vue +21 -0
- package/dist/runtime/components/ui/EPCheckbox.vue.d.ts +11 -0
- package/dist/runtime/components/ui/EPColorPicker.d.vue.ts +19 -0
- package/dist/runtime/components/ui/EPColorPicker.vue +63 -0
- package/dist/runtime/components/ui/EPColorPicker.vue.d.ts +19 -0
- package/dist/runtime/components/ui/EPDialog.d.vue.ts +24 -0
- package/dist/runtime/components/ui/EPDialog.vue +54 -0
- package/dist/runtime/components/ui/EPDialog.vue.d.ts +24 -0
- package/dist/runtime/components/ui/EPDropdownMenu.d.vue.ts +25 -0
- package/dist/runtime/components/ui/EPDropdownMenu.vue +40 -0
- package/dist/runtime/components/ui/EPDropdownMenu.vue.d.ts +25 -0
- package/dist/runtime/components/ui/EPFormField.d.vue.ts +24 -0
- package/dist/runtime/components/ui/EPFormField.vue +62 -0
- package/dist/runtime/components/ui/EPFormField.vue.d.ts +24 -0
- package/dist/runtime/components/ui/EPInputText.d.vue.ts +17 -0
- package/dist/runtime/components/ui/EPInputText.vue +33 -0
- package/dist/runtime/components/ui/EPInputText.vue.d.ts +17 -0
- package/dist/runtime/components/ui/EPLinkEditor.d.vue.ts +16 -0
- package/dist/runtime/components/ui/EPLinkEditor.vue +169 -0
- package/dist/runtime/components/ui/EPLinkEditor.vue.d.ts +16 -0
- package/dist/runtime/components/ui/EPRadioButton.d.vue.ts +16 -0
- package/dist/runtime/components/ui/EPRadioButton.vue +34 -0
- package/dist/runtime/components/ui/EPRadioButton.vue.d.ts +16 -0
- package/dist/runtime/components/ui/EPSelect.d.vue.ts +21 -0
- package/dist/runtime/components/ui/EPSelect.vue +73 -0
- package/dist/runtime/components/ui/EPSelect.vue.d.ts +21 -0
- package/dist/runtime/components/ui/EPSlider.d.vue.ts +16 -0
- package/dist/runtime/components/ui/EPSlider.vue +51 -0
- package/dist/runtime/components/ui/EPSlider.vue.d.ts +16 -0
- package/dist/runtime/components/ui/EPSwitch.d.vue.ts +11 -0
- package/dist/runtime/components/ui/EPSwitch.vue +23 -0
- package/dist/runtime/components/ui/EPSwitch.vue.d.ts +11 -0
- package/dist/runtime/components/ui/EPTextarea.d.vue.ts +12 -0
- package/dist/runtime/components/ui/EPTextarea.vue +18 -0
- package/dist/runtime/components/ui/EPTextarea.vue.d.ts +12 -0
- package/dist/runtime/components/ui/EPToast.d.vue.ts +13 -0
- package/dist/runtime/components/ui/EPToast.vue +60 -0
- package/dist/runtime/components/ui/EPToast.vue.d.ts +13 -0
- package/dist/runtime/components/ui/EPTooltip.d.vue.ts +17 -0
- package/dist/runtime/components/ui/EPTooltip.vue +34 -0
- package/dist/runtime/components/ui/EPTooltip.vue.d.ts +17 -0
- package/dist/runtime/components/ui/form-field-context.d.ts +12 -0
- package/dist/runtime/components/ui/form-field-context.js +1 -0
- package/dist/runtime/composables/useEponyme.d.ts +24 -0
- package/dist/runtime/composables/useEponyme.js +63 -0
- package/dist/runtime/composables/useEponymeAuth.d.ts +16 -0
- package/dist/runtime/composables/useEponymeAuth.js +81 -0
- package/dist/runtime/composables/useEponymeCollection.d.ts +27 -0
- package/dist/runtime/composables/useEponymeCollection.js +44 -0
- package/dist/runtime/composables/useEponymeColorPresets.d.ts +5 -0
- package/dist/runtime/composables/useEponymeColorPresets.js +16 -0
- package/dist/runtime/composables/useEponymeConfig.d.ts +1 -0
- package/dist/runtime/composables/useEponymeConfig.js +4 -0
- package/dist/runtime/composables/useEponymeForm.d.ts +35 -0
- package/dist/runtime/composables/useEponymeForm.js +75 -0
- package/dist/runtime/composables/useEponymeHistory.d.ts +26 -0
- package/dist/runtime/composables/useEponymeHistory.js +24 -0
- package/dist/runtime/composables/useEponymeValidation.d.ts +21 -0
- package/dist/runtime/composables/useEponymeValidation.js +34 -0
- package/dist/runtime/fields/array.d.ts +2 -0
- package/dist/runtime/fields/array.js +3 -0
- package/dist/runtime/fields/boolean.d.ts +2 -0
- package/dist/runtime/fields/boolean.js +3 -0
- package/dist/runtime/fields/checkbox-group.d.ts +2 -0
- package/dist/runtime/fields/checkbox-group.js +3 -0
- package/dist/runtime/fields/color.d.ts +2 -0
- package/dist/runtime/fields/color.js +3 -0
- package/dist/runtime/fields/date.d.ts +3 -0
- package/dist/runtime/fields/date.js +6 -0
- package/dist/runtime/fields/email.d.ts +2 -0
- package/dist/runtime/fields/email.js +3 -0
- package/dist/runtime/fields/image.d.ts +2 -0
- package/dist/runtime/fields/image.js +3 -0
- package/dist/runtime/fields/index.d.ts +37 -0
- package/dist/runtime/fields/index.js +19 -0
- package/dist/runtime/fields/number.d.ts +2 -0
- package/dist/runtime/fields/number.js +3 -0
- package/dist/runtime/fields/radio.d.ts +2 -0
- package/dist/runtime/fields/radio.js +3 -0
- package/dist/runtime/fields/rich-text.d.ts +2 -0
- package/dist/runtime/fields/rich-text.js +3 -0
- package/dist/runtime/fields/section.d.ts +2 -0
- package/dist/runtime/fields/section.js +3 -0
- package/dist/runtime/fields/select.d.ts +2 -0
- package/dist/runtime/fields/select.js +3 -0
- package/dist/runtime/fields/slug.d.ts +2 -0
- package/dist/runtime/fields/slug.js +3 -0
- package/dist/runtime/fields/string.d.ts +2 -0
- package/dist/runtime/fields/string.js +3 -0
- package/dist/runtime/fields/tab.d.ts +2 -0
- package/dist/runtime/fields/tab.js +3 -0
- package/dist/runtime/fields/textarea.d.ts +2 -0
- package/dist/runtime/fields/textarea.js +3 -0
- package/dist/runtime/fields/url.d.ts +2 -0
- package/dist/runtime/fields/url.js +3 -0
- package/dist/runtime/middleware/eponyme-auth.d.ts +2 -0
- package/dist/runtime/middleware/eponyme-auth.js +13 -0
- package/dist/runtime/middleware/eponyme-owner.d.ts +2 -0
- package/dist/runtime/middleware/eponyme-owner.js +7 -0
- package/dist/runtime/pages/EponymeChangePasswordPage.d.vue.ts +3 -0
- package/dist/runtime/pages/EponymeChangePasswordPage.vue +118 -0
- package/dist/runtime/pages/EponymeChangePasswordPage.vue.d.ts +3 -0
- package/dist/runtime/pages/EponymeDetailPage.d.vue.ts +3 -0
- package/dist/runtime/pages/EponymeDetailPage.vue +83 -0
- package/dist/runtime/pages/EponymeDetailPage.vue.d.ts +3 -0
- package/dist/runtime/pages/EponymeIndexPage.d.vue.ts +3 -0
- package/dist/runtime/pages/EponymeIndexPage.vue +51 -0
- package/dist/runtime/pages/EponymeIndexPage.vue.d.ts +3 -0
- package/dist/runtime/pages/EponymeLoginPage.d.vue.ts +3 -0
- package/dist/runtime/pages/EponymeLoginPage.vue +102 -0
- package/dist/runtime/pages/EponymeLoginPage.vue.d.ts +3 -0
- package/dist/runtime/pages/EponymeUsersPage.d.vue.ts +3 -0
- package/dist/runtime/pages/EponymeUsersPage.vue +261 -0
- package/dist/runtime/pages/EponymeUsersPage.vue.d.ts +3 -0
- package/dist/runtime/server/api/eponyme/[name].get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme/[name].get.js +20 -0
- package/dist/runtime/server/api/eponyme/[name].patch.d.ts +11 -0
- package/dist/runtime/server/api/eponyme/[name].patch.js +20 -0
- package/dist/runtime/server/api/eponyme-auth/change-password.post.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-auth/change-password.post.js +21 -0
- package/dist/runtime/server/api/eponyme-auth/login.post.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-auth/login.post.js +12 -0
- package/dist/runtime/server/api/eponyme-auth/logout.post.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-auth/logout.post.js +13 -0
- package/dist/runtime/server/api/eponyme-auth/session.get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-auth/session.get.js +5 -0
- package/dist/runtime/server/api/eponyme-collections/[path].delete.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-collections/[path].delete.js +11 -0
- package/dist/runtime/server/api/eponyme-collections/[path].get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-collections/[path].get.js +11 -0
- package/dist/runtime/server/api/eponyme-collections/[path].post.d.ts +7 -0
- package/dist/runtime/server/api/eponyme-collections/[path].post.js +16 -0
- package/dist/runtime/server/api/eponyme-forms/[path].delete.d.ts +6 -0
- package/dist/runtime/server/api/eponyme-forms/[path].delete.js +19 -0
- package/dist/runtime/server/api/eponyme-forms/[path].get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-forms/[path].get.js +22 -0
- package/dist/runtime/server/api/eponyme-forms/[path].post.d.ts +19 -0
- package/dist/runtime/server/api/eponyme-forms/[path].post.js +36 -0
- package/dist/runtime/server/api/eponyme-history/[path].get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-history/[path].get.js +11 -0
- package/dist/runtime/server/api/eponyme-history/[path].patch.d.ts +2 -0
- package/dist/runtime/server/api/eponyme-history/[path].patch.js +18 -0
- package/dist/runtime/server/api/eponyme-sitemap.get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-sitemap.get.js +5 -0
- package/dist/runtime/server/api/eponyme-statuses.get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-statuses.get.js +7 -0
- package/dist/runtime/server/api/eponyme-users/[id]/reset-password.post.d.ts +5 -0
- package/dist/runtime/server/api/eponyme-users/[id]/reset-password.post.js +12 -0
- package/dist/runtime/server/api/eponyme-users/[id].patch.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-users/[id].patch.js +14 -0
- package/dist/runtime/server/api/eponyme-users/index.get.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-users/index.get.js +7 -0
- package/dist/runtime/server/api/eponyme-users/index.post.d.ts +5 -0
- package/dist/runtime/server/api/eponyme-users/index.post.js +13 -0
- package/dist/runtime/server/plugins/eponyme-sync.d.ts +2 -0
- package/dist/runtime/server/plugins/eponyme-sync.js +14 -0
- package/dist/runtime/server/services/eponyme-auth-service.d.ts +2 -0
- package/dist/runtime/server/services/eponyme-auth-service.js +15 -0
- package/dist/runtime/server/services/eponyme-auth-store.d.ts +119 -0
- package/dist/runtime/server/services/eponyme-auth-store.js +227 -0
- package/dist/runtime/server/services/eponyme-form-service.d.ts +3 -0
- package/dist/runtime/server/services/eponyme-form-service.js +8 -0
- package/dist/runtime/server/services/eponyme-form-store.d.ts +100 -0
- package/dist/runtime/server/services/eponyme-form-store.js +109 -0
- package/dist/runtime/server/services/eponyme-service.d.ts +3 -0
- package/dist/runtime/server/services/eponyme-service.js +8 -0
- package/dist/runtime/server/services/eponyme-store.d.ts +202 -0
- package/dist/runtime/server/services/eponyme-store.js +338 -0
- package/dist/runtime/server/utils/auth.d.ts +11 -0
- package/dist/runtime/server/utils/auth.js +41 -0
- package/dist/runtime/server/utils/eponyme-form.d.ts +14 -0
- package/dist/runtime/server/utils/eponyme-form.js +6 -0
- package/dist/runtime/server/utils/eponyme-sitemap.d.ts +7 -0
- package/dist/runtime/server/utils/eponyme-sitemap.js +7 -0
- package/dist/runtime/server/utils/form-route.d.ts +14 -0
- package/dist/runtime/server/utils/form-route.js +14 -0
- package/dist/runtime/server/utils/password.d.ts +4 -0
- package/dist/runtime/server/utils/password.js +30 -0
- package/dist/runtime/types/auth.d.ts +20 -0
- package/dist/runtime/types/auth.js +7 -0
- package/dist/runtime/types/eponyme-config.d.ts +11 -0
- package/dist/runtime/types/eponyme-navigation.d.ts +6 -0
- package/dist/runtime/types/eponyme-navigation.js +0 -0
- package/dist/runtime/types/eponyme.d.ts +87 -0
- package/dist/runtime/types/eponyme.js +0 -0
- package/dist/runtime/types/field.d.ts +203 -0
- package/dist/runtime/types/field.js +0 -0
- package/dist/runtime/types/index.d.ts +3 -0
- package/dist/runtime/types/index.js +3 -0
- package/dist/runtime/types/nuxt.d.ts +11 -0
- package/dist/runtime/utils/as-record.d.ts +1 -0
- package/dist/runtime/utils/as-record.js +3 -0
- package/dist/runtime/utils/build-eponyme-schema.d.ts +22 -0
- package/dist/runtime/utils/build-eponyme-schema.js +42 -0
- package/dist/runtime/utils/changed-paths.d.ts +6 -0
- package/dist/runtime/utils/changed-paths.js +25 -0
- package/dist/runtime/utils/create-default-eponyme-data.d.ts +5 -0
- package/dist/runtime/utils/create-default-eponyme-data.js +12 -0
- package/dist/runtime/utils/field-path.d.ts +14 -0
- package/dist/runtime/utils/field-path.js +23 -0
- package/dist/runtime/utils/get-eponyme-schemas.d.ts +7 -0
- package/dist/runtime/utils/get-eponyme-schemas.js +33 -0
- package/dist/runtime/utils/get-field-default-value.d.ts +4 -0
- package/dist/runtime/utils/get-field-default-value.js +56 -0
- package/dist/runtime/utils/humanize-label.d.ts +6 -0
- package/dist/runtime/utils/humanize-label.js +3 -0
- package/dist/runtime/utils/initials.d.ts +1 -0
- package/dist/runtime/utils/initials.js +3 -0
- package/dist/runtime/utils/is-field-visible.d.ts +2 -0
- package/dist/runtime/utils/is-field-visible.js +14 -0
- package/dist/runtime/utils/normalize-hex-color.d.ts +8 -0
- package/dist/runtime/utils/normalize-hex-color.js +11 -0
- package/dist/runtime/utils/normalize-slug.d.ts +1 -0
- package/dist/runtime/utils/normalize-slug.js +4 -0
- package/dist/runtime/utils/preview.d.ts +30 -0
- package/dist/runtime/utils/preview.js +30 -0
- package/dist/runtime/utils/validate-eponyme-data.d.ts +10 -0
- package/dist/runtime/utils/validate-eponyme-data.js +204 -0
- package/dist/types.d.mts +11 -0
- package/package.json +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Karibsen Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
# Eponyme
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![License][license-src]][license-href]
|
|
6
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
+
|
|
8
|
+
A type-safe content manager made for Nuxt.
|
|
9
|
+
|
|
10
|
+
Define your content in `eponyme.config.ts`. Eponyme provides defaults, validation, PostgreSQL persistence, a server API, and a generated dashboard.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Type-safe fields for text, slugs, rich text, numbers, booleans, images, links, dates, colors, sections, tabs, and arrays
|
|
15
|
+
- Declarative defaults and validation
|
|
16
|
+
- Conditional fields, character counters, and sortable arrays
|
|
17
|
+
- Private drafts with explicit publishing
|
|
18
|
+
- Collections for articles, pages, and other repeatable content
|
|
19
|
+
- Public forms with typed schemas, server-side validation, and stored submissions
|
|
20
|
+
- Persistent version history with dashboard restoration
|
|
21
|
+
- Draft previews for configured public routes
|
|
22
|
+
- A general sitemap metadata endpoint for configured public routes
|
|
23
|
+
- A generated dashboard at `/__eponyme`
|
|
24
|
+
- Server sessions with `owner`, `editor`, and `viewer` roles
|
|
25
|
+
- PostgreSQL and Prisma persistence using JSONB
|
|
26
|
+
- Typed composables for entries and collections
|
|
27
|
+
- Reusable `EP*` interface primitives
|
|
28
|
+
|
|
29
|
+
Runtime components follow three clear layers:
|
|
30
|
+
|
|
31
|
+
- `components/ui` contains reusable `EP*` primitives.
|
|
32
|
+
- `components/fields` turns those primitives into complete fields.
|
|
33
|
+
- `components/editor` owns the dashboard, navigation, and editing workflows.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Install the module:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add @karibsen/eponyme
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Add it to `nuxt.config.ts`:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
export default defineNuxtConfig({
|
|
47
|
+
modules: ['@karibsen/eponyme'],
|
|
48
|
+
eponyme: {
|
|
49
|
+
prismaClient: '~/server/utils/prisma',
|
|
50
|
+
dashboardPath: '/__eponyme',
|
|
51
|
+
previewPaths: {
|
|
52
|
+
homepage: '/',
|
|
53
|
+
articles: '/articles/:slug',
|
|
54
|
+
},
|
|
55
|
+
auth: {
|
|
56
|
+
sessionDurationDays: 7,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Create `eponyme.config.ts` at the root of the application:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
export default defineEponymeConfig({
|
|
66
|
+
homepage: {
|
|
67
|
+
title: field.string({
|
|
68
|
+
label: 'Title',
|
|
69
|
+
required: true,
|
|
70
|
+
defaultValue: 'Welcome',
|
|
71
|
+
}),
|
|
72
|
+
subtitle: field.textarea({
|
|
73
|
+
label: 'Introduction',
|
|
74
|
+
maxLength: 160,
|
|
75
|
+
visibleWhen: { field: 'published', equals: true },
|
|
76
|
+
}),
|
|
77
|
+
body: field.richText({
|
|
78
|
+
label: 'Content',
|
|
79
|
+
placeholder: 'Write your story…',
|
|
80
|
+
required: true,
|
|
81
|
+
}),
|
|
82
|
+
contactEmail: field.email({
|
|
83
|
+
label: 'Contact email',
|
|
84
|
+
defaultValue: 'hello@example.com',
|
|
85
|
+
}),
|
|
86
|
+
callToAction: field.url({
|
|
87
|
+
label: 'Call to action',
|
|
88
|
+
defaultValue: {
|
|
89
|
+
href: '/contact',
|
|
90
|
+
type: 'internal',
|
|
91
|
+
openInNewTab: false,
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
published: field.boolean({
|
|
95
|
+
label: 'Published',
|
|
96
|
+
}),
|
|
97
|
+
launchDate: field.date({
|
|
98
|
+
label: 'Launch date',
|
|
99
|
+
defaultValue: today(),
|
|
100
|
+
}),
|
|
101
|
+
accentColor: field.color({
|
|
102
|
+
label: 'Accent color',
|
|
103
|
+
defaultValue: '#171714',
|
|
104
|
+
}),
|
|
105
|
+
tags: field.array({
|
|
106
|
+
label: 'Tags',
|
|
107
|
+
of: field.string({ required: true }),
|
|
108
|
+
defaultValue: ['nuxt', 'content'],
|
|
109
|
+
minItems: 1,
|
|
110
|
+
maxItems: 5,
|
|
111
|
+
addLabel: 'Add tag',
|
|
112
|
+
itemLabel: 'Tag $i',
|
|
113
|
+
}),
|
|
114
|
+
hero: field.section({
|
|
115
|
+
label: 'Hero',
|
|
116
|
+
fields: {
|
|
117
|
+
title: field.string({ label: 'Title', required: true }),
|
|
118
|
+
introduction: field.textarea({ label: 'Introduction' }),
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
metadata: field.tab({
|
|
122
|
+
label: 'Metadata',
|
|
123
|
+
tabs: {
|
|
124
|
+
seo: {
|
|
125
|
+
label: 'SEO',
|
|
126
|
+
fields: {
|
|
127
|
+
title: field.string({ maxLength: 60 }),
|
|
128
|
+
description: field.textarea({ maxLength: 160 }),
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
social: {
|
|
132
|
+
label: 'Social',
|
|
133
|
+
fields: {
|
|
134
|
+
title: field.string({ maxLength: 70 }),
|
|
135
|
+
image: field.image(),
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Every field accepts a synchronous custom validator:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
field.string({
|
|
148
|
+
validate: (value, data) => value !== data.slug || 'The title must be different from the slug.',
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Prisma
|
|
153
|
+
|
|
154
|
+
Eponyme uses the Prisma client owned by your application. It does not create the connection or run migrations.
|
|
155
|
+
|
|
156
|
+
Export an initialized client from the path configured in `nuxt.config.ts`:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// server/utils/prisma.ts
|
|
160
|
+
import { PrismaClient } from '@prisma/client'
|
|
161
|
+
|
|
162
|
+
const prisma = new PrismaClient()
|
|
163
|
+
|
|
164
|
+
export default prisma
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Add the required models to your Prisma schema:
|
|
168
|
+
|
|
169
|
+
```prisma
|
|
170
|
+
model Eponyme {
|
|
171
|
+
name String @id
|
|
172
|
+
data Json @db.JsonB
|
|
173
|
+
createdAt DateTime @default(now())
|
|
174
|
+
updatedAt DateTime @updatedAt
|
|
175
|
+
versions EponymeVersion[]
|
|
176
|
+
|
|
177
|
+
@@map("eponyme_entries")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
model EponymeVersion {
|
|
181
|
+
id Int @id @default(autoincrement())
|
|
182
|
+
entryName String
|
|
183
|
+
data Json @db.JsonB
|
|
184
|
+
action String
|
|
185
|
+
status String
|
|
186
|
+
createdAt DateTime @default(now())
|
|
187
|
+
userId String?
|
|
188
|
+
entry Eponyme @relation(fields: [entryName], references: [name], onDelete: Cascade)
|
|
189
|
+
user EponymeUser? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
190
|
+
|
|
191
|
+
@@index([entryName, createdAt])
|
|
192
|
+
@@index([userId])
|
|
193
|
+
@@map("eponyme_versions")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
model EponymeUser {
|
|
197
|
+
id String @id
|
|
198
|
+
username String
|
|
199
|
+
usernameNormalized String @unique
|
|
200
|
+
passwordHash String
|
|
201
|
+
role String
|
|
202
|
+
active Boolean @default(true)
|
|
203
|
+
mustChangePassword Boolean @default(true)
|
|
204
|
+
failedLoginAttempts Int @default(0)
|
|
205
|
+
lockedUntil DateTime?
|
|
206
|
+
createdAt DateTime @default(now())
|
|
207
|
+
updatedAt DateTime @updatedAt
|
|
208
|
+
sessions EponymeUserSession[]
|
|
209
|
+
versions EponymeVersion[]
|
|
210
|
+
|
|
211
|
+
@@index([role, active])
|
|
212
|
+
@@map("eponyme_users")
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
model EponymeUserSession {
|
|
216
|
+
id String @id
|
|
217
|
+
tokenHash String @unique
|
|
218
|
+
userId String
|
|
219
|
+
expiresAt DateTime
|
|
220
|
+
createdAt DateTime @default(now())
|
|
221
|
+
user EponymeUser @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
222
|
+
|
|
223
|
+
@@index([userId])
|
|
224
|
+
@@index([expiresAt])
|
|
225
|
+
@@map("eponyme_user_sessions")
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
model EponymeFormSubmission {
|
|
229
|
+
id String @id
|
|
230
|
+
formName String
|
|
231
|
+
data Json @db.JsonB
|
|
232
|
+
createdAt DateTime @default(now())
|
|
233
|
+
|
|
234
|
+
@@index([formName, createdAt])
|
|
235
|
+
@@map("eponyme_form_submissions")
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Run the migration with the workflow used by your application.
|
|
240
|
+
|
|
241
|
+
The playground includes the same models in `playground/prisma/schema.prisma`. Copy `playground/.env.example` to `playground/.env`, set `DATABASE_URL`, then run:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pnpm --dir playground prisma:migrate
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Dashboard and authentication
|
|
248
|
+
|
|
249
|
+
The dashboard is available at `/__eponyme` by default. Set `dashboardPath` to use another route.
|
|
250
|
+
|
|
251
|
+
On the first server start, Eponyme creates an `EponymeOwner` account when no user exists. The temporary password is printed once in the server console. The owner must replace it before accessing the dashboard.
|
|
252
|
+
|
|
253
|
+
Roles are intentionally small:
|
|
254
|
+
|
|
255
|
+
- `owner` manages content and users.
|
|
256
|
+
- `editor` manages content.
|
|
257
|
+
- `viewer` has read-only dashboard access.
|
|
258
|
+
|
|
259
|
+
## Collections
|
|
260
|
+
|
|
261
|
+
Use `collection()` for repeatable content:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
export default defineEponymeConfig({
|
|
265
|
+
articles: collection({
|
|
266
|
+
label: 'Articles',
|
|
267
|
+
description: 'Stories published on the site.',
|
|
268
|
+
titleField: 'title',
|
|
269
|
+
slugField: 'slug',
|
|
270
|
+
fields: {
|
|
271
|
+
title: field.string({
|
|
272
|
+
label: 'Title',
|
|
273
|
+
required: true,
|
|
274
|
+
}),
|
|
275
|
+
slug: field.slug({
|
|
276
|
+
label: 'Slug',
|
|
277
|
+
required: true,
|
|
278
|
+
}),
|
|
279
|
+
excerpt: field.textarea({
|
|
280
|
+
label: 'Excerpt',
|
|
281
|
+
maxLength: 220,
|
|
282
|
+
}),
|
|
283
|
+
cover: field.image({
|
|
284
|
+
label: 'Cover',
|
|
285
|
+
}),
|
|
286
|
+
body: field.richText({
|
|
287
|
+
label: 'Content',
|
|
288
|
+
required: true,
|
|
289
|
+
}),
|
|
290
|
+
},
|
|
291
|
+
}),
|
|
292
|
+
})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Each collection entry has its own draft, publication state, and version history. Eponyme generates the slug when an entry is created, then keeps it immutable to preserve public URLs.
|
|
296
|
+
|
|
297
|
+
Read published collection content with the typed composables:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
const { entries } = useEponymeCollection('articles')
|
|
301
|
+
const { data: response } = useEponymeCollectionEntry('articles', 'my-article')
|
|
302
|
+
|
|
303
|
+
// entries.value contains published entries only.
|
|
304
|
+
// response.value?.data contains the typed article.
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The public collection endpoint is also available:
|
|
308
|
+
|
|
309
|
+
```http
|
|
310
|
+
GET /api/eponyme-collections/articles
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
It returns published entries by default. Pass `version=draft` from an authenticated request to read dashboard content.
|
|
314
|
+
|
|
315
|
+
## Entries
|
|
316
|
+
|
|
317
|
+
`useEponyme()` is auto-imported and infers its data from `eponyme.config.ts`:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const {
|
|
321
|
+
data,
|
|
322
|
+
pending,
|
|
323
|
+
error,
|
|
324
|
+
errors,
|
|
325
|
+
refresh,
|
|
326
|
+
save,
|
|
327
|
+
} = useEponyme('homepage')
|
|
328
|
+
|
|
329
|
+
await save()
|
|
330
|
+
await save({ title: 'A new title' })
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Public reads use the published version. Request the draft explicitly when building an editing workflow:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
const { data, status, save } = useEponyme('homepage', {
|
|
337
|
+
version: 'draft',
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
await save({ title: 'Work in progress' }, 'draft')
|
|
341
|
+
await save(data.value, 'publish')
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The dashboard uses explicit saves. `Ctrl+S` or `Cmd+S` stores a draft. Publish replaces the public version.
|
|
345
|
+
|
|
346
|
+
The `errors` ref contains field errors returned with HTTP `422`. Eponyme creates missing singleton rows from configured defaults. It also reconciles stored JSONB data when fields are added or removed from the schema.
|
|
347
|
+
|
|
348
|
+
## Preview
|
|
349
|
+
|
|
350
|
+
Map a configured entry to its public route:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
export default defineNuxtConfig({
|
|
354
|
+
eponyme: {
|
|
355
|
+
prismaClient: '~/server/utils/prisma',
|
|
356
|
+
previewPaths: {
|
|
357
|
+
'homepage': '/',
|
|
358
|
+
'pages/contact': '/contact',
|
|
359
|
+
'articles': '/articles/:slug',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
A singleton is keyed by its exact entry name. A collection is keyed by the collection
|
|
366
|
+
name, and its value must contain a `:slug` placeholder. An entry named
|
|
367
|
+
`articles/my-article` then previews at `/articles/my-article`. The same mapping drives
|
|
368
|
+
the sitemap, so a public route is declared once.
|
|
369
|
+
|
|
370
|
+
The dashboard adds preview parameters to that route. `useEponyme()` and
|
|
371
|
+
`useEponymeCollectionEntry()` detect them and load the selected draft or historical
|
|
372
|
+
version. Unpublished content requires an authenticated Eponyme session, so an
|
|
373
|
+
anonymous visitor who guesses a preview URL still gets the published content only.
|
|
374
|
+
|
|
375
|
+
Both composables also accept an explicit version, which bypasses the query parameters:
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
const { data } = useEponymeCollectionEntry('articles', slug, { version: 'draft' })
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Sitemap
|
|
382
|
+
|
|
383
|
+
Eponyme exposes all configured public URLs through one endpoint:
|
|
384
|
+
|
|
385
|
+
```http
|
|
386
|
+
GET /api/eponyme-sitemap
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Singleton entries use their exact `previewPaths` value. Collections use a dynamic `:slug` path:
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
export default defineNuxtConfig({
|
|
393
|
+
eponyme: {
|
|
394
|
+
prismaClient: '~/server/utils/prisma',
|
|
395
|
+
previewPaths: {
|
|
396
|
+
homepage: '/',
|
|
397
|
+
articles: '/articles/:slug',
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
})
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The response contains metadata only:
|
|
404
|
+
|
|
405
|
+
```json
|
|
406
|
+
{
|
|
407
|
+
"entries": [
|
|
408
|
+
{
|
|
409
|
+
"loc": "/"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
"loc": "/articles/my-article",
|
|
413
|
+
"lastmod": "2026-07-28T12:00:00.000Z"
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Only published collection entries are included. Drafts and deleted entries are omitted. `lastmod` is the publication timestamp, so editing a private draft does not change the public sitemap.
|
|
420
|
+
|
|
421
|
+
Each collection is read with a single query, so the endpoint stays cheap as collections grow.
|
|
422
|
+
|
|
423
|
+
The same data is available to your own server code through an auto-imported, typed
|
|
424
|
+
utility, so a sitemap integration never has to make an internal HTTP request:
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
// server/routes/sitemap.xml.ts
|
|
428
|
+
export default defineEventHandler(async (event) => {
|
|
429
|
+
const entries = await getEponymeSitemapEntries()
|
|
430
|
+
setHeader(event, 'content-type', 'application/xml')
|
|
431
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
432
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
433
|
+
${entries.map(entry => ` <url><loc>https://example.com${entry.loc}</loc>${entry.lastmod ? `<lastmod>${entry.lastmod}</lastmod>` : ''}</url>`).join('\n')}
|
|
434
|
+
</urlset>`
|
|
435
|
+
})
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Forms
|
|
439
|
+
|
|
440
|
+
Use `form()` for public forms. A form owns a schema, its own routes and its anti-abuse
|
|
441
|
+
rules, so it is a top-level primitive rather than a field value:
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
export default defineEponymeConfig({
|
|
445
|
+
contact: form({
|
|
446
|
+
label: 'Contact',
|
|
447
|
+
fields: {
|
|
448
|
+
name: field.string({ label: 'Name', required: true }),
|
|
449
|
+
email: field.email({ label: 'Email', required: true }),
|
|
450
|
+
message: field.textarea({ label: 'Message', required: true }),
|
|
451
|
+
},
|
|
452
|
+
submission: { mode: 'managed' },
|
|
453
|
+
}),
|
|
454
|
+
})
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
A form only accepts field types a visitor can fill in: `string`, `textarea`, `email`,
|
|
458
|
+
`url`, `number`, `boolean`, `select`, `radio` and `checkboxGroup`. Authoring types such as
|
|
459
|
+
`richText`, `image`, `slug`, `date`, `color`, `array`, `section` and `tabs` are rejected at
|
|
460
|
+
configuration time.
|
|
461
|
+
|
|
462
|
+
### Submission modes
|
|
463
|
+
|
|
464
|
+
`custom` is the default, so a form never writes to the database unless you ask for it.
|
|
465
|
+
|
|
466
|
+
- `custom`: Eponyme provides the schema, the validation and the data; your application
|
|
467
|
+
owns delivery — an API route, an email service, a webhook, your own table.
|
|
468
|
+
- `managed`: Eponyme validates, stores the submission, and lists it in the dashboard.
|
|
469
|
+
|
|
470
|
+
The configured mode decides what `submit()` does, not the presence of a callback, so the
|
|
471
|
+
behaviour stays readable from `eponyme.config.ts` alone.
|
|
472
|
+
|
|
473
|
+
### Rendering a form
|
|
474
|
+
|
|
475
|
+
`useEponymeForm()` renders nothing: your application owns the markup, so a public page
|
|
476
|
+
never inherits the dashboard styling or its editor dependencies.
|
|
477
|
+
|
|
478
|
+
```vue
|
|
479
|
+
<script setup lang="ts">
|
|
480
|
+
const { fields, errors, honeypot, pending, submitted, submit } = useEponymeForm('contact')
|
|
481
|
+
</script>
|
|
482
|
+
|
|
483
|
+
<template>
|
|
484
|
+
<p v-if="submitted">Thanks, your message was received.</p>
|
|
485
|
+
<form v-else novalidate @submit.prevent="submit">
|
|
486
|
+
<div v-for="entry in fields" :key="entry.name">
|
|
487
|
+
<label :for="entry.name">{{ entry.label }}</label>
|
|
488
|
+
<input
|
|
489
|
+
:id="entry.name"
|
|
490
|
+
:value="entry.value"
|
|
491
|
+
@input="entry.update(($event.target as HTMLInputElement).value)"
|
|
492
|
+
>
|
|
493
|
+
<span v-for="message in entry.errors" :key="message">{{ message }}</span>
|
|
494
|
+
</div>
|
|
495
|
+
<!-- Trap field: a human never sees it, a bot fills it in. -->
|
|
496
|
+
<input v-if="honeypot" :name="honeypot" tabindex="-1" autocomplete="off" hidden>
|
|
497
|
+
<button type="submit" :disabled="pending">Send</button>
|
|
498
|
+
</form>
|
|
499
|
+
</template>
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
For a `custom` form, pass the handler that delivers the data:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
const { submit } = useEponymeForm('contact', {
|
|
506
|
+
async onSubmit(data) {
|
|
507
|
+
await $fetch('/api/contact', { method: 'POST', body: data })
|
|
508
|
+
},
|
|
509
|
+
})
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Server-side validation
|
|
513
|
+
|
|
514
|
+
Client-side validation only improves feedback and is never the security boundary. A
|
|
515
|
+
`custom` form posts to your own route, so validate it there with the auto-imported
|
|
516
|
+
utility:
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
// server/api/contact.post.ts
|
|
520
|
+
export default defineEventHandler(async (event) => {
|
|
521
|
+
const result = validateEponymeForm('contact', await readBody(event))
|
|
522
|
+
if (result.errors) {
|
|
523
|
+
setResponseStatus(event, 422)
|
|
524
|
+
return { errors: result.errors }
|
|
525
|
+
}
|
|
526
|
+
await sendEmail(result.data)
|
|
527
|
+
return { delivered: true }
|
|
528
|
+
})
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
A `managed` form is validated by Eponyme itself on `POST /api/eponyme-forms/<name>`.
|
|
532
|
+
|
|
533
|
+
### Submissions and anti-abuse
|
|
534
|
+
|
|
535
|
+
Managed submissions are listed in the dashboard with sorting, pagination, a detail view
|
|
536
|
+
and per-row deletion. Reading them requires a session; deleting requires `editor` or
|
|
537
|
+
`owner`.
|
|
538
|
+
|
|
539
|
+
Two protections apply to the public submission route:
|
|
540
|
+
|
|
541
|
+
- a **honeypot** field, named `_eponyme_hp` by default. A filled honeypot gets the normal
|
|
542
|
+
success response and stores nothing, so a bot learns nothing from the answer. Set
|
|
543
|
+
`honeypot: false` to disable it, or pass another name.
|
|
544
|
+
- a **body size limit**, 64 KB by default, configurable with `maxBodyBytes`. An oversized
|
|
545
|
+
body is refused with `413` before it is parsed.
|
|
546
|
+
|
|
547
|
+
Rate limiting, CAPTCHA support and file uploads are not implemented yet.
|
|
548
|
+
|
|
549
|
+
## Current status
|
|
550
|
+
|
|
551
|
+
Eponyme is at version `0.1.0`. It is ready for controlled projects and production pilots. A few workflows still need hardening before a broad public release:
|
|
552
|
+
|
|
553
|
+
- Dynamic preview routes for collection entries
|
|
554
|
+
- Reliable preview version switching and cache invalidation
|
|
555
|
+
- Client revision tokens for long-running concurrent edits
|
|
556
|
+
- Recoverable deletion
|
|
557
|
+
- Public form submissions
|
|
558
|
+
|
|
559
|
+
## Development
|
|
560
|
+
|
|
561
|
+
Install dependencies:
|
|
562
|
+
|
|
563
|
+
```bash
|
|
564
|
+
pnpm install
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Prepare generated types:
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
pnpm dev:prepare
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Start the playground:
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
pnpm dev
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Run project checks:
|
|
580
|
+
|
|
581
|
+
```bash
|
|
582
|
+
pnpm lint
|
|
583
|
+
pnpm test
|
|
584
|
+
pnpm test:types
|
|
585
|
+
pnpm dev:build
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Build the package:
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
pnpm prepack
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
## License
|
|
595
|
+
|
|
596
|
+
MIT
|
|
597
|
+
|
|
598
|
+
<!-- Badges -->
|
|
599
|
+
[npm-version-src]: https://img.shields.io/npm/v/@karibsen/eponyme/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
600
|
+
[npm-version-href]: https://npmjs.com/package/@karibsen/eponyme
|
|
601
|
+
|
|
602
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@karibsen/eponyme.svg
|
|
603
|
+
[npm-downloads-href]: https://npm.chart.dev/@karibsen/eponyme
|
|
604
|
+
|
|
605
|
+
[license-src]: https://img.shields.io/npm/l/@karibsen/eponyme.svg
|
|
606
|
+
[license-href]: https://npmjs.com/package/@karibsen/eponyme
|
|
607
|
+
|
|
608
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt
|
|
609
|
+
[nuxt-href]: https://nuxt.com
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EponymeConfig, EponymeSchema, EponymeCollectionOptions, CollectionSlugField, EponymeCollectionDefinition, EponymeFormSchema, EponymeFormOptions, EponymeFormDefinition } from '../dist/runtime/types/index.js';
|
|
2
|
+
export { ArrayFieldDefinition, ArrayFieldOptions, ArrayItemDefinition, ArrayItemFieldDefinition, ArrayItemSchema, ArrayItemValue, BooleanFieldDefinition, BooleanFieldOptions, CheckboxGroupFieldDefinition, CheckboxGroupFieldOptions, CollectionSlugField, ColorFieldDefinition, ColorFieldOptions, DateFieldDefinition, DateFieldOptions, EmailFieldDefinition, EmailFieldOptions, EponymeAuthSession, EponymeAuthUser, EponymeCollectionDataByName, EponymeCollectionDefinition, EponymeCollectionDefinitionBase, EponymeCollectionName, EponymeCollectionOptions, EponymeConfig, EponymeData, EponymeDataByName, EponymeFormDataByName, EponymeFormDefinition, EponymeFormDefinitionBase, EponymeFormFieldDefinition, EponymeFormMode, EponymeFormName, EponymeFormOptions, EponymeFormSchema, EponymeManagedUserResult, EponymeName, EponymeRole, EponymeSchema, FieldDefinition, FieldValidationResult, FieldValidator, FieldVisibilityCondition, ImageFieldDefinition, ImageFieldOptions, NumberFieldDefinition, NumberFieldOptions, RadioFieldDefinition, RadioFieldOptions, RichTextFieldDefinition, RichTextFieldOptions, SectionFieldDefinition, SectionFieldOptions, SectionItemFieldDefinition, SectionItemValue, SectionSchema, SectionValue, SelectFieldDefinition, SelectFieldOptions, SelectOption, SlugFieldDefinition, SlugFieldOptions, StringFieldDefinition, StringFieldOptions, TabDefinition, TabFieldDefinition, TabFieldOptions, TabsSchema, TabsValue, TextareaFieldDefinition, TextareaFieldOptions, TodayDateDefault, UrlFieldDefinition, UrlFieldOptions, UrlType, UrlValue } from '../dist/runtime/types/index.js';
|
|
3
|
+
export { field, today } from '../dist/runtime/fields/index.js';
|
|
4
|
+
export { EPONYME_ROLES, canEditEponyme, isEponymeRole } from '../dist/runtime/types/auth.js';
|
|
5
|
+
|
|
6
|
+
declare function defineEponymeConfig<const T extends EponymeConfig>(config: T): T;
|
|
7
|
+
|
|
8
|
+
type CollectionOptions<T extends EponymeSchema> = Omit<EponymeCollectionOptions<T>, 'slugField'> & {
|
|
9
|
+
slugField: CollectionSlugField<T>;
|
|
10
|
+
};
|
|
11
|
+
declare function collection<const T extends EponymeSchema>(options: CollectionOptions<T>): EponymeCollectionDefinition<T>;
|
|
12
|
+
|
|
13
|
+
declare function form<const T extends EponymeFormSchema>(options: EponymeFormOptions<T>): EponymeFormDefinition<T>;
|
|
14
|
+
|
|
15
|
+
export { collection, defineEponymeConfig, form };
|
package/dist/eponyme.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export { field, today } from '../dist/runtime/fields/index.js';
|
|
2
|
+
export { EPONYME_ROLES, canEditEponyme, isEponymeRole } from '../dist/runtime/types/auth.js';
|
|
3
|
+
|
|
4
|
+
function defineEponymeConfig(config) {
|
|
5
|
+
return config;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function collection(options) {
|
|
9
|
+
if (options.fields[options.slugField]?.type !== "slug")
|
|
10
|
+
throw new TypeError(`[Eponyme] Collection slug field "${options.slugField}" must use field.slug().`);
|
|
11
|
+
return { __eponymeCollection: true, ...options };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const PUBLIC_FIELD_TYPES = ["string", "textarea", "email", "url", "number", "boolean", "select", "radio", "checkboxGroup"];
|
|
15
|
+
const DEFAULT_HONEYPOT = "_eponyme_hp";
|
|
16
|
+
const DEFAULT_MAX_BODY_BYTES = 64 * 1024;
|
|
17
|
+
function form(options) {
|
|
18
|
+
for (const [name, definition] of Object.entries(options.fields)) {
|
|
19
|
+
const type = definition.type;
|
|
20
|
+
if (!PUBLIC_FIELD_TYPES.includes(type))
|
|
21
|
+
throw new TypeError(`[Eponyme] Form field "${name}" uses field.${type}(), which is not available in a public form. Use one of: ${PUBLIC_FIELD_TYPES.join(", ")}.`);
|
|
22
|
+
}
|
|
23
|
+
const honeypot = options.honeypot === void 0 ? DEFAULT_HONEYPOT : options.honeypot;
|
|
24
|
+
if (honeypot && honeypot in options.fields)
|
|
25
|
+
throw new TypeError(`[Eponyme] Form honeypot "${honeypot}" collides with a declared field. Rename the field or set another honeypot.`);
|
|
26
|
+
return {
|
|
27
|
+
__eponymeForm: true,
|
|
28
|
+
label: options.label,
|
|
29
|
+
description: options.description,
|
|
30
|
+
fields: options.fields,
|
|
31
|
+
// Storing a submission has to be an explicit decision, so `custom` is the default.
|
|
32
|
+
submission: { mode: options.submission?.mode ?? "custom" },
|
|
33
|
+
honeypot,
|
|
34
|
+
maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { collection, defineEponymeConfig, form };
|