@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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { createDefaultEponymeData } from "../../utils/create-default-eponyme-data.js";
|
|
3
|
+
import { getEponymeCollections, getEponymeSchemas } from "../../utils/get-eponyme-schemas.js";
|
|
4
|
+
import { normalizeSlug } from "../../utils/normalize-slug.js";
|
|
5
|
+
import { applyPreviewSlug } from "../../utils/preview.js";
|
|
6
|
+
import { validateEponymeData, validateEponymePatch } from "../../utils/validate-eponyme-data.js";
|
|
7
|
+
export class EponymeService {
|
|
8
|
+
constructor(config, client) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
this.schemas = getEponymeSchemas(config);
|
|
11
|
+
this.collections = getEponymeCollections(config);
|
|
12
|
+
}
|
|
13
|
+
schemas;
|
|
14
|
+
collections;
|
|
15
|
+
getSchema(name) {
|
|
16
|
+
return this.schemas[name] ?? this.getCollectionEntry(name)?.definition.fields;
|
|
17
|
+
}
|
|
18
|
+
getCollection(name) {
|
|
19
|
+
return this.collections[name];
|
|
20
|
+
}
|
|
21
|
+
getCollectionEntry(name) {
|
|
22
|
+
for (const [collectionName, definition] of Object.entries(this.collections)) {
|
|
23
|
+
const prefix = `${collectionName}/`;
|
|
24
|
+
if (!name.startsWith(prefix)) continue;
|
|
25
|
+
const slug = name.slice(prefix.length);
|
|
26
|
+
if (slug && !slug.includes("/")) return { name: collectionName, slug, definition };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Reconcile every configured eponyme at application startup. */
|
|
30
|
+
async syncAll() {
|
|
31
|
+
await Promise.all(Object.keys(this.schemas).map((name) => this.loadState(name)));
|
|
32
|
+
}
|
|
33
|
+
reconcile(schema, value, mode) {
|
|
34
|
+
const persisted = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
35
|
+
const defaults = createDefaultEponymeData(schema);
|
|
36
|
+
return Object.fromEntries(Object.keys(schema).map((key) => {
|
|
37
|
+
const candidate = key in persisted ? persisted[key] : defaults[key];
|
|
38
|
+
const errors = validateEponymeData({ [key]: schema[key] }, { [key]: candidate }, mode);
|
|
39
|
+
return [key, Object.keys(errors).length ? defaults[key] : candidate];
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
createState(schema, value) {
|
|
43
|
+
const data = this.reconcile(schema, value ?? createDefaultEponymeData(schema), "publish");
|
|
44
|
+
return {
|
|
45
|
+
__eponyme: {
|
|
46
|
+
version: 1,
|
|
47
|
+
draft: data,
|
|
48
|
+
published: data,
|
|
49
|
+
status: "published",
|
|
50
|
+
publishedAt: null
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
normalizeState(schema, value) {
|
|
55
|
+
const stored = getStoredState(value);
|
|
56
|
+
if (!stored) return this.createState(schema, value);
|
|
57
|
+
return {
|
|
58
|
+
__eponyme: {
|
|
59
|
+
version: 1,
|
|
60
|
+
draft: this.reconcile(schema, stored.draft, "draft"),
|
|
61
|
+
published: this.reconcile(schema, stored.published, "publish"),
|
|
62
|
+
status: stored.status,
|
|
63
|
+
publishedAt: stored.publishedAt
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Reads the stored state along with the `updatedAt` it was read at, used as the optimistic lock token. */
|
|
68
|
+
async loadRow(name) {
|
|
69
|
+
const schema = this.getSchema(name);
|
|
70
|
+
if (!schema) return void 0;
|
|
71
|
+
const defaults = this.createState(schema);
|
|
72
|
+
const row = this.getCollectionEntry(name) ? await this.client.eponyme.findUnique({ where: { name } }) : await this.client.eponyme.upsert({ where: { name }, create: { name, data: defaults }, update: {} });
|
|
73
|
+
if (!row) return void 0;
|
|
74
|
+
const state = this.normalizeState(schema, row.data);
|
|
75
|
+
if (sameData(state, row.data)) return { state, updatedAt: row.updatedAt };
|
|
76
|
+
const healed = await this.client.eponyme.update({ where: { name }, data: { data: state } });
|
|
77
|
+
return { state, updatedAt: healed.updatedAt ?? row.updatedAt };
|
|
78
|
+
}
|
|
79
|
+
async loadState(name) {
|
|
80
|
+
return (await this.loadRow(name))?.state;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Writes a new state only if the row still carries the `updatedAt` we read.
|
|
84
|
+
* Returns false when a concurrent editor won the race, so the caller can report a conflict
|
|
85
|
+
* instead of silently discarding their changes.
|
|
86
|
+
*/
|
|
87
|
+
async writeState(name, next, expectedUpdatedAt) {
|
|
88
|
+
const data = next;
|
|
89
|
+
if (!expectedUpdatedAt) {
|
|
90
|
+
await this.client.eponyme.update({ where: { name }, data: { data } });
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
const { count } = await this.client.eponyme.updateMany({ where: { name, updatedAt: expectedUpdatedAt }, data: { data } });
|
|
94
|
+
return count > 0;
|
|
95
|
+
}
|
|
96
|
+
async get(name, version = "published") {
|
|
97
|
+
return (await this.getResult(name, version))?.data;
|
|
98
|
+
}
|
|
99
|
+
async getResult(name, version = "published") {
|
|
100
|
+
if (typeof version === "number") return this.getVersionResult(name, version);
|
|
101
|
+
const state = await this.loadState(name);
|
|
102
|
+
if (!state) return void 0;
|
|
103
|
+
if (version === "published" && this.getCollectionEntry(name) && !state.__eponyme.publishedAt) return void 0;
|
|
104
|
+
return {
|
|
105
|
+
data: state.__eponyme[version],
|
|
106
|
+
status: state.__eponyme.status,
|
|
107
|
+
publishedAt: state.__eponyme.publishedAt
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Content as it was in a given version, for previewing without restoring it. */
|
|
111
|
+
async getVersionResult(name, versionId) {
|
|
112
|
+
const schema = this.getSchema(name);
|
|
113
|
+
if (!schema) return void 0;
|
|
114
|
+
const version = await this.client.eponymeVersion.findUnique({ where: { id: versionId } });
|
|
115
|
+
if (!version || version.entryName !== name) return void 0;
|
|
116
|
+
const state = this.normalizeState(schema, version.data);
|
|
117
|
+
return {
|
|
118
|
+
data: state.__eponyme.draft,
|
|
119
|
+
status: state.__eponyme.status,
|
|
120
|
+
publishedAt: state.__eponyme.publishedAt
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async statuses() {
|
|
124
|
+
const entries = await Promise.all(Object.keys(this.schemas).map(async (name) => {
|
|
125
|
+
const state = await this.loadState(name);
|
|
126
|
+
return [name, state?.__eponyme.status ?? "published"];
|
|
127
|
+
}));
|
|
128
|
+
return Object.fromEntries(entries);
|
|
129
|
+
}
|
|
130
|
+
async listCollection(name, version = "published") {
|
|
131
|
+
const definition = this.collections[name];
|
|
132
|
+
if (!definition) return void 0;
|
|
133
|
+
const rows = await this.client.eponyme.findMany({
|
|
134
|
+
where: { name: { startsWith: `${name}/` } },
|
|
135
|
+
orderBy: [
|
|
136
|
+
{ updatedAt: "desc" },
|
|
137
|
+
{ name: "asc" }
|
|
138
|
+
]
|
|
139
|
+
});
|
|
140
|
+
const entries = await Promise.all(rows.filter((row) => !row.name.slice(name.length + 1).includes("/")).map(async (row) => {
|
|
141
|
+
const slug = row.name.slice(name.length + 1);
|
|
142
|
+
const result = await this.getResult(row.name, version);
|
|
143
|
+
return {
|
|
144
|
+
slug,
|
|
145
|
+
title: String(result?.data[definition.titleField] || slug),
|
|
146
|
+
data: result?.data ?? {},
|
|
147
|
+
status: version === "published" ? "published" : result?.status ?? "draft",
|
|
148
|
+
publishedAt: result?.publishedAt ?? null,
|
|
149
|
+
updatedAt: row.updatedAt ? new Date(row.updatedAt).toISOString() : null
|
|
150
|
+
};
|
|
151
|
+
}));
|
|
152
|
+
return version === "draft" ? entries : entries.filter((entry) => entry.publishedAt);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Builds the public URL list configured by `previewPaths`.
|
|
156
|
+
* Collections are resolved directly from their stored rows so this performs
|
|
157
|
+
* one query per collection rather than one query per entry.
|
|
158
|
+
*/
|
|
159
|
+
async getSitemapEntries(previewPaths) {
|
|
160
|
+
const groups = await Promise.all(Object.entries(previewPaths).map(async ([name, path]) => {
|
|
161
|
+
const collection = this.collections[name];
|
|
162
|
+
if (!collection) {
|
|
163
|
+
const result = await this.getResult(name, "published");
|
|
164
|
+
if (!result) return [];
|
|
165
|
+
return [withLastmod(path, result.publishedAt)];
|
|
166
|
+
}
|
|
167
|
+
if (!path.includes(":slug"))
|
|
168
|
+
throw new Error(`[Eponyme] previewPaths.${name} must include ":slug" to generate collection sitemap URLs.`);
|
|
169
|
+
const rows = await this.client.eponyme.findMany({
|
|
170
|
+
where: { name: { startsWith: `${name}/` } },
|
|
171
|
+
orderBy: [
|
|
172
|
+
{ updatedAt: "desc" },
|
|
173
|
+
{ name: "asc" }
|
|
174
|
+
]
|
|
175
|
+
});
|
|
176
|
+
return rows.flatMap((row) => {
|
|
177
|
+
const slug = row.name.slice(name.length + 1);
|
|
178
|
+
if (!slug || slug.includes("/")) return [];
|
|
179
|
+
const state = this.normalizeState(collection.fields, row.data);
|
|
180
|
+
if (!state.__eponyme.publishedAt) return [];
|
|
181
|
+
return [withLastmod(applyPreviewSlug(path, slug), state.__eponyme.publishedAt)];
|
|
182
|
+
});
|
|
183
|
+
}));
|
|
184
|
+
const entries = /* @__PURE__ */ new Map();
|
|
185
|
+
for (const entry of groups.flat())
|
|
186
|
+
entries.set(entry.loc, entry);
|
|
187
|
+
return [...entries.values()];
|
|
188
|
+
}
|
|
189
|
+
async createCollectionEntry(name, payload, actorId) {
|
|
190
|
+
const definition = this.collections[name];
|
|
191
|
+
if (!definition) return void 0;
|
|
192
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { errors: { _form: ["Body must be an object."] } };
|
|
193
|
+
const input = payload;
|
|
194
|
+
if (!String(input[definition.titleField] || "").trim())
|
|
195
|
+
return { errors: { [definition.titleField]: ["This field is required."] } };
|
|
196
|
+
const slug = normalizeSlug(String(input[definition.slugField] || input[definition.titleField] || ""));
|
|
197
|
+
if (!slug) return { errors: { [definition.slugField]: ["A slug or title is required."] } };
|
|
198
|
+
const entryName = `${name}/${slug}`;
|
|
199
|
+
if (await this.client.eponyme.findUnique({ where: { name: entryName } }))
|
|
200
|
+
return { errors: { [definition.slugField]: ["This slug is already in use."] } };
|
|
201
|
+
const defaults = createDefaultEponymeData(definition.fields);
|
|
202
|
+
const data = { ...defaults, ...input, [definition.slugField]: slug };
|
|
203
|
+
const errors = validateEponymePatch(definition.fields, data, data, "draft");
|
|
204
|
+
if (Object.keys(errors).length) return { errors };
|
|
205
|
+
const state = {
|
|
206
|
+
__eponyme: {
|
|
207
|
+
version: 1,
|
|
208
|
+
draft: data,
|
|
209
|
+
published: defaults,
|
|
210
|
+
status: "draft",
|
|
211
|
+
publishedAt: null
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
try {
|
|
215
|
+
await this.client.eponyme.create({ data: { name: entryName, data: state } });
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (isPrismaError(error, "P2002")) return { errors: { [definition.slugField]: ["This slug is already in use."] } };
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
await this.client.eponymeVersion.create({ data: { entryName, data: state, action: "draft", status: "draft", userId: actorId ?? null } });
|
|
221
|
+
return { slug, data, status: "draft", publishedAt: null };
|
|
222
|
+
}
|
|
223
|
+
async deleteCollectionEntry(name) {
|
|
224
|
+
if (!this.getCollectionEntry(name)) return false;
|
|
225
|
+
try {
|
|
226
|
+
await this.client.eponyme.delete({ where: { name } });
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (isPrismaError(error, "P2025")) return false;
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
async patch(name, payload, action = "publish", actorId) {
|
|
234
|
+
const schema = this.getSchema(name);
|
|
235
|
+
if (!schema) return void 0;
|
|
236
|
+
const row = await this.loadRow(name);
|
|
237
|
+
if (!row) return void 0;
|
|
238
|
+
const { state, updatedAt } = row;
|
|
239
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { errors: { _form: ["Body must be an object."] } };
|
|
240
|
+
const collectionEntry = this.getCollectionEntry(name);
|
|
241
|
+
if (collectionEntry && collectionEntry.definition.slugField in payload) {
|
|
242
|
+
const submittedSlug = payload[collectionEntry.definition.slugField];
|
|
243
|
+
if (submittedSlug !== collectionEntry.slug)
|
|
244
|
+
return { errors: { [collectionEntry.definition.slugField]: ["The slug cannot be changed after creation."] } };
|
|
245
|
+
}
|
|
246
|
+
const data = { ...state.__eponyme.draft, ...payload };
|
|
247
|
+
const patchErrors = validateEponymePatch(schema, payload, data, action);
|
|
248
|
+
const errors = action === "publish" ? mergeErrors(patchErrors, validateEponymeData(schema, data, "publish")) : patchErrors;
|
|
249
|
+
if (Object.keys(errors).length) return { errors };
|
|
250
|
+
const publishedAt = action === "publish" ? (/* @__PURE__ */ new Date()).toISOString() : state.__eponyme.publishedAt;
|
|
251
|
+
const next = {
|
|
252
|
+
__eponyme: {
|
|
253
|
+
version: 1,
|
|
254
|
+
draft: data,
|
|
255
|
+
published: action === "publish" ? data : state.__eponyme.published,
|
|
256
|
+
status: action === "publish" ? "published" : "draft",
|
|
257
|
+
publishedAt
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
if (!await this.writeState(name, next, updatedAt)) return { conflict: true };
|
|
261
|
+
await this.client.eponymeVersion.create({
|
|
262
|
+
data: {
|
|
263
|
+
entryName: name,
|
|
264
|
+
data: next,
|
|
265
|
+
action,
|
|
266
|
+
status: next.__eponyme.status,
|
|
267
|
+
userId: actorId ?? null
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
return { data, status: next.__eponyme.status, publishedAt };
|
|
271
|
+
}
|
|
272
|
+
async history(name, limit = 50) {
|
|
273
|
+
if (!this.getSchema(name)) return void 0;
|
|
274
|
+
const versions = await this.client.eponymeVersion.findMany({
|
|
275
|
+
where: { entryName: name },
|
|
276
|
+
orderBy: { createdAt: "desc" },
|
|
277
|
+
take: Math.min(Math.max(limit, 1), 100),
|
|
278
|
+
include: { user: true }
|
|
279
|
+
});
|
|
280
|
+
return versions.map((version) => ({
|
|
281
|
+
id: version.id,
|
|
282
|
+
action: version.action === "draft" || version.action === "publish" ? version.action : "restore",
|
|
283
|
+
status: version.status === "draft" ? "draft" : "published",
|
|
284
|
+
createdAt: new Date(version.createdAt).toISOString(),
|
|
285
|
+
user: version.user ? { id: version.user.id, username: version.user.username } : null
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
async restore(name, versionId, actorId) {
|
|
289
|
+
const schema = this.getSchema(name);
|
|
290
|
+
if (!schema) return void 0;
|
|
291
|
+
const version = await this.client.eponymeVersion.findUnique({ where: { id: versionId } });
|
|
292
|
+
if (!version || version.entryName !== name) return void 0;
|
|
293
|
+
const current = await this.loadRow(name);
|
|
294
|
+
if (!current) return void 0;
|
|
295
|
+
const state = this.normalizeState(schema, version.data);
|
|
296
|
+
if (!await this.writeState(name, state, current.updatedAt)) return { conflict: true };
|
|
297
|
+
await this.client.eponymeVersion.create({
|
|
298
|
+
data: {
|
|
299
|
+
entryName: name,
|
|
300
|
+
data: state,
|
|
301
|
+
action: "restore",
|
|
302
|
+
status: state.__eponyme.status,
|
|
303
|
+
userId: actorId ?? null
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
data: state.__eponyme.draft,
|
|
308
|
+
status: state.__eponyme.status,
|
|
309
|
+
publishedAt: state.__eponyme.publishedAt
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function getStoredState(value) {
|
|
314
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
315
|
+
const envelope = value;
|
|
316
|
+
const state = envelope.__eponyme ?? envelope.__keditor;
|
|
317
|
+
const valid = Boolean(
|
|
318
|
+
state && state.version === 1 && state.draft && typeof state.draft === "object" && !Array.isArray(state.draft) && state.published && typeof state.published === "object" && !Array.isArray(state.published) && (state.status === "draft" || state.status === "published") && (state.publishedAt === null || typeof state.publishedAt === "string")
|
|
319
|
+
);
|
|
320
|
+
return valid ? state : void 0;
|
|
321
|
+
}
|
|
322
|
+
function mergeErrors(...groups) {
|
|
323
|
+
const errors = {};
|
|
324
|
+
for (const group of groups) {
|
|
325
|
+
for (const [field, messages] of Object.entries(group))
|
|
326
|
+
errors[field] = [.../* @__PURE__ */ new Set([...errors[field] ?? [], ...messages])];
|
|
327
|
+
}
|
|
328
|
+
return errors;
|
|
329
|
+
}
|
|
330
|
+
export function isPrismaError(error, code) {
|
|
331
|
+
return Boolean(error && typeof error === "object" && error.code === code);
|
|
332
|
+
}
|
|
333
|
+
function sameData(left, right) {
|
|
334
|
+
return isDeepStrictEqual(left, right);
|
|
335
|
+
}
|
|
336
|
+
function withLastmod(loc, publishedAt) {
|
|
337
|
+
return publishedAt ? { loc, lastmod: publishedAt } : { loc };
|
|
338
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { EponymeAuthUser, EponymeRole } from '../../types/index.js';
|
|
3
|
+
export declare const EPONYME_SESSION_COOKIE = "eponyme_session";
|
|
4
|
+
export declare function getEponymeEventUser(event: H3Event): Promise<EponymeAuthUser | undefined>;
|
|
5
|
+
export declare function requireEponymeUser(event: H3Event, options?: {
|
|
6
|
+
roles?: EponymeRole[];
|
|
7
|
+
allowPasswordChangeRequired?: boolean;
|
|
8
|
+
}): Promise<EponymeAuthUser>;
|
|
9
|
+
export declare function setEponymeSessionCookie(event: H3Event, token: string, expiresAt: Date): void;
|
|
10
|
+
export declare function clearEponymeSessionCookie(event: H3Event): void;
|
|
11
|
+
export declare function assertEponymeMutationOrigin(event: H3Event): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createError,
|
|
3
|
+
deleteCookie,
|
|
4
|
+
getCookie,
|
|
5
|
+
getHeader,
|
|
6
|
+
getRequestURL,
|
|
7
|
+
setCookie
|
|
8
|
+
} from "h3";
|
|
9
|
+
import { useEponymeAuthService } from "../services/eponyme-auth-service.js";
|
|
10
|
+
export const EPONYME_SESSION_COOKIE = "eponyme_session";
|
|
11
|
+
export async function getEponymeEventUser(event) {
|
|
12
|
+
return (await useEponymeAuthService().getSession(getCookie(event, EPONYME_SESSION_COOKIE)))?.user;
|
|
13
|
+
}
|
|
14
|
+
export async function requireEponymeUser(event, options = {}) {
|
|
15
|
+
const user = await getEponymeEventUser(event);
|
|
16
|
+
if (!user) throw createError({ statusCode: 401, statusMessage: "Authentication required." });
|
|
17
|
+
if (user.mustChangePassword && !options.allowPasswordChangeRequired)
|
|
18
|
+
throw createError({ statusCode: 403, statusMessage: "Password change required.", data: { code: "PASSWORD_CHANGE_REQUIRED" } });
|
|
19
|
+
if (options.roles && !options.roles.includes(user.role))
|
|
20
|
+
throw createError({ statusCode: 403, statusMessage: "Insufficient permissions." });
|
|
21
|
+
return user;
|
|
22
|
+
}
|
|
23
|
+
export function setEponymeSessionCookie(event, token, expiresAt) {
|
|
24
|
+
setCookie(event, EPONYME_SESSION_COOKIE, token, {
|
|
25
|
+
httpOnly: true,
|
|
26
|
+
sameSite: "lax",
|
|
27
|
+
secure: process.env.NODE_ENV === "production",
|
|
28
|
+
path: "/",
|
|
29
|
+
expires: expiresAt
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function clearEponymeSessionCookie(event) {
|
|
33
|
+
deleteCookie(event, EPONYME_SESSION_COOKIE, { path: "/" });
|
|
34
|
+
}
|
|
35
|
+
export function assertEponymeMutationOrigin(event) {
|
|
36
|
+
const origin = getHeader(event, "origin");
|
|
37
|
+
if (!origin) return;
|
|
38
|
+
const requestUrl = getRequestURL(event);
|
|
39
|
+
if (origin !== requestUrl.origin)
|
|
40
|
+
throw createError({ statusCode: 403, statusMessage: "Invalid request origin." });
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ValidationErrors } from '../../utils/validate-eponyme-data.js';
|
|
2
|
+
export type EponymeFormValidationResult = {
|
|
3
|
+
data: Record<string, unknown>;
|
|
4
|
+
errors?: never;
|
|
5
|
+
} | {
|
|
6
|
+
data?: never;
|
|
7
|
+
errors: ValidationErrors;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Auto-imported so a `custom` form keeps a server-side security boundary: client
|
|
11
|
+
* validation only ever improves feedback. Throws on an unknown form name, since
|
|
12
|
+
* that is a configuration mistake rather than bad user input.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateEponymeForm(name: string, body: unknown): EponymeFormValidationResult;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { useEponymeFormService } from "../services/eponyme-form-service.js";
|
|
2
|
+
export function validateEponymeForm(name, body) {
|
|
3
|
+
const result = useEponymeFormService().validate(name, body);
|
|
4
|
+
if (!result) throw new Error(`[Eponyme] Unknown form "${name}". Declare it with form() in eponyme.config.ts.`);
|
|
5
|
+
return result;
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EponymeSitemapEntry } from '../services/eponyme-store.js';
|
|
2
|
+
export type { EponymeSitemapEntry };
|
|
3
|
+
/**
|
|
4
|
+
* Auto-imported so a Nuxt sitemap integration can build its own route without an
|
|
5
|
+
* internal HTTP request.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getEponymeSitemapEntries(): Promise<EponymeSitemapEntry[]>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
|
+
import { useEponymeService } from "../services/eponyme-service.js";
|
|
3
|
+
export async function getEponymeSitemapEntries() {
|
|
4
|
+
const runtimeConfig = useRuntimeConfig();
|
|
5
|
+
const previewPaths = runtimeConfig.public.eponyme?.previewPaths ?? {};
|
|
6
|
+
return useEponymeService().getSitemapEntries(previewPaths);
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
/**
|
|
3
|
+
* Form names may contain slashes (a form nested in a folder), so the route is a
|
|
4
|
+
* wildcard and the path is re-parsed here. The form name is everything before the
|
|
5
|
+
* trailing `/submissions` segment, which is what removes the ambiguity between a
|
|
6
|
+
* nested form and the route suffix.
|
|
7
|
+
*/
|
|
8
|
+
export interface EponymeFormRoute {
|
|
9
|
+
name: string;
|
|
10
|
+
/** True when the path carried the `/submissions` suffix. */
|
|
11
|
+
submissions: boolean;
|
|
12
|
+
submissionId?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function readEponymeFormRoute(event: H3Event): EponymeFormRoute | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getRequestURL } from "h3";
|
|
2
|
+
export function readEponymeFormRoute(event) {
|
|
3
|
+
const path = decodeURIComponent(getRequestURL(event).pathname.replace(/^\/api\/eponyme-forms\/?/, ""));
|
|
4
|
+
if (!path) return void 0;
|
|
5
|
+
const segments = path.split("/").filter(Boolean);
|
|
6
|
+
const suffix = segments.lastIndexOf("submissions");
|
|
7
|
+
if (suffix === -1) return { name: segments.join("/"), submissions: false };
|
|
8
|
+
if (suffix === 0 || segments.length > suffix + 2) return void 0;
|
|
9
|
+
return {
|
|
10
|
+
name: segments.slice(0, suffix).join("/"),
|
|
11
|
+
submissions: true,
|
|
12
|
+
submissionId: segments[suffix + 1]
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function validatePassword(password: unknown): string | undefined;
|
|
2
|
+
export declare function hashPassword(password: string): Promise<string>;
|
|
3
|
+
export declare function verifyPassword(password: string, encoded: string): Promise<boolean>;
|
|
4
|
+
export declare function generateTemporaryPassword(): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { randomBytes, scrypt as scryptCallback, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const scrypt = promisify(scryptCallback);
|
|
4
|
+
const KEY_LENGTH = 64;
|
|
5
|
+
const PASSWORD_MIN_LENGTH = 12;
|
|
6
|
+
const PASSWORD_MAX_LENGTH = 128;
|
|
7
|
+
export function validatePassword(password) {
|
|
8
|
+
if (typeof password !== "string") return "Password is required.";
|
|
9
|
+
if (password.length < PASSWORD_MIN_LENGTH) return `Password must contain at least ${PASSWORD_MIN_LENGTH} characters.`;
|
|
10
|
+
if (password.length > PASSWORD_MAX_LENGTH) return `Password must contain at most ${PASSWORD_MAX_LENGTH} characters.`;
|
|
11
|
+
}
|
|
12
|
+
export async function hashPassword(password) {
|
|
13
|
+
const salt = randomBytes(16).toString("base64url");
|
|
14
|
+
const derived = await scrypt(password, salt, KEY_LENGTH);
|
|
15
|
+
return `scrypt$${salt}$${derived.toString("base64url")}`;
|
|
16
|
+
}
|
|
17
|
+
export async function verifyPassword(password, encoded) {
|
|
18
|
+
const [algorithm, salt, expectedValue] = encoded.split("$");
|
|
19
|
+
if (algorithm !== "scrypt" || !salt || !expectedValue) return false;
|
|
20
|
+
try {
|
|
21
|
+
const expected = Buffer.from(expectedValue, "base64url");
|
|
22
|
+
const actual = await scrypt(password, salt, expected.length);
|
|
23
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function generateTemporaryPassword() {
|
|
29
|
+
return randomBytes(18).toString("base64url");
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const EPONYME_ROLES: readonly ["viewer", "editor", "owner"];
|
|
2
|
+
export type EponymeRole = typeof EPONYME_ROLES[number];
|
|
3
|
+
export interface EponymeAuthUser {
|
|
4
|
+
id: string;
|
|
5
|
+
username: string;
|
|
6
|
+
role: EponymeRole;
|
|
7
|
+
active: boolean;
|
|
8
|
+
mustChangePassword: boolean;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface EponymeAuthSession {
|
|
13
|
+
user: EponymeAuthUser | null;
|
|
14
|
+
}
|
|
15
|
+
export interface EponymeManagedUserResult {
|
|
16
|
+
user: EponymeAuthUser;
|
|
17
|
+
temporaryPassword: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function isEponymeRole(value: unknown): value is EponymeRole;
|
|
20
|
+
export declare function canEditEponyme(role: EponymeRole | undefined): boolean;
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ArrayFieldDefinition, ArrayItemValue, BooleanFieldDefinition, CheckboxGroupFieldDefinition, EmailFieldDefinition, FieldDefinition, NumberFieldDefinition, RadioFieldDefinition, SectionFieldDefinition, SectionValue, SelectFieldDefinition, SlugFieldDefinition, StringFieldDefinition, TabFieldDefinition, TabsValue, TextareaFieldDefinition, UrlFieldDefinition, UrlValue } from './field.js';
|
|
2
|
+
export type EponymeSchema = Record<string, FieldDefinition>;
|
|
3
|
+
export interface EponymeCollectionDefinitionBase {
|
|
4
|
+
__eponymeCollection: true;
|
|
5
|
+
label?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
fields: EponymeSchema;
|
|
8
|
+
slugField: string;
|
|
9
|
+
titleField: string;
|
|
10
|
+
}
|
|
11
|
+
export interface EponymeCollectionOptions<T extends EponymeSchema = EponymeSchema> {
|
|
12
|
+
label?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
fields: T;
|
|
15
|
+
slugField: keyof T & string;
|
|
16
|
+
titleField: keyof T & string;
|
|
17
|
+
}
|
|
18
|
+
export interface EponymeCollectionDefinition<T extends EponymeSchema = EponymeSchema> extends EponymeCollectionOptions<T> {
|
|
19
|
+
__eponymeCollection: true;
|
|
20
|
+
fields: T;
|
|
21
|
+
slugField: keyof T & string;
|
|
22
|
+
titleField: keyof T & string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A public form only accepts field types a visitor can meaningfully fill in.
|
|
26
|
+
* `slug`, `richText`, `image`, `date`, `color`, `array`, `section` and `tabs` are
|
|
27
|
+
* authoring concerns and are rejected by `form()`.
|
|
28
|
+
*/
|
|
29
|
+
export type EponymeFormFieldDefinition = StringFieldDefinition | TextareaFieldDefinition | EmailFieldDefinition | UrlFieldDefinition | NumberFieldDefinition | BooleanFieldDefinition | SelectFieldDefinition | RadioFieldDefinition | CheckboxGroupFieldDefinition;
|
|
30
|
+
export type EponymeFormSchema = Record<string, EponymeFormFieldDefinition>;
|
|
31
|
+
/** `custom` leaves delivery to the host application; `managed` stores the submission. */
|
|
32
|
+
export type EponymeFormMode = 'custom' | 'managed';
|
|
33
|
+
export interface EponymeFormSubmissionOptions {
|
|
34
|
+
mode?: EponymeFormMode;
|
|
35
|
+
}
|
|
36
|
+
export interface EponymeFormOptions<T extends EponymeFormSchema = EponymeFormSchema> {
|
|
37
|
+
label?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
fields: T;
|
|
40
|
+
submission?: EponymeFormSubmissionOptions;
|
|
41
|
+
/** Name of the trap field bots fill in. `false` disables the check. */
|
|
42
|
+
honeypot?: string | false;
|
|
43
|
+
/** In bytes. */
|
|
44
|
+
maxBodyBytes?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface EponymeFormDefinitionBase {
|
|
47
|
+
__eponymeForm: true;
|
|
48
|
+
label?: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
fields: EponymeFormSchema;
|
|
51
|
+
submission: Required<EponymeFormSubmissionOptions>;
|
|
52
|
+
honeypot: string | false;
|
|
53
|
+
maxBodyBytes: number;
|
|
54
|
+
}
|
|
55
|
+
export interface EponymeFormDefinition<T extends EponymeFormSchema = EponymeFormSchema> extends EponymeFormDefinitionBase {
|
|
56
|
+
fields: T;
|
|
57
|
+
}
|
|
58
|
+
export interface EponymeConfig {
|
|
59
|
+
[name: string]: EponymeSchema | EponymeCollectionDefinitionBase | EponymeFormDefinitionBase | EponymeConfig;
|
|
60
|
+
}
|
|
61
|
+
export type CollectionSlugField<T extends EponymeSchema> = {
|
|
62
|
+
[K in keyof T & string]: T[K] extends SlugFieldDefinition ? K : never;
|
|
63
|
+
}[keyof T & string];
|
|
64
|
+
export type FieldValue<T extends FieldDefinition> = T extends {
|
|
65
|
+
type: 'number';
|
|
66
|
+
} ? number : T extends {
|
|
67
|
+
type: 'boolean';
|
|
68
|
+
} ? boolean : T extends UrlFieldDefinition ? UrlValue : T extends ArrayFieldDefinition<infer Item> ? Array<ArrayItemValue<Item>> : T extends CheckboxGroupFieldDefinition<infer Value> ? Value[] : T extends SectionFieldDefinition<infer Section> ? SectionValue<Section> : T extends TabFieldDefinition<infer Tabs> ? TabsValue<Tabs> : string;
|
|
69
|
+
export type EponymeData<T extends EponymeSchema> = {
|
|
70
|
+
[K in keyof T]: T[K] extends FieldDefinition ? FieldValue<T[K]> : never;
|
|
71
|
+
};
|
|
72
|
+
export type EponymeDataByName<T extends EponymeConfig, Name extends EponymeName<T>> = EponymeDataAtPath<T, Name>;
|
|
73
|
+
export type EponymeCollectionName<T extends EponymeConfig> = {
|
|
74
|
+
[K in keyof T & string]: T[K] extends EponymeCollectionDefinitionBase ? K : T[K] extends EponymeFormDefinitionBase ? never : T[K] extends EponymeSchema ? never : T[K] extends EponymeConfig ? `${K}/${EponymeCollectionName<T[K]>}` : never;
|
|
75
|
+
}[keyof T & string];
|
|
76
|
+
export type EponymeCollectionDataByName<T extends EponymeConfig, Name extends EponymeCollectionName<T>> = EponymeCollectionAtPath<T, Name> extends EponymeCollectionDefinition<infer Schema> ? EponymeData<Schema> : never;
|
|
77
|
+
export type EponymeName<T extends EponymeConfig> = {
|
|
78
|
+
[K in keyof T & string]: T[K] extends EponymeCollectionDefinitionBase ? never : T[K] extends EponymeFormDefinitionBase ? never : T[K] extends EponymeSchema ? K : T[K] extends EponymeConfig ? `${K}/${EponymeName<T[K]>}` : never;
|
|
79
|
+
}[keyof T & string];
|
|
80
|
+
export type EponymeFormName<T extends EponymeConfig> = {
|
|
81
|
+
[K in keyof T & string]: T[K] extends EponymeFormDefinitionBase ? K : T[K] extends EponymeCollectionDefinitionBase ? never : T[K] extends EponymeSchema ? never : T[K] extends EponymeConfig ? `${K}/${EponymeFormName<T[K]>}` : never;
|
|
82
|
+
}[keyof T & string];
|
|
83
|
+
export type EponymeFormDataByName<T extends EponymeConfig, Name extends EponymeFormName<T>> = EponymeFormAtPath<T, Name> extends EponymeFormDefinition<infer Schema> ? EponymeData<Schema> : never;
|
|
84
|
+
type EponymeDataAtPath<T extends EponymeConfig, Name extends string> = Name extends `${infer Folder}/${infer Rest}` ? Folder extends keyof T ? T[Folder] extends EponymeConfig ? EponymeDataAtPath<T[Folder], Rest> : never : never : Name extends keyof T ? T[Name] extends EponymeSchema ? EponymeData<T[Name]> : never : never;
|
|
85
|
+
type EponymeCollectionAtPath<T extends EponymeConfig, Name extends string> = Name extends `${infer Folder}/${infer Rest}` ? Folder extends keyof T ? T[Folder] extends EponymeConfig ? EponymeCollectionAtPath<T[Folder], Rest> : never : never : Name extends keyof T ? T[Name] : never;
|
|
86
|
+
type EponymeFormAtPath<T extends EponymeConfig, Name extends string> = Name extends `${infer Folder}/${infer Rest}` ? Folder extends keyof T ? T[Folder] extends EponymeConfig ? EponymeFormAtPath<T[Folder], Rest> : never : never : Name extends keyof T ? T[Name] : never;
|
|
87
|
+
export {};
|
|
File without changes
|