From 8c45efc92e2adb23095f45aaeb3bc89f4d205256 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 27 Mar 2026 20:06:38 +0200 Subject: [PATCH] initttt --- .dockerignore | 61 + .env.example | 47 + .gitignore | 84 + AGENTS.md | 198 + CLAUDE.md | 1 + README.md | 7 + apps/front/package.json | 33 + apps/front/src/index.ts | 153 + apps/front/src/instrumentation.ts | 50 + apps/front/tsconfig.json | 33 + apps/front/view.html | 290 + apps/main/.gitignore | 23 + apps/main/.npmrc | 1 + apps/main/.prettierignore | 9 + apps/main/components.json | 16 + apps/main/package.json | 78 + apps/main/src/app.d.ts | 20 + apps/main/src/app.html | 11 + apps/main/src/demo.spec.ts | 7 + apps/main/src/hooks.server.ts | 79 + apps/main/src/instrumentation.server.ts | 27 + apps/main/src/lib/auth.client.ts | 19 + .../src/lib/components/app-sidebar.svelte | 35 + .../lib/components/atoms/button-text.svelte | 17 + .../main/src/lib/components/atoms/icon.svelte | 11 + .../src/lib/components/atoms/title.svelte | 61 + .../molecules/max-width-wrapper.svelte | 11 + apps/main/src/lib/components/nav-main.svelte | 81 + apps/main/src/lib/components/nav-user.svelte | 150 + .../src/lib/components/team-switcher.svelte | 80 + .../ui/accordion/accordion-content.svelte | 22 + .../ui/accordion/accordion-item.svelte | 17 + .../ui/accordion/accordion-trigger.svelte | 32 + .../components/ui/accordion/accordion.svelte | 16 + .../src/lib/components/ui/accordion/index.ts | 16 + .../alert-dialog/alert-dialog-action.svelte | 18 + .../alert-dialog/alert-dialog-cancel.svelte | 18 + .../alert-dialog/alert-dialog-content.svelte | 29 + .../alert-dialog-description.svelte | 17 + .../alert-dialog/alert-dialog-footer.svelte | 20 + .../alert-dialog/alert-dialog-header.svelte | 20 + .../alert-dialog/alert-dialog-overlay.svelte | 20 + .../alert-dialog/alert-dialog-portal.svelte | 7 + .../ui/alert-dialog/alert-dialog-title.svelte | 17 + .../alert-dialog/alert-dialog-trigger.svelte | 7 + .../ui/alert-dialog/alert-dialog.svelte | 7 + .../lib/components/ui/alert-dialog/index.ts | 37 + .../ui/alert/alert-description.svelte | 23 + .../components/ui/alert/alert-title.svelte | 20 + .../src/lib/components/ui/alert/alert.svelte | 44 + .../main/src/lib/components/ui/alert/index.ts | 14 + .../ui/aspect-ratio/aspect-ratio.svelte | 7 + .../lib/components/ui/aspect-ratio/index.ts | 3 + .../ui/avatar/avatar-fallback.svelte | 17 + .../components/ui/avatar/avatar-image.svelte | 17 + .../lib/components/ui/avatar/avatar.svelte | 19 + .../src/lib/components/ui/avatar/index.ts | 13 + .../src/lib/components/ui/badge/badge.svelte | 50 + .../main/src/lib/components/ui/badge/index.ts | 2 + .../ui/breadcrumb/breadcrumb-ellipsis.svelte | 23 + .../ui/breadcrumb/breadcrumb-item.svelte | 20 + .../ui/breadcrumb/breadcrumb-link.svelte | 31 + .../ui/breadcrumb/breadcrumb-list.svelte | 23 + .../ui/breadcrumb/breadcrumb-page.svelte | 23 + .../ui/breadcrumb/breadcrumb-separator.svelte | 27 + .../ui/breadcrumb/breadcrumb.svelte | 21 + .../src/lib/components/ui/breadcrumb/index.ts | 25 + .../button-group-separator.svelte | 20 + .../ui/button-group/button-group-text.svelte | 30 + .../ui/button-group/button-group.svelte | 46 + .../lib/components/ui/button-group/index.ts | 13 + .../lib/components/ui/button/button.svelte | 82 + .../src/lib/components/ui/button/index.ts | 17 + .../ui/calendar/calendar-caption.svelte | 76 + .../ui/calendar/calendar-cell.svelte | 19 + .../ui/calendar/calendar-day.svelte | 35 + .../ui/calendar/calendar-grid-body.svelte | 12 + .../ui/calendar/calendar-grid-head.svelte | 12 + .../ui/calendar/calendar-grid-row.svelte | 12 + .../ui/calendar/calendar-grid.svelte | 16 + .../ui/calendar/calendar-head-cell.svelte | 19 + .../ui/calendar/calendar-header.svelte | 19 + .../ui/calendar/calendar-heading.svelte | 16 + .../ui/calendar/calendar-month-select.svelte | 44 + .../ui/calendar/calendar-month.svelte | 15 + .../ui/calendar/calendar-months.svelte | 19 + .../ui/calendar/calendar-nav.svelte | 19 + .../ui/calendar/calendar-next-button.svelte | 31 + .../ui/calendar/calendar-prev-button.svelte | 31 + .../ui/calendar/calendar-year-select.svelte | 43 + .../components/ui/calendar/calendar.svelte | 115 + .../src/lib/components/ui/calendar/index.ts | 40 + .../lib/components/ui/card/card-action.svelte | 20 + .../components/ui/card/card-content.svelte | 15 + .../ui/card/card-description.svelte | 20 + .../lib/components/ui/card/card-footer.svelte | 20 + .../lib/components/ui/card/card-header.svelte | 23 + .../lib/components/ui/card/card-title.svelte | 20 + .../src/lib/components/ui/card/card.svelte | 23 + apps/main/src/lib/components/ui/card/index.ts | 25 + .../ui/carousel/carousel-content.svelte | 43 + .../ui/carousel/carousel-item.svelte | 30 + .../ui/carousel/carousel-next.svelte | 38 + .../ui/carousel/carousel-previous.svelte | 38 + .../components/ui/carousel/carousel.svelte | 93 + .../src/lib/components/ui/carousel/context.ts | 58 + .../src/lib/components/ui/carousel/index.ts | 19 + .../ui/chart/chart-container.svelte | 80 + .../components/ui/chart/chart-style.svelte | 37 + .../components/ui/chart/chart-tooltip.svelte | 159 + .../lib/components/ui/chart/chart-utils.ts | 66 + .../main/src/lib/components/ui/chart/index.ts | 6 + .../components/ui/checkbox/checkbox.svelte | 36 + .../src/lib/components/ui/checkbox/index.ts | 6 + .../ui/collapsible/collapsible-content.svelte | 7 + .../ui/collapsible/collapsible-trigger.svelte | 7 + .../ui/collapsible/collapsible.svelte | 11 + .../lib/components/ui/collapsible/index.ts | 13 + .../ui/command/command-dialog.svelte | 40 + .../ui/command/command-empty.svelte | 17 + .../ui/command/command-group.svelte | 32 + .../ui/command/command-input.svelte | 26 + .../components/ui/command/command-item.svelte | 20 + .../ui/command/command-link-item.svelte | 20 + .../components/ui/command/command-list.svelte | 17 + .../ui/command/command-loading.svelte | 7 + .../ui/command/command-separator.svelte | 17 + .../ui/command/command-shortcut.svelte | 20 + .../lib/components/ui/command/command.svelte | 28 + .../src/lib/components/ui/command/index.ts | 37 + .../context-menu-checkbox-item.svelte | 40 + .../context-menu/context-menu-content.svelte | 28 + .../context-menu-group-heading.svelte | 21 + .../ui/context-menu/context-menu-group.svelte | 7 + .../ui/context-menu/context-menu-item.svelte | 27 + .../ui/context-menu/context-menu-label.svelte | 24 + .../context-menu/context-menu-portal.svelte | 7 + .../context-menu-radio-group.svelte | 16 + .../context-menu-radio-item.svelte | 33 + .../context-menu-separator.svelte | 17 + .../context-menu/context-menu-shortcut.svelte | 20 + .../context-menu-sub-content.svelte | 20 + .../context-menu-sub-trigger.svelte | 29 + .../ui/context-menu/context-menu-sub.svelte | 7 + .../context-menu/context-menu-trigger.svelte | 7 + .../ui/context-menu/context-menu.svelte | 7 + .../lib/components/ui/context-menu/index.ts | 52 + .../ui/data-table/data-table.svelte.ts | 142 + .../ui/data-table/flex-render.svelte | 40 + .../src/lib/components/ui/data-table/index.ts | 3 + .../ui/data-table/render-helpers.ts | 111 + .../components/ui/dialog/dialog-close.svelte | 7 + .../ui/dialog/dialog-content.svelte | 45 + .../ui/dialog/dialog-description.svelte | 17 + .../components/ui/dialog/dialog-footer.svelte | 20 + .../components/ui/dialog/dialog-header.svelte | 20 + .../ui/dialog/dialog-overlay.svelte | 20 + .../components/ui/dialog/dialog-portal.svelte | 7 + .../components/ui/dialog/dialog-title.svelte | 17 + .../ui/dialog/dialog-trigger.svelte | 7 + .../lib/components/ui/dialog/dialog.svelte | 7 + .../src/lib/components/ui/dialog/index.ts | 34 + .../components/ui/drawer/drawer-close.svelte | 7 + .../ui/drawer/drawer-content.svelte | 40 + .../ui/drawer/drawer-description.svelte | 17 + .../components/ui/drawer/drawer-footer.svelte | 20 + .../components/ui/drawer/drawer-header.svelte | 20 + .../components/ui/drawer/drawer-nested.svelte | 12 + .../ui/drawer/drawer-overlay.svelte | 20 + .../components/ui/drawer/drawer-portal.svelte | 7 + .../components/ui/drawer/drawer-title.svelte | 17 + .../ui/drawer/drawer-trigger.svelte | 7 + .../lib/components/ui/drawer/drawer.svelte | 12 + .../src/lib/components/ui/drawer/index.ts | 38 + .../dropdown-menu-checkbox-group.svelte | 16 + .../dropdown-menu-checkbox-item.svelte | 43 + .../dropdown-menu-content.svelte | 29 + .../dropdown-menu-group-heading.svelte | 22 + .../dropdown-menu/dropdown-menu-group.svelte | 7 + .../dropdown-menu/dropdown-menu-item.svelte | 27 + .../dropdown-menu/dropdown-menu-label.svelte | 24 + .../dropdown-menu/dropdown-menu-portal.svelte | 7 + .../dropdown-menu-radio-group.svelte | 16 + .../dropdown-menu-radio-item.svelte | 33 + .../dropdown-menu-separator.svelte | 17 + .../dropdown-menu-shortcut.svelte | 20 + .../dropdown-menu-sub-content.svelte | 20 + .../dropdown-menu-sub-trigger.svelte | 29 + .../ui/dropdown-menu/dropdown-menu-sub.svelte | 7 + .../dropdown-menu-trigger.svelte | 7 + .../ui/dropdown-menu/dropdown-menu.svelte | 7 + .../lib/components/ui/dropdown-menu/index.ts | 54 + .../components/ui/empty/empty-content.svelte | 23 + .../ui/empty/empty-description.svelte | 23 + .../components/ui/empty/empty-header.svelte | 20 + .../components/ui/empty/empty-media.svelte | 41 + .../components/ui/empty/empty-title.svelte | 20 + .../src/lib/components/ui/empty/empty.svelte | 23 + .../main/src/lib/components/ui/empty/index.ts | 22 + .../components/ui/field/field-content.svelte | 20 + .../ui/field/field-description.svelte | 25 + .../components/ui/field/field-error.svelte | 58 + .../components/ui/field/field-group.svelte | 23 + .../components/ui/field/field-label.svelte | 26 + .../components/ui/field/field-legend.svelte | 29 + .../ui/field/field-separator.svelte | 38 + .../lib/components/ui/field/field-set.svelte | 24 + .../components/ui/field/field-title.svelte | 23 + .../src/lib/components/ui/field/field.svelte | 53 + .../main/src/lib/components/ui/field/index.ts | 33 + .../lib/components/ui/form/form-button.svelte | 7 + .../ui/form/form-description.svelte | 17 + .../ui/form/form-element-field.svelte | 24 + .../ui/form/form-field-errors.svelte | 30 + .../lib/components/ui/form/form-field.svelte | 29 + .../components/ui/form/form-fieldset.svelte | 15 + .../lib/components/ui/form/form-label.svelte | 24 + .../lib/components/ui/form/form-legend.svelte | 16 + apps/main/src/lib/components/ui/form/index.ts | 33 + .../ui/hover-card/hover-card-content.svelte | 31 + .../ui/hover-card/hover-card-portal.svelte | 7 + .../ui/hover-card/hover-card-trigger.svelte | 7 + .../ui/hover-card/hover-card.svelte | 7 + .../src/lib/components/ui/hover-card/index.ts | 15 + .../lib/components/ui/input-group/index.ts | 22 + .../ui/input-group/input-group-addon.svelte | 55 + .../ui/input-group/input-group-button.svelte | 49 + .../ui/input-group/input-group-input.svelte | 23 + .../ui/input-group/input-group-text.svelte | 22 + .../input-group/input-group-textarea.svelte | 23 + .../ui/input-group/input-group.svelte | 38 + .../src/lib/components/ui/input-otp/index.ts | 15 + .../ui/input-otp/input-otp-group.svelte | 20 + .../ui/input-otp/input-otp-separator.svelte | 19 + .../ui/input-otp/input-otp-slot.svelte | 31 + .../components/ui/input-otp/input-otp.svelte | 22 + .../main/src/lib/components/ui/input/index.ts | 7 + .../src/lib/components/ui/input/input.svelte | 52 + apps/main/src/lib/components/ui/item/index.ts | 34 + .../components/ui/item/item-actions.svelte | 20 + .../components/ui/item/item-content.svelte | 20 + .../ui/item/item-description.svelte | 24 + .../lib/components/ui/item/item-footer.svelte | 20 + .../lib/components/ui/item/item-group.svelte | 21 + .../lib/components/ui/item/item-header.svelte | 20 + .../lib/components/ui/item/item-media.svelte | 42 + .../components/ui/item/item-separator.svelte | 19 + .../lib/components/ui/item/item-title.svelte | 20 + .../src/lib/components/ui/item/item.svelte | 60 + apps/main/src/lib/components/ui/kbd/index.ts | 10 + .../lib/components/ui/kbd/kbd-group.svelte | 20 + .../main/src/lib/components/ui/kbd/kbd.svelte | 25 + .../main/src/lib/components/ui/label/index.ts | 7 + .../src/lib/components/ui/label/label.svelte | 20 + .../src/lib/components/ui/menubar/index.ts | 55 + .../ui/menubar/menubar-checkbox-item.svelte | 43 + .../ui/menubar/menubar-content.svelte | 35 + .../ui/menubar/menubar-group-heading.svelte | 22 + .../ui/menubar/menubar-group.svelte | 12 + .../components/ui/menubar/menubar-item.svelte | 27 + .../ui/menubar/menubar-label.svelte | 25 + .../components/ui/menubar/menubar-menu.svelte | 7 + .../ui/menubar/menubar-portal.svelte | 7 + .../ui/menubar/menubar-radio-group.svelte | 11 + .../ui/menubar/menubar-radio-item.svelte | 33 + .../ui/menubar/menubar-separator.svelte | 17 + .../ui/menubar/menubar-shortcut.svelte | 20 + .../ui/menubar/menubar-sub-content.svelte | 20 + .../ui/menubar/menubar-sub-trigger.svelte | 29 + .../components/ui/menubar/menubar-sub.svelte | 7 + .../ui/menubar/menubar-trigger.svelte | 20 + .../lib/components/ui/menubar/menubar.svelte | 20 + .../lib/components/ui/native-select/index.ts | 12 + .../native-select-opt-group.svelte | 14 + .../native-select/native-select-option.svelte | 14 + .../ui/native-select/native-select.svelte | 38 + .../components/ui/navigation-menu/index.ts | 28 + .../navigation-menu-content.svelte | 21 + .../navigation-menu-indicator.svelte | 22 + .../navigation-menu-item.svelte | 17 + .../navigation-menu-link.svelte | 20 + .../navigation-menu-list.svelte | 17 + .../navigation-menu-trigger.svelte | 34 + .../navigation-menu-viewport.svelte | 22 + .../ui/navigation-menu/navigation-menu.svelte | 32 + .../src/lib/components/ui/pagination/index.ts | 31 + .../ui/pagination/pagination-content.svelte | 20 + .../ui/pagination/pagination-ellipsis.svelte | 22 + .../ui/pagination/pagination-item.svelte | 14 + .../ui/pagination/pagination-link.svelte | 39 + .../pagination/pagination-next-button.svelte | 33 + .../ui/pagination/pagination-next.svelte | 29 + .../pagination/pagination-prev-button.svelte | 33 + .../ui/pagination/pagination-previous.svelte | 29 + .../ui/pagination/pagination.svelte | 28 + .../src/lib/components/ui/popover/index.ts | 19 + .../ui/popover/popover-close.svelte | 7 + .../ui/popover/popover-content.svelte | 31 + .../ui/popover/popover-portal.svelte | 7 + .../ui/popover/popover-trigger.svelte | 17 + .../lib/components/ui/popover/popover.svelte | 7 + .../src/lib/components/ui/progress/index.ts | 7 + .../components/ui/progress/progress.svelte | 27 + .../lib/components/ui/radio-group/index.ts | 10 + .../ui/radio-group/radio-group-item.svelte | 31 + .../ui/radio-group/radio-group.svelte | 19 + .../lib/components/ui/range-calendar/index.ts | 40 + .../range-calendar-caption.svelte | 76 + .../range-calendar/range-calendar-cell.svelte | 19 + .../range-calendar/range-calendar-day.svelte | 39 + .../range-calendar-grid-body.svelte | 7 + .../range-calendar-grid-head.svelte | 7 + .../range-calendar-grid-row.svelte | 12 + .../range-calendar/range-calendar-grid.svelte | 16 + .../range-calendar-head-cell.svelte | 19 + .../range-calendar-header.svelte | 19 + .../range-calendar-heading.svelte | 16 + .../range-calendar-month-select.svelte | 44 + .../range-calendar-month.svelte | 15 + .../range-calendar-months.svelte | 19 + .../range-calendar/range-calendar-nav.svelte | 19 + .../range-calendar-next-button.svelte | 31 + .../range-calendar-prev-button.svelte | 31 + .../range-calendar-year-select.svelte | 43 + .../ui/range-calendar/range-calendar.svelte | 112 + .../src/lib/components/ui/resizable/index.ts | 13 + .../ui/resizable/resizable-handle.svelte | 30 + .../ui/resizable/resizable-pane-group.svelte | 20 + .../lib/components/ui/scroll-area/index.ts | 10 + .../scroll-area/scroll-area-scrollbar.svelte | 31 + .../ui/scroll-area/scroll-area.svelte | 43 + .../src/lib/components/ui/select/index.ts | 37 + .../ui/select/select-content.svelte | 45 + .../ui/select/select-group-heading.svelte | 21 + .../components/ui/select/select-group.svelte | 7 + .../components/ui/select/select-item.svelte | 38 + .../components/ui/select/select-label.svelte | 20 + .../components/ui/select/select-portal.svelte | 7 + .../select/select-scroll-down-button.svelte | 20 + .../ui/select/select-scroll-up-button.svelte | 20 + .../ui/select/select-separator.svelte | 18 + .../ui/select/select-trigger.svelte | 29 + .../lib/components/ui/select/select.svelte | 11 + .../src/lib/components/ui/separator/index.ts | 7 + .../components/ui/separator/separator.svelte | 21 + .../main/src/lib/components/ui/sheet/index.ts | 34 + .../components/ui/sheet/sheet-close.svelte | 7 + .../components/ui/sheet/sheet-content.svelte | 60 + .../ui/sheet/sheet-description.svelte | 17 + .../components/ui/sheet/sheet-footer.svelte | 20 + .../components/ui/sheet/sheet-header.svelte | 20 + .../components/ui/sheet/sheet-overlay.svelte | 20 + .../components/ui/sheet/sheet-portal.svelte | 7 + .../components/ui/sheet/sheet-title.svelte | 17 + .../components/ui/sheet/sheet-trigger.svelte | 7 + .../src/lib/components/ui/sheet/sheet.svelte | 7 + .../lib/components/ui/sidebar/constants.ts | 6 + .../components/ui/sidebar/context.svelte.ts | 81 + .../src/lib/components/ui/sidebar/index.ts | 75 + .../ui/sidebar/sidebar-content.svelte | 24 + .../ui/sidebar/sidebar-footer.svelte | 21 + .../ui/sidebar/sidebar-group-action.svelte | 36 + .../ui/sidebar/sidebar-group-content.svelte | 21 + .../ui/sidebar/sidebar-group-label.svelte | 34 + .../ui/sidebar/sidebar-group.svelte | 21 + .../ui/sidebar/sidebar-header.svelte | 21 + .../ui/sidebar/sidebar-input.svelte | 21 + .../ui/sidebar/sidebar-inset.svelte | 24 + .../ui/sidebar/sidebar-menu-action.svelte | 43 + .../ui/sidebar/sidebar-menu-badge.svelte | 29 + .../ui/sidebar/sidebar-menu-button.svelte | 103 + .../ui/sidebar/sidebar-menu-item.svelte | 21 + .../ui/sidebar/sidebar-menu-skeleton.svelte | 36 + .../ui/sidebar/sidebar-menu-sub-button.svelte | 43 + .../ui/sidebar/sidebar-menu-sub-item.svelte | 21 + .../ui/sidebar/sidebar-menu-sub.svelte | 25 + .../components/ui/sidebar/sidebar-menu.svelte | 21 + .../ui/sidebar/sidebar-provider.svelte | 53 + .../components/ui/sidebar/sidebar-rail.svelte | 36 + .../ui/sidebar/sidebar-separator.svelte | 19 + .../ui/sidebar/sidebar-trigger.svelte | 35 + .../lib/components/ui/sidebar/sidebar.svelte | 104 + .../src/lib/components/ui/skeleton/index.ts | 7 + .../components/ui/skeleton/skeleton.svelte | 17 + .../src/lib/components/ui/slider/index.ts | 7 + .../lib/components/ui/slider/slider.svelte | 52 + .../src/lib/components/ui/sonner/index.ts | 1 + .../lib/components/ui/sonner/sonner.svelte | 34 + .../src/lib/components/ui/spinner/index.ts | 1 + .../lib/components/ui/spinner/spinner.svelte | 14 + .../src/lib/components/ui/switch/index.ts | 7 + .../lib/components/ui/switch/switch.svelte | 29 + .../main/src/lib/components/ui/table/index.ts | 28 + .../lib/components/ui/table/table-body.svelte | 20 + .../components/ui/table/table-caption.svelte | 20 + .../lib/components/ui/table/table-cell.svelte | 23 + .../components/ui/table/table-footer.svelte | 20 + .../lib/components/ui/table/table-head.svelte | 23 + .../components/ui/table/table-header.svelte | 20 + .../lib/components/ui/table/table-row.svelte | 23 + .../src/lib/components/ui/table/table.svelte | 22 + apps/main/src/lib/components/ui/tabs/index.ts | 16 + .../components/ui/tabs/tabs-content.svelte | 17 + .../lib/components/ui/tabs/tabs-list.svelte | 20 + .../components/ui/tabs/tabs-trigger.svelte | 20 + .../src/lib/components/ui/tabs/tabs.svelte | 19 + .../src/lib/components/ui/textarea/index.ts | 7 + .../components/ui/textarea/textarea.svelte | 23 + .../lib/components/ui/toggle-group/index.ts | 10 + .../ui/toggle-group/toggle-group-item.svelte | 35 + .../ui/toggle-group/toggle-group.svelte | 59 + .../src/lib/components/ui/toggle/index.ts | 13 + .../lib/components/ui/toggle/toggle.svelte | 52 + .../src/lib/components/ui/tooltip/index.ts | 19 + .../ui/tooltip/tooltip-content.svelte | 52 + .../ui/tooltip/tooltip-portal.svelte | 7 + .../ui/tooltip/tooltip-provider.svelte | 7 + .../ui/tooltip/tooltip-trigger.svelte | 7 + .../lib/components/ui/tooltip/tooltip.svelte | 7 + apps/main/src/lib/core/constants.ts | 56 + apps/main/src/lib/core/server.utils.ts | 25 + .../src/lib/domains/account/account.remote.ts | 168 + .../lib/domains/account/account.vm.svelte.ts | 153 + .../account/sessions/sessions-card.svelte | 182 + .../account/sessions/sessions.vm.svelte.ts | 209 + .../notifications/notification.vm.svelte.ts | 235 + .../notifications/notifications-table.svelte | 566 ++ .../notifications/notifications.remote.ts | 155 + .../domains/security/2fa-verify.vm.svelte.ts | 160 + .../src/lib/domains/security/2fa.vm.svelte.ts | 234 + .../lib/domains/security/auth.vm.svelte.ts | 71 + .../domains/security/email-login-form.svelte | 78 + .../lib/domains/security/two-fa-card.svelte | 324 + .../src/lib/domains/security/twofa.remote.ts | 205 + apps/main/src/lib/global.stores.ts | 14 + apps/main/src/lib/hooks/is-mobile.svelte.ts | 9 + apps/main/src/lib/make-client.ts | 21 + apps/main/src/lib/utils.ts | 13 + apps/main/src/routes/(main)/+layout.server.ts | 13 + apps/main/src/routes/(main)/+layout.svelte | 58 + apps/main/src/routes/(main)/+page.server.ts | 6 + apps/main/src/routes/(main)/+page.svelte | 12 + .../src/routes/(main)/account/+layout.svelte | 134 + .../src/routes/(main)/account/+page.svelte | 261 + .../src/routes/(main)/dashboard/+page.svelte | 267 + .../src/routes/(main)/devices/+page.svelte | 1 + .../main/src/routes/(main)/links/+page.svelte | 1 + .../routes/(main)/notifications/+page.svelte | 27 + apps/main/src/routes/+layout.svelte | 20 + .../src/routes/api/debug/users/+server.ts | 129 + .../src/routes/auth/2fa/+layout.server.ts | 14 + apps/main/src/routes/auth/2fa/+page.svelte | 285 + .../src/routes/auth/login/+page.server.ts | 13 + apps/main/src/routes/auth/login/+page.svelte | 71 + apps/main/src/routes/layout.css | 196 + apps/main/static/favicon.png | Bin 0 -> 303316 bytes apps/main/static/fonts/manrope-variable.ttf | Bin 0 -> 164936 bytes apps/main/static/images/avatar.png | Bin 0 -> 2011 bytes apps/main/static/robots.txt | 3 + apps/main/svelte.config.js | 20 + apps/main/tsconfig.json | 20 + apps/main/vite.config.ts | 46 + apps/orchestrator/package.json | 33 + apps/orchestrator/src/index.ts | 29 + apps/orchestrator/src/instrumentation.ts | 50 + apps/orchestrator/tsconfig.json | 33 + dev/README.md | 42 + dev/clickhouse-cluster.xml | 75 + dev/clickhouse-config.xml | 1142 +++ dev/clickhouse-custom-function.xml | 21 + dev/clickhouse-users.xml | 123 + dev/docker-compose.dev.yaml | 212 + dev/otel-collector-config.yaml | 124 + dev/otel-collector-opamp-config.yaml | 1 + dockerfiles/app_builder.Dockerfile | 47 + dockerfiles/main.Dockerfile | 27 + dockerfiles/migrator.Dockerfile | 18 + dockerfiles/processor.Dockerfile | 21 + package.json | 38 + packages/db/drizzle.config.ts | 13 + packages/db/index.ts | 11 + packages/db/package.json | 27 + packages/db/schema/auth.schema.ts | 52 + packages/db/schema/better.auth.schema.ts | 75 + packages/db/schema/general.schema.ts | 49 + packages/db/schema/index.ts | 4 + packages/db/schema/task.schema.ts | 35 + packages/keystore/build.queue.ts | 17 + packages/keystore/index.ts | 18 + packages/keystore/package.json | 15 + packages/logger/index.ts | 281 + packages/logger/package.json | 17 + packages/logger/tsconfig.json | 5 + packages/logic/core/array.utils.ts | 7 + packages/logic/core/data/countries.ts | 264 + packages/logic/core/data/phonecc.ts | 1227 +++ packages/logic/core/date.utils.ts | 83 + packages/logic/core/error.ts | 8 + packages/logic/core/flow.execution.context.ts | 5 + packages/logic/core/hash.utils.ts | 31 + packages/logic/core/http.telemetry.ts | 88 + packages/logic/core/observability.ts | 80 + packages/logic/core/pagination.utils.ts | 12 + packages/logic/core/rate.limiter.ts | 40 + packages/logic/core/settings.ts | 1 + packages/logic/core/string.utils/index.ts | 106 + .../core/string.utils/sequence.matcher.ts | 555 ++ packages/logic/domains/2fa/controller.ts | 396 + packages/logic/domains/2fa/data.ts | 48 + packages/logic/domains/2fa/errors.ts | 180 + packages/logic/domains/2fa/repository.ts | 695 ++ .../logic/domains/2fa/sensitive-actions.ts | 43 + packages/logic/domains/auth/config.base.ts | 99 + packages/logic/domains/auth/controller.ts | 60 + packages/logic/domains/auth/errors.ts | 32 + .../logic/domains/notifications/controller.ts | 96 + packages/logic/domains/notifications/data.ts | 115 + .../logic/domains/notifications/errors.ts | 78 + .../logic/domains/notifications/repository.ts | 453 + packages/logic/domains/tasks/controller.ts | 72 + packages/logic/domains/tasks/data.ts | 71 + packages/logic/domains/tasks/errors.ts | 87 + packages/logic/domains/tasks/repository.ts | 163 + .../logic/domains/user/account.repository.ts | 250 + packages/logic/domains/user/controller.ts | 96 + packages/logic/domains/user/data.ts | 159 + packages/logic/domains/user/errors.ts | 77 + packages/logic/domains/user/repository.ts | 420 + packages/logic/package.json | 40 + packages/logic/tsconfig.json | 16 + packages/result/index.ts | 81 + packages/result/package.json | 11 + packages/settings/index.ts | 181 + packages/settings/package.json | 15 + pnpm-lock.yaml | 7616 +++++++++++++++++ pnpm-workspace.yaml | 9 + scripts/generate_example_env_file.py | 95 + scripts/migrate.sh | 7 + scripts/populate.env.sh | 15 + scripts/prod.start.sh | 14 + scripts/spinup.dev.env.sh | 3 + scripts/teardown.dev.env.sh | 3 + tsconfig.json | 5 + turbo.json | 24 + 544 files changed, 33060 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 README.md create mode 100644 apps/front/package.json create mode 100644 apps/front/src/index.ts create mode 100644 apps/front/src/instrumentation.ts create mode 100644 apps/front/tsconfig.json create mode 100644 apps/front/view.html create mode 100644 apps/main/.gitignore create mode 100644 apps/main/.npmrc create mode 100644 apps/main/.prettierignore create mode 100644 apps/main/components.json create mode 100644 apps/main/package.json create mode 100644 apps/main/src/app.d.ts create mode 100644 apps/main/src/app.html create mode 100644 apps/main/src/demo.spec.ts create mode 100644 apps/main/src/hooks.server.ts create mode 100644 apps/main/src/instrumentation.server.ts create mode 100644 apps/main/src/lib/auth.client.ts create mode 100644 apps/main/src/lib/components/app-sidebar.svelte create mode 100644 apps/main/src/lib/components/atoms/button-text.svelte create mode 100644 apps/main/src/lib/components/atoms/icon.svelte create mode 100644 apps/main/src/lib/components/atoms/title.svelte create mode 100644 apps/main/src/lib/components/molecules/max-width-wrapper.svelte create mode 100644 apps/main/src/lib/components/nav-main.svelte create mode 100644 apps/main/src/lib/components/nav-user.svelte create mode 100644 apps/main/src/lib/components/team-switcher.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion-content.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion-item.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/accordion.svelte create mode 100644 apps/main/src/lib/components/ui/accordion/index.ts create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte create mode 100644 apps/main/src/lib/components/ui/alert-dialog/index.ts create mode 100644 apps/main/src/lib/components/ui/alert/alert-description.svelte create mode 100644 apps/main/src/lib/components/ui/alert/alert-title.svelte create mode 100644 apps/main/src/lib/components/ui/alert/alert.svelte create mode 100644 apps/main/src/lib/components/ui/alert/index.ts create mode 100644 apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte create mode 100644 apps/main/src/lib/components/ui/aspect-ratio/index.ts create mode 100644 apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte create mode 100644 apps/main/src/lib/components/ui/avatar/avatar-image.svelte create mode 100644 apps/main/src/lib/components/ui/avatar/avatar.svelte create mode 100644 apps/main/src/lib/components/ui/avatar/index.ts create mode 100644 apps/main/src/lib/components/ui/badge/badge.svelte create mode 100644 apps/main/src/lib/components/ui/badge/index.ts create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte create mode 100644 apps/main/src/lib/components/ui/breadcrumb/index.ts create mode 100644 apps/main/src/lib/components/ui/button-group/button-group-separator.svelte create mode 100644 apps/main/src/lib/components/ui/button-group/button-group-text.svelte create mode 100644 apps/main/src/lib/components/ui/button-group/button-group.svelte create mode 100644 apps/main/src/lib/components/ui/button-group/index.ts create mode 100644 apps/main/src/lib/components/ui/button/button.svelte create mode 100644 apps/main/src/lib/components/ui/button/index.ts create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-caption.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-cell.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-day.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-grid.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-header.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-heading.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-month.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-months.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-nav.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/calendar.svelte create mode 100644 apps/main/src/lib/components/ui/calendar/index.ts create mode 100644 apps/main/src/lib/components/ui/card/card-action.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-content.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-description.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-footer.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-header.svelte create mode 100644 apps/main/src/lib/components/ui/card/card-title.svelte create mode 100644 apps/main/src/lib/components/ui/card/card.svelte create mode 100644 apps/main/src/lib/components/ui/card/index.ts create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-content.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-item.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-next.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel-previous.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/carousel.svelte create mode 100644 apps/main/src/lib/components/ui/carousel/context.ts create mode 100644 apps/main/src/lib/components/ui/carousel/index.ts create mode 100644 apps/main/src/lib/components/ui/chart/chart-container.svelte create mode 100644 apps/main/src/lib/components/ui/chart/chart-style.svelte create mode 100644 apps/main/src/lib/components/ui/chart/chart-tooltip.svelte create mode 100644 apps/main/src/lib/components/ui/chart/chart-utils.ts create mode 100644 apps/main/src/lib/components/ui/chart/index.ts create mode 100644 apps/main/src/lib/components/ui/checkbox/checkbox.svelte create mode 100644 apps/main/src/lib/components/ui/checkbox/index.ts create mode 100644 apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte create mode 100644 apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/collapsible/collapsible.svelte create mode 100644 apps/main/src/lib/components/ui/collapsible/index.ts create mode 100644 apps/main/src/lib/components/ui/command/command-dialog.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-empty.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-group.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-input.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-item.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-link-item.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-list.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-loading.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-separator.svelte create mode 100644 apps/main/src/lib/components/ui/command/command-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/command/command.svelte create mode 100644 apps/main/src/lib/components/ui/command/index.ts create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/context-menu.svelte create mode 100644 apps/main/src/lib/components/ui/context-menu/index.ts create mode 100644 apps/main/src/lib/components/ui/data-table/data-table.svelte.ts create mode 100644 apps/main/src/lib/components/ui/data-table/flex-render.svelte create mode 100644 apps/main/src/lib/components/ui/data-table/index.ts create mode 100644 apps/main/src/lib/components/ui/data-table/render-helpers.ts create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-close.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-content.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-description.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-footer.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-header.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-portal.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-title.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/dialog.svelte create mode 100644 apps/main/src/lib/components/ui/dialog/index.ts create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-close.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-content.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-description.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-footer.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-header.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-nested.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-portal.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-title.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/drawer.svelte create mode 100644 apps/main/src/lib/components/ui/drawer/index.ts create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte create mode 100644 apps/main/src/lib/components/ui/dropdown-menu/index.ts create mode 100644 apps/main/src/lib/components/ui/empty/empty-content.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-description.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-header.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-media.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty-title.svelte create mode 100644 apps/main/src/lib/components/ui/empty/empty.svelte create mode 100644 apps/main/src/lib/components/ui/empty/index.ts create mode 100644 apps/main/src/lib/components/ui/field/field-content.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-description.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-error.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-group.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-label.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-legend.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-separator.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-set.svelte create mode 100644 apps/main/src/lib/components/ui/field/field-title.svelte create mode 100644 apps/main/src/lib/components/ui/field/field.svelte create mode 100644 apps/main/src/lib/components/ui/field/index.ts create mode 100644 apps/main/src/lib/components/ui/form/form-button.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-description.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-element-field.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-field-errors.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-field.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-fieldset.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-label.svelte create mode 100644 apps/main/src/lib/components/ui/form/form-legend.svelte create mode 100644 apps/main/src/lib/components/ui/form/index.ts create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card-content.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card-portal.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/hover-card.svelte create mode 100644 apps/main/src/lib/components/ui/hover-card/index.ts create mode 100644 apps/main/src/lib/components/ui/input-group/index.ts create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-addon.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-button.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-input.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-text.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte create mode 100644 apps/main/src/lib/components/ui/input-group/input-group.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/index.ts create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp-group.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp-separator.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp-slot.svelte create mode 100644 apps/main/src/lib/components/ui/input-otp/input-otp.svelte create mode 100644 apps/main/src/lib/components/ui/input/index.ts create mode 100644 apps/main/src/lib/components/ui/input/input.svelte create mode 100644 apps/main/src/lib/components/ui/item/index.ts create mode 100644 apps/main/src/lib/components/ui/item/item-actions.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-content.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-description.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-footer.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-group.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-header.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-media.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-separator.svelte create mode 100644 apps/main/src/lib/components/ui/item/item-title.svelte create mode 100644 apps/main/src/lib/components/ui/item/item.svelte create mode 100644 apps/main/src/lib/components/ui/kbd/index.ts create mode 100644 apps/main/src/lib/components/ui/kbd/kbd-group.svelte create mode 100644 apps/main/src/lib/components/ui/kbd/kbd.svelte create mode 100644 apps/main/src/lib/components/ui/label/index.ts create mode 100644 apps/main/src/lib/components/ui/label/label.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/index.ts create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-checkbox-item.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-content.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-group.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-item.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-label.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-menu.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-portal.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-radio-item.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-separator.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-shortcut.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-sub-content.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-sub-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-sub.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/menubar/menubar.svelte create mode 100644 apps/main/src/lib/components/ui/native-select/index.ts create mode 100644 apps/main/src/lib/components/ui/native-select/native-select-opt-group.svelte create mode 100644 apps/main/src/lib/components/ui/native-select/native-select-option.svelte create mode 100644 apps/main/src/lib/components/ui/native-select/native-select.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/index.ts create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-content.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-indicator.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-link.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-list.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu-viewport.svelte create mode 100644 apps/main/src/lib/components/ui/navigation-menu/navigation-menu.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/index.ts create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-content.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-ellipsis.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-item.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-link.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-next-button.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-next.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-prev-button.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination-previous.svelte create mode 100644 apps/main/src/lib/components/ui/pagination/pagination.svelte create mode 100644 apps/main/src/lib/components/ui/popover/index.ts create mode 100644 apps/main/src/lib/components/ui/popover/popover-close.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover-content.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover-portal.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/popover/popover.svelte create mode 100644 apps/main/src/lib/components/ui/progress/index.ts create mode 100644 apps/main/src/lib/components/ui/progress/progress.svelte create mode 100644 apps/main/src/lib/components/ui/radio-group/index.ts create mode 100644 apps/main/src/lib/components/ui/radio-group/radio-group-item.svelte create mode 100644 apps/main/src/lib/components/ui/radio-group/radio-group.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/index.ts create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-caption.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-cell.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-day.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-grid.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-header.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-heading.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-month-select.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-month.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-months.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-nav.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar-year-select.svelte create mode 100644 apps/main/src/lib/components/ui/range-calendar/range-calendar.svelte create mode 100644 apps/main/src/lib/components/ui/resizable/index.ts create mode 100644 apps/main/src/lib/components/ui/resizable/resizable-handle.svelte create mode 100644 apps/main/src/lib/components/ui/resizable/resizable-pane-group.svelte create mode 100644 apps/main/src/lib/components/ui/scroll-area/index.ts create mode 100644 apps/main/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte create mode 100644 apps/main/src/lib/components/ui/scroll-area/scroll-area.svelte create mode 100644 apps/main/src/lib/components/ui/select/index.ts create mode 100644 apps/main/src/lib/components/ui/select/select-content.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-group-heading.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-group.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-item.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-label.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-portal.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-scroll-down-button.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-scroll-up-button.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-separator.svelte create mode 100644 apps/main/src/lib/components/ui/select/select-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/select/select.svelte create mode 100644 apps/main/src/lib/components/ui/separator/index.ts create mode 100644 apps/main/src/lib/components/ui/separator/separator.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/index.ts create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-close.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-content.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-description.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-footer.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-header.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-overlay.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-portal.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-title.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/sheet/sheet.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/constants.ts create mode 100644 apps/main/src/lib/components/ui/sidebar/context.svelte.ts create mode 100644 apps/main/src/lib/components/ui/sidebar/index.ts create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-content.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-footer.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group-action.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group-content.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group-label.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-group.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-header.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-input.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-inset.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-action.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-button.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-item.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-menu.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-provider.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-rail.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-separator.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/sidebar/sidebar.svelte create mode 100644 apps/main/src/lib/components/ui/skeleton/index.ts create mode 100644 apps/main/src/lib/components/ui/skeleton/skeleton.svelte create mode 100644 apps/main/src/lib/components/ui/slider/index.ts create mode 100644 apps/main/src/lib/components/ui/slider/slider.svelte create mode 100644 apps/main/src/lib/components/ui/sonner/index.ts create mode 100644 apps/main/src/lib/components/ui/sonner/sonner.svelte create mode 100644 apps/main/src/lib/components/ui/spinner/index.ts create mode 100644 apps/main/src/lib/components/ui/spinner/spinner.svelte create mode 100644 apps/main/src/lib/components/ui/switch/index.ts create mode 100644 apps/main/src/lib/components/ui/switch/switch.svelte create mode 100644 apps/main/src/lib/components/ui/table/index.ts create mode 100644 apps/main/src/lib/components/ui/table/table-body.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-caption.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-cell.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-footer.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-head.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-header.svelte create mode 100644 apps/main/src/lib/components/ui/table/table-row.svelte create mode 100644 apps/main/src/lib/components/ui/table/table.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/index.ts create mode 100644 apps/main/src/lib/components/ui/tabs/tabs-content.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/tabs-list.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/tabs-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/tabs/tabs.svelte create mode 100644 apps/main/src/lib/components/ui/textarea/index.ts create mode 100644 apps/main/src/lib/components/ui/textarea/textarea.svelte create mode 100644 apps/main/src/lib/components/ui/toggle-group/index.ts create mode 100644 apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte create mode 100644 apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte create mode 100644 apps/main/src/lib/components/ui/toggle/index.ts create mode 100644 apps/main/src/lib/components/ui/toggle/toggle.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/index.ts create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte create mode 100644 apps/main/src/lib/components/ui/tooltip/tooltip.svelte create mode 100644 apps/main/src/lib/core/constants.ts create mode 100644 apps/main/src/lib/core/server.utils.ts create mode 100644 apps/main/src/lib/domains/account/account.remote.ts create mode 100644 apps/main/src/lib/domains/account/account.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/account/sessions/sessions-card.svelte create mode 100644 apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/notifications/notification.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/notifications/notifications-table.svelte create mode 100644 apps/main/src/lib/domains/notifications/notifications.remote.ts create mode 100644 apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/security/2fa.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/security/auth.vm.svelte.ts create mode 100644 apps/main/src/lib/domains/security/email-login-form.svelte create mode 100644 apps/main/src/lib/domains/security/two-fa-card.svelte create mode 100644 apps/main/src/lib/domains/security/twofa.remote.ts create mode 100644 apps/main/src/lib/global.stores.ts create mode 100644 apps/main/src/lib/hooks/is-mobile.svelte.ts create mode 100644 apps/main/src/lib/make-client.ts create mode 100644 apps/main/src/lib/utils.ts create mode 100644 apps/main/src/routes/(main)/+layout.server.ts create mode 100644 apps/main/src/routes/(main)/+layout.svelte create mode 100644 apps/main/src/routes/(main)/+page.server.ts create mode 100644 apps/main/src/routes/(main)/+page.svelte create mode 100644 apps/main/src/routes/(main)/account/+layout.svelte create mode 100644 apps/main/src/routes/(main)/account/+page.svelte create mode 100644 apps/main/src/routes/(main)/dashboard/+page.svelte create mode 100644 apps/main/src/routes/(main)/devices/+page.svelte create mode 100644 apps/main/src/routes/(main)/links/+page.svelte create mode 100644 apps/main/src/routes/(main)/notifications/+page.svelte create mode 100644 apps/main/src/routes/+layout.svelte create mode 100644 apps/main/src/routes/api/debug/users/+server.ts create mode 100644 apps/main/src/routes/auth/2fa/+layout.server.ts create mode 100644 apps/main/src/routes/auth/2fa/+page.svelte create mode 100644 apps/main/src/routes/auth/login/+page.server.ts create mode 100644 apps/main/src/routes/auth/login/+page.svelte create mode 100644 apps/main/src/routes/layout.css create mode 100644 apps/main/static/favicon.png create mode 100644 apps/main/static/fonts/manrope-variable.ttf create mode 100644 apps/main/static/images/avatar.png create mode 100644 apps/main/static/robots.txt create mode 100644 apps/main/svelte.config.js create mode 100644 apps/main/tsconfig.json create mode 100644 apps/main/vite.config.ts create mode 100644 apps/orchestrator/package.json create mode 100644 apps/orchestrator/src/index.ts create mode 100644 apps/orchestrator/src/instrumentation.ts create mode 100644 apps/orchestrator/tsconfig.json create mode 100644 dev/README.md create mode 100644 dev/clickhouse-cluster.xml create mode 100644 dev/clickhouse-config.xml create mode 100644 dev/clickhouse-custom-function.xml create mode 100644 dev/clickhouse-users.xml create mode 100644 dev/docker-compose.dev.yaml create mode 100644 dev/otel-collector-config.yaml create mode 100644 dev/otel-collector-opamp-config.yaml create mode 100644 dockerfiles/app_builder.Dockerfile create mode 100644 dockerfiles/main.Dockerfile create mode 100644 dockerfiles/migrator.Dockerfile create mode 100644 dockerfiles/processor.Dockerfile create mode 100644 package.json create mode 100644 packages/db/drizzle.config.ts create mode 100644 packages/db/index.ts create mode 100644 packages/db/package.json create mode 100644 packages/db/schema/auth.schema.ts create mode 100644 packages/db/schema/better.auth.schema.ts create mode 100644 packages/db/schema/general.schema.ts create mode 100644 packages/db/schema/index.ts create mode 100644 packages/db/schema/task.schema.ts create mode 100644 packages/keystore/build.queue.ts create mode 100644 packages/keystore/index.ts create mode 100644 packages/keystore/package.json create mode 100644 packages/logger/index.ts create mode 100644 packages/logger/package.json create mode 100644 packages/logger/tsconfig.json create mode 100644 packages/logic/core/array.utils.ts create mode 100644 packages/logic/core/data/countries.ts create mode 100644 packages/logic/core/data/phonecc.ts create mode 100644 packages/logic/core/date.utils.ts create mode 100644 packages/logic/core/error.ts create mode 100644 packages/logic/core/flow.execution.context.ts create mode 100644 packages/logic/core/hash.utils.ts create mode 100644 packages/logic/core/http.telemetry.ts create mode 100644 packages/logic/core/observability.ts create mode 100644 packages/logic/core/pagination.utils.ts create mode 100644 packages/logic/core/rate.limiter.ts create mode 100644 packages/logic/core/settings.ts create mode 100644 packages/logic/core/string.utils/index.ts create mode 100644 packages/logic/core/string.utils/sequence.matcher.ts create mode 100644 packages/logic/domains/2fa/controller.ts create mode 100644 packages/logic/domains/2fa/data.ts create mode 100644 packages/logic/domains/2fa/errors.ts create mode 100644 packages/logic/domains/2fa/repository.ts create mode 100644 packages/logic/domains/2fa/sensitive-actions.ts create mode 100644 packages/logic/domains/auth/config.base.ts create mode 100644 packages/logic/domains/auth/controller.ts create mode 100644 packages/logic/domains/auth/errors.ts create mode 100644 packages/logic/domains/notifications/controller.ts create mode 100644 packages/logic/domains/notifications/data.ts create mode 100644 packages/logic/domains/notifications/errors.ts create mode 100644 packages/logic/domains/notifications/repository.ts create mode 100644 packages/logic/domains/tasks/controller.ts create mode 100644 packages/logic/domains/tasks/data.ts create mode 100644 packages/logic/domains/tasks/errors.ts create mode 100644 packages/logic/domains/tasks/repository.ts create mode 100644 packages/logic/domains/user/account.repository.ts create mode 100644 packages/logic/domains/user/controller.ts create mode 100644 packages/logic/domains/user/data.ts create mode 100644 packages/logic/domains/user/errors.ts create mode 100644 packages/logic/domains/user/repository.ts create mode 100644 packages/logic/package.json create mode 100644 packages/logic/tsconfig.json create mode 100644 packages/result/index.ts create mode 100644 packages/result/package.json create mode 100644 packages/settings/index.ts create mode 100644 packages/settings/package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100755 scripts/generate_example_env_file.py create mode 100755 scripts/migrate.sh create mode 100755 scripts/populate.env.sh create mode 100755 scripts/prod.start.sh create mode 100755 scripts/spinup.dev.env.sh create mode 100755 scripts/teardown.dev.env.sh create mode 100644 tsconfig.json create mode 100644 turbo.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2e2ad5c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,61 @@ +.zed + +# Dependencies +node_modules +.pnp +.pnp.js + +__pycache__ +.venv + +# ignore generated log files +**/logs/**.log +**/logs/**.log.gz +**/logs/**-audit.json + +**/data/credentials/** +**/testdocs/** + +ot_res.json +out.json +payload.json + +screenshots/*.jpeg +screenshots/*.png +screenshots/*.jpg + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist +.svelte-kit + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + +creds.md + +onlydevs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a2515b0 --- /dev/null +++ b/.env.example @@ -0,0 +1,47 @@ +APP_NAME=${{project.APP_NAME}} +NODE_ENV=${{project.NODE_ENV}} +LOG_LEVEL=${{project.LOG_LEVEL}} + +REDIS_URL=${{project.REDIS_URL}} +DATABASE_URL=${{project.DATABASE_URL}} + +INTERNAL_API_KEY=${{project.INTERNAL_API_KEY}} +DEBUG_KEY=${{project.DEBUG_KEY}} + +PUBLIC_URL=${{project.PUBLIC_URL}} + +PROCESSOR_API_URL=${{project.PROCESSOR_API_URL}} +APP_BUILDER_API_URL=${{project.APP_BUILDER_API_URL}} +APP_BUILDER_ASSETS_PUBLIC_URL=${{project.APP_BUILDER_ASSETS_PUBLIC_URL}} + +CLIENT_DOWNLOADED_APK_NAME=${{project.CLIENT_DOWNLOADED_APK_NAME}} + +MOBILE_APP_API_URL=${{project.MOBILE_APP_API_URL}} + +BETTER_AUTH_SECRET=${{project.BETTER_AUTH_SECRET}} +BETTER_AUTH_URL=${{project.BETTER_AUTH_URL}} + +TWOFA_SECRET=${{project.TWOFA_SECRET}} +TWO_FA_SESSION_EXPIRY_MINUTES=${{project.TWO_FA_SESSION_EXPIRY_MINUTES}} +TWO_FA_REQUIRED_HOURS=${{project.TWO_FA_REQUIRED_HOURS}} + +DEFAULT_ADMIN_EMAIL=${{project.DEFAULT_ADMIN_EMAIL}} +DEFAULT_ADMIN_PASSWORD=${{project.DEFAULT_ADMIN_PASSWORD}} + +OTEL_SERVICE_NAME=${{project.OTEL_SERVICE_NAME}} +OTEL_TRACES_EXPORTER=${{project.OTEL_TRACES_EXPORTER}} +OTEL_EXPORTER_OTLP_HTTP_ENDPOINT=${{project.OTEL_EXPORTER_OTLP_HTTP_ENDPOINT}} +OTEL_EXPORTER_OTLP_ENDPOINT=${{project.OTEL_EXPORTER_OTLP_ENDPOINT}} +OTEL_EXPORTER_OTLP_PROTOCOL=${{project.OTEL_EXPORTER_OTLP_PROTOCOL}} +OTEL_RESOURCE_ATTRIBUTES=${{project.OTEL_RESOURCE_ATTRIBUTES}} + +R2_BUCKET_NAME=${{project.R2_BUCKET_NAME}} +R2_REGION=${{project.R2_REGION}} +R2_ENDPOINT=${{project.R2_ENDPOINT}} +R2_ACCESS_KEY=${{project.R2_ACCESS_KEY}} +R2_SECRET_KEY=${{project.R2_SECRET_KEY}} +R2_PUBLIC_URL=${{project.R2_PUBLIC_URL}} + +MAX_FILE_SIZE=${{project.MAX_FILE_SIZE}} +ALLOWED_MIME_TYPES=${{project.ALLOWED_MIME_TYPES}} +ALLOWED_EXTENSIONS=${{project.ALLOWED_EXTENSIONS}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a865cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,84 @@ +.zed + +mobile/**/*.iml +mobile/**/.gradle +mobile/**/local.properties +mobile/**/.idea/caches +mobile/**/.idea/libraries +mobile/**/.idea/modules.xml +mobile/**/.idea/workspace.xml +mobile/**/.idea/navEditor.xml +mobile/**/.idea/assetWizardSettings.xml +mobile/**/.DS_Store +mobile/**/build +mobile/**/captures +mobile/**/.externalNativeBuild +mobile/**/.cxx +mobile/**/local.properties + + +# Dependencies +node_modules +.pnp +.pnp.js + +__pycache__ +.venv + +secret.md + +# ignore generated log files +**/logs/**.log +**/logs/**.log.gz +**/logs/**-audit.json + +**/data/credentials/** +**/testdocs/** + +scripts/whatsapp.req.sh + +ot_res.json +out.json +payload.json + +screenshots/*.jpeg +screenshots/*.png +screenshots/*.jpg + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist +.svelte-kit + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + +**.apk + +creds.md + +onlydevs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8727886 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,198 @@ +# AGENTS.md + +This document defines the laws, principles, and rule sets that govern this codebase. Any agent or developer making changes has to adhere to these rules. + +**Before starting off** — Read the README.md file to understand the project's goals and objectives. + +--- + +## Agent Rules (Override Everything) + +1. **No testing by yourself** — All testing is done by the team. +2. **No assumptions about code or domain logic** — Always confirm and be sure before making changes. +3. **No running scripts** — Do not run build, dev, test, or migrate scripts unless explicitly approved. +4. **No touching migration files** — Do not mess with the `migrations` sql dir, as those are generated manually via drizzle orm + +More rules are only to be added by the human, in case such a suggestion becomes viable. + +--- + +## 1. Project Overview + +- **Monorepo**: Turbo repo +- **Package Manager**: pnpm +- **Language**: TypeScript everywhere +- **Node**: >= 24 + +### Applications + +| App | Purpose | +| ---------------- | ----------------------------------------------------------------------------------------------------------------- | +| `apps/main` | SvelteKit UI — the primary user-facing application | +| `apps/processor` | Hono web server — intended for asynchronous processing (jobs, workers). Currently minimal; structure is evolving. | + +### Packages + +Packages live under `packages/` and are **standalone, modular** pieces consumed by apps: + +| Package | Purpose | +| --------------- | --------------------------------------------- | +| `@pkg/logic` | Domain logic (DDD, controllers, repositories) | +| `@pkg/db` | Drizzle schema, database access | +| `@pkg/logger` | Logging, `getError` for error construction | +| `@pkg/result` | Result type, `ERROR_CODES`, `tryCatch` | +| `@pkg/keystore` | Redis instance (sessions, 2FA, etc.) | +| `@pkg/settings` | App settings / env config | + +### Data Stores + +- **PostgreSQL (Drizzle)** — Primary relational data (auth, users, accounts, etc.) +- **Redis (Valkey)** — Sessions, 2FA verification state (via `@pkg/keystore`) + +Additional stores (NoSQL DBs, R2, etc.) may be introduced later. Follow existing domain patterns when adding new data access. + +--- + +## 2. The Logic Package: DDD + Layered Architecture + +The `@pkg/logic` package contains **all domain logic**. It follows: + +1. **Domain-Driven Design (DDD)** — Bounded contexts as domains +2. **Layered architecture** — Clear separation of concerns +3. **Result-style error handling** — Errors as values; avoid try-catch in domain code + +### Domain Structure + +Each domain is a folder under `packages/logic/domains/`: + +``` +domains/ + / + data.ts # Types, schemas (Valibot) + repository.ts # Data access + controller.ts # Use cases / application logic + errors.ts # Domain-specific error constructors (using getError) +``` + +The logic package is **pure domain logic** — no HTTP routes or routers. API exposure is handled by the main app via SvelteKit remote functions. Auth uses `config.base.ts` with better-auth. Add new domains as needed; mirror existing patterns. + +### Path Aliases (logic package) + +- `@/*` → `./*` +- `@domains/*` → `./domains/*` +- `@core/*` → `./core/*` + +### Flow Execution Context + +Domain operations receive a `FlowExecCtx` (`fctx`) for tracing and audit: + +```ts +type FlowExecCtx = { + flowId: string; + userId?: string; + sessionId?: string; +}; +``` + +--- + +## 3. Error Handling: Result Pattern (neverthrow) + +Errors are **values**, not exceptions. The codebase uses Result-style handling. + +### Current Conventions + +1. **Logic package** — Uses `neverthrow` (`ResultAsync`, `okAsync`, `errAsync`) for async operations that may fail. +2. **`@pkg/result`** — Provides `Result`, `ERROR_CODES`, and `tryCatch()`. The `Result` type is legacy; So don't reach for it primarily. +3. Use `ERROR_CODES` for consistent error codes. +4. **`getError()`** — From `@pkg/logger`. Use at boundaries when converting a thrown error to an `Err` object: + `return getError({ code: ERROR_CODES.XXX, message: "...", description: "...", detail: "..." }, e)`. +5. **Domain errors** — Each domain has an `errors.ts` that exports error constructors built with `getError`. Use these instead of ad-hoc error objects. +6. **Check before use** — Always check `result.isOk()` / `result.isErr()` before using `result.value`; never assume success. + +### Err Shape + +```ts +type Err = { + flowId?: string; + code: string; + message: string; + description: string; + detail: string; + actionable?: boolean; + error?: any; + // Flexible, but more "defined base fields" in the future +}; +``` + +--- + +## 4. Frontend (Main App) + +The main app is a **SvelteKit** application with a domain-driven UI structure. + +### Structure + +- **Routes**: File-based routing under `src/routes/`. Layout groups (e.g. `(main)`, `auth`) wrap related pages. +- **Domain-driven UI**: Feature code lives under `src/lib/domains//` — each domain has its own folder with view models, components, and remote functions. +- **View Model (VM) pattern**: Domain logic and state for a screen live in `*.vm.svelte.ts` classes. VMs hold reactive state (`$state`), orchestrate remote function calls, and expose methods. Pages import and use a VM instance. + +### SvelteKit Remote Functions + +The main app uses **SvelteKit remote functions** as the primary API layer — replacing Hono routers in the logic package. Each domain has a `*.remote.ts` file that exposes `query` (reads) and `command` (writes) functions, called directly from VMs. Auth context and `FlowExecCtx` are built inside each remote function from `event.locals` via helpers in `$lib/core/server.utils`. + +Naming convention: `*SQ` for queries, `*SC` for commands. + +### Global Stores + +Shared state (`user`, `session`, `breadcrumbs`) lives in `$lib/global.stores.ts`. + +### Conventions + +- Pages are thin: they mount a VM, render components, and wire up lifecycle. +- VMs own async flows, polling, and error handling for their domain. +- VMs call remote functions directly; remote functions invoke logic controllers. +- UI components under `$lib/components/` are shared; domain-specific components live in `$lib/domains//`. + +--- + +## 5. Processor App + +The processor is a **Hono** server intended for **background work** and async jobs. Its structure is still evolving and it is to be updated soon. + +When logic is added, processing logic should live under `src/domains//` and call into `@pkg/logic` controllers and repositories. + +--- + +## 6. Observability + +The stack uses **OpenTelemetry** end-to-end for traces, logs, and metrics, shipped to a **SigNoz** instance (via OTel Collector). + +### How it fits together + +- **`apps/main`** bootstraps the OTel SDK in `instrumentation.server.ts` (auto-instrumentation via `@opentelemetry/sdk-node`). SvelteKit's `tracing` and `instrumentation` experimental flags wire this into the request lifecycle. +- **`@pkg/logger`** ships Winston logs to OTel via `OpenTelemetryTransportV3` — logs are correlated with active traces automatically. +- **`@pkg/logic/core/observability.ts`** provides two tracing helpers for domain code: + - `traceResultAsync` — wraps a `ResultAsync` operation in an OTel span. Use this in repositories and controllers. + - `withFlowSpan` — lower-level span wrapper for non-Result async code. +- Both helpers accept `fctx` and stamp spans with `flow.id`, `flow.user_id`, and `flow.session_id` for end-to-end trace correlation. + +### Convention + +When adding new domain operations in repositories or controllers, wrap them with `traceResultAsync`. Keep span names consistent and descriptive (e.g. `"user.getUserInfo"`). Do not add ad-hoc spans outside these helpers. + +--- + +## 7. Validation & Schemas + +- **Valibot** is used for schema validation in the logic package and in remote function input. +- Domain data types are defined in `data.ts` per domain. +- Use `v.InferOutput` for TypeScript types. +- Remote functions pass Valibot schemas to `query()` and `command()` for input validation. + +--- + +## 8. Package Naming + +- Apps: `@apps/*` (e.g. `@apps/main`, `@apps/processor`) +- Packages: `@pkg/*` (e.g. `@pkg/logic`, `@pkg/db`, `@pkg/logger`) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dba777a --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Illusory IOTAM + +This is the source code for a SaaS project with purposes to basically offer the users the ability to use specific Android applications, but instead of the apps running on their own phone, they run on our hosted Docker-Android instances. + +Right now the project is in alpha testing phase, so it is subject to change (greenfield project) + +--- diff --git a/apps/front/package.json b/apps/front/package.json new file mode 100644 index 0000000..89ff791 --- /dev/null +++ b/apps/front/package.json @@ -0,0 +1,33 @@ +{ + "name": "@apps/front", + "type": "module", + "scripts": { + "dev": "PORT=3000 tsx watch src/index.ts", + "build": "tsc", + "prod": "HOST=0.0.0.0 PORT=3000 tsx src/index.ts" + }, + "dependencies": { + "@hono/node-server": "^1.19.9", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/auto-instrumentations-node": "^0.70.1", + "@opentelemetry/exporter-logs-otlp-proto": "^0.212.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.212.0", + "@opentelemetry/sdk-logs": "^0.212.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-node": "^0.212.0", + "@pkg/db": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/logic": "workspace:*", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "hono": "^4.12.8", + "import-in-the-middle": "^3.0.0", + "valibot": "^1.2.0" + }, + "devDependencies": { + "@types/node": "^25.3.2", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } +} diff --git a/apps/front/src/index.ts b/apps/front/src/index.ts new file mode 100644 index 0000000..c4b9227 --- /dev/null +++ b/apps/front/src/index.ts @@ -0,0 +1,153 @@ +import "./instrumentation.js"; + +import { createHttpTelemetryMiddleware } from "@pkg/logic/core/http.telemetry"; +import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context"; +import { logDomainEvent } from "@pkg/logger"; +import { serve } from "@hono/node-server"; +import { settings } from "@pkg/settings"; +import { randomUUID } from "node:crypto"; +import { Hono } from "hono"; + +const app = new Hono().use("*", createHttpTelemetryMiddleware("front")); + +const host = process.env.HOST || "0.0.0.0"; +const port = Number(process.env.PORT || "3000"); + +function normalizeBaseUrl(url: string): string { + return url.endsWith("/") ? url.slice(0, -1) : url; +} + +function buildFlowExecCtx(): FlowExecCtx { + return { flowId: randomUUID() }; +} + +function getClientDownloadedApkName(): string { + const filename = settings.clientDownloadedApkName.trim(); + return filename.toLowerCase().endsWith(".apk") + ? filename + : `${filename}.apk`; +} + +app.get("/health", (c) => { + return c.json({ ok: true }); +}); + +app.get("/ping", (c) => { + return c.text("pong"); +}); + +app.get("/downloads/file/:buildId", async (c) => { + const fctx = buildFlowExecCtx(); + const buildId = c.req.param("buildId"); + + logDomainEvent({ + event: "processor.apk_download.started", + fctx, + meta: { buildId }, + }); + + const buildResult = await mobileBuildController.validateActiveBuildId( + fctx, + buildId, + ); + if (buildResult.isErr()) { + logDomainEvent({ + level: "warn", + event: "processor.apk_download.rejected", + fctx, + error: buildResult.error, + meta: { buildId }, + }); + return c.json( + { + data: null, + error: { ...buildResult.error, flowId: fctx.flowId }, + }, + 404, + ); + } + + const build = buildResult.value; + if (!build.apkAssetPath) { + logDomainEvent({ + level: "warn", + event: "processor.apk_download.missing_artifact", + fctx, + meta: { buildId }, + }); + return c.json( + { + data: null, + error: { + flowId: fctx.flowId, + code: "NOT_FOUND", + message: "APK not available", + description: "This build does not have a generated APK yet", + detail: `buildId=${buildId}`, + }, + }, + 404, + ); + } + + const assetUrl = `${normalizeBaseUrl(settings.appBuilderApiUrl)}${build.apkAssetPath}`; + const assetResponse = await fetch(assetUrl); + + if (!assetResponse.ok || !assetResponse.body) { + logDomainEvent({ + level: "error", + event: "processor.apk_download.fetch_failed", + fctx, + meta: { + buildId, + assetUrl, + status: assetResponse.status, + }, + }); + return c.json( + { + data: null, + error: { + flowId: fctx.flowId, + code: "INTERNAL_SERVER_ERROR", + message: "Failed to fetch APK artifact", + description: "Please try again later", + detail: `assetUrl=${assetUrl} status=${assetResponse.status}`, + }, + }, + 502, + ); + } + + logDomainEvent({ + event: "processor.apk_download.succeeded", + fctx, + meta: { + buildId, + assetUrl, + downloadName: getClientDownloadedApkName(), + }, + }); + + return new Response(assetResponse.body, { + status: 200, + headers: { + "content-type": + assetResponse.headers.get("content-type") || + "application/vnd.android.package-archive", + "content-disposition": `attachment; filename="${getClientDownloadedApkName()}"`, + "cache-control": "no-store", + }, + }); +}); + +serve( + { + fetch: app.fetch, + port, + hostname: host, + }, + (info) => { + console.log(`Server is running on http://${host}:${info.port}`); + }, +); diff --git a/apps/front/src/instrumentation.ts b/apps/front/src/instrumentation.ts new file mode 100644 index 0000000..6b3ec48 --- /dev/null +++ b/apps/front/src/instrumentation.ts @@ -0,0 +1,50 @@ +import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; +import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"; +import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; +import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto"; +import { createAddHookMessageChannel } from "import-in-the-middle"; +import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { settings } from "@pkg/settings"; +import { register } from "node:module"; + +const { registerOptions } = createAddHookMessageChannel(); +register("import-in-the-middle/hook.mjs", import.meta.url, registerOptions); + +const normalizedEndpoint = settings.otelExporterOtlpHttpEndpoint.startsWith( + "http", +) + ? settings.otelExporterOtlpHttpEndpoint + : `http://${settings.otelExporterOtlpHttpEndpoint}`; + +if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) { + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = normalizedEndpoint; +} + +const sdk = new NodeSDK({ + serviceName: `${settings.otelServiceName}-processor`, + traceExporter: new OTLPTraceExporter(), + metricReader: new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter(), + exportIntervalMillis: 10_000, + }), + logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())], + instrumentations: [ + getNodeAutoInstrumentations({ + "@opentelemetry/instrumentation-winston": { + // We add OpenTelemetryTransportV3 explicitly in @pkg/logger. + disableLogSending: true, + }, + }), + ], +}); + +sdk.start(); + +const shutdown = () => { + void sdk.shutdown(); +}; + +process.on("SIGTERM", shutdown); +process.on("SIGINT", shutdown); diff --git a/apps/front/tsconfig.json b/apps/front/tsconfig.json new file mode 100644 index 0000000..fce7dfe --- /dev/null +++ b/apps/front/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "verbatimModuleSyntax": false, + "skipLibCheck": true, + "types": [ + "node" + ], + "baseUrl": ".", + "paths": { + "@pkg/logic": ["../../packages/logic"], + "@pkg/logic/*": ["../../packages/logic/*"], + "@pkg/db": ["../../packages/db"], + "@pkg/db/*": ["../../packages/db/*"], + "@pkg/logger": ["../../packages/logger"], + "@pkg/logger/*": ["../../packages/logger/*"], + "@pkg/result": ["../../packages/result"], + "@pkg/result/*": ["../../packages/result/*"], + "@pkg/settings": ["../../packages/settings"], + "@pkg/settings/*": ["../../packages/settings/*"], + "@/*": ["../../packages/logic/*"], + "@core/*": ["../../packages/logic/core/*"], + "@domains/*": ["../../packages/logic/domains/*"] + }, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + "outDir": "./dist" + }, + "exclude": ["node_modules"] +} diff --git a/apps/front/view.html b/apps/front/view.html new file mode 100644 index 0000000..4f4d891 --- /dev/null +++ b/apps/front/view.html @@ -0,0 +1,290 @@ + + + + + + + + + + Loading... + + + +
+
+ Connecting... +
+ + + + + + diff --git a/apps/main/.gitignore b/apps/main/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/apps/main/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/apps/main/.npmrc b/apps/main/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/apps/main/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/apps/main/.prettierignore b/apps/main/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/apps/main/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/apps/main/components.json b/apps/main/components.json new file mode 100644 index 0000000..1e6a638 --- /dev/null +++ b/apps/main/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/routes/layout.css", + "baseColor": "neutral" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} diff --git a/apps/main/package.json b/apps/main/package.json new file mode 100644 index 0000000..3819056 --- /dev/null +++ b/apps/main/package.json @@ -0,0 +1,78 @@ +{ + "name": "@apps/main", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev --port 5173", + "build": "NODE_ENV=build vite build", + "prod": "HOST=0.0.0.0 PORT=3000 node ./build/index.js", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check .", + "test:unit": "vitest" + }, + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/auto-instrumentations-node": "^0.70.1", + "@opentelemetry/exporter-logs-otlp-proto": "^0.212.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.212.0", + "@opentelemetry/sdk-logs": "^0.212.0", + "@opentelemetry/sdk-node": "^0.212.0", + "@pkg/db": "workspace:*", + "@pkg/keystore": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/logic": "workspace:*", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "argon2": "^0.43.0", + "better-auth": "^1.4.20", + "date-fns": "^4.1.0", + "import-in-the-middle": "^3.0.0", + "nanoid": "^5.1.6", + "neverthrow": "^8.2.0", + "qrcode": "^1.5.4", + "sharp": "^0.34.5", + "valibot": "^1.2.0" + }, + "devDependencies": { + "@iconify/json": "^2.2.434", + "@internationalized/date": "^3.10.0", + "@lucide/svelte": "^0.561.0", + "@sveltejs/adapter-node": "^5.5.4", + "@sveltejs/kit": "^2.53.4", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.18", + "@tanstack/table-core": "^8.21.3", + "@types/qrcode": "^1.5.6", + "bits-ui": "^2.14.4", + "clsx": "^2.1.1", + "embla-carousel-svelte": "^8.6.0", + "formsnap": "^2.0.1", + "layerchart": "2.0.0-next.43", + "mode-watcher": "^1.1.0", + "paneforge": "^1.0.2", + "prettier": "^3.7.4", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "svelte": "^5.53.6", + "svelte-check": "^4.4.4", + "svelte-sonner": "^1.0.7", + "sveltekit-superforms": "^2.30.0", + "tailwind-merge": "^3.4.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "unplugin-icons": "^23.0.1", + "vaul-svelte": "^1.0.0-next.7", + "vite": "^7.2.6", + "vitest": "^4.0.15" + } +} diff --git a/apps/main/src/app.d.ts b/apps/main/src/app.d.ts new file mode 100644 index 0000000..7ded0fe --- /dev/null +++ b/apps/main/src/app.d.ts @@ -0,0 +1,20 @@ +import type { Session, User } from "@pkg/logic/domains/user/data"; +import "unplugin-icons/types/svelte"; + +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + interface Locals { + flowId?: string; + session?: Session; + user?: User; + } + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/apps/main/src/app.html b/apps/main/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/apps/main/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/apps/main/src/demo.spec.ts b/apps/main/src/demo.spec.ts new file mode 100644 index 0000000..e07cbbd --- /dev/null +++ b/apps/main/src/demo.spec.ts @@ -0,0 +1,7 @@ +import { describe, it, expect } from 'vitest'; + +describe('sum test', () => { + it('adds 1 + 2 to equal 3', () => { + expect(1 + 2).toBe(3); + }); +}); diff --git a/apps/main/src/hooks.server.ts b/apps/main/src/hooks.server.ts new file mode 100644 index 0000000..45d5ffb --- /dev/null +++ b/apps/main/src/hooks.server.ts @@ -0,0 +1,79 @@ +import { checkInitial2FaRequired } from "@pkg/logic/domains/2fa/sensitive-actions"; +import type { Handle, HandleServerError } from "@sveltejs/kit"; +import { auth } from "@pkg/logic/domains/auth/config.base"; +import { svelteKitHandler } from "better-auth/svelte-kit"; +import type { User } from "@pkg/logic/domains/user/data"; +import { sequence } from "@sveltejs/kit/hooks"; +import { building } from "$app/environment"; + +export const handleError: HandleServerError = async ({ error, event }) => { + console.log("[-] Running error middleware for : ", event.url.pathname); + console.log(error); + return { message: (error as Error).message ?? "Internal Server Error" }; +}; + +export const zero: Handle = async ({ event, resolve }) => { + return svelteKitHandler({ event, resolve, auth, building }); +}; + +export const first: Handle = async ({ event, resolve }) => { + if ( + event.url.pathname.includes("/api/v1") || + event.url.pathname.includes("/api/auth") || + event.url.pathname.includes("/api/debug") || + event.url.pathname.includes("/api/chat") || + event.url.pathname.includes("/auth") || + event.url.pathname.includes("/link") + ) { + return await resolve(event); + } + console.log("[+] Running middleware for : ", event.url.pathname); + const baseUrl = event.url.origin; + const signInUrl = baseUrl + "/auth/login"; + const isSignInPage = event.url.pathname === "/auth/login"; + const redirectResponse = new Response(null, { + status: 302, + headers: { Location: signInUrl }, + }); + + const u = await auth.api.getSession({ headers: event.request.headers }); + if (!u || !u.user || !u.session) { + return redirectResponse; + } + if (u.user && isSignInPage) { + return new Response(null, { + status: 302, + headers: { Location: baseUrl }, + }); + } + + console.log("Setting user & session to locals"); + + const fid = crypto.randomUUID(); + + event.locals.flowId = fid; + event.locals.session = u.session; + event.locals.user = u.user as any as User; + + const needs2FA = await checkInitial2FaRequired( + { + flowId: fid, + userId: u.user.id, + sessionId: u.session.id, + }, + u.user as any, + u.session.id, + ); + if (needs2FA && !event.url.pathname.includes("/auth/2fa")) { + return new Response(null, { + status: 302, + headers: { + Location: `/auth/2fa?redirect=${encodeURIComponent(event.url.pathname)}`, + }, + }); + } + + return resolve(event); +}; + +export const handle = sequence(zero, first); diff --git a/apps/main/src/instrumentation.server.ts b/apps/main/src/instrumentation.server.ts new file mode 100644 index 0000000..4184ea0 --- /dev/null +++ b/apps/main/src/instrumentation.server.ts @@ -0,0 +1,27 @@ +import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; +import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"; +import { createAddHookMessageChannel } from "import-in-the-middle"; +import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { settings } from "@pkg/settings"; +import { register } from "node:module"; + +const { registerOptions } = createAddHookMessageChannel(); +register("import-in-the-middle/hook.mjs", import.meta.url, registerOptions); + +const sdk = new NodeSDK({ + serviceName: settings.otelServiceName || settings.appName, + traceExporter: new OTLPTraceExporter(), + logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())], + instrumentations: [ + getNodeAutoInstrumentations({ + "@opentelemetry/instrumentation-winston": { + // We add OpenTelemetryTransportV3 explicitly in @pkg/logger. + disableLogSending: true, + }, + }), + ], +}); + +sdk.start(); diff --git a/apps/main/src/lib/auth.client.ts b/apps/main/src/lib/auth.client.ts new file mode 100644 index 0000000..543a4bc --- /dev/null +++ b/apps/main/src/lib/auth.client.ts @@ -0,0 +1,19 @@ +import { + adminClient, + customSessionClient, + inferAdditionalFields, + multiSessionClient, + usernameClient, +} from "better-auth/client/plugins"; +import type { auth } from "@pkg/logic/domains/auth/config.base"; +import { createAuthClient } from "better-auth/svelte"; + +export const authClient = createAuthClient({ + plugins: [ + usernameClient(), + adminClient(), + multiSessionClient(), + customSessionClient(), + inferAdditionalFields(), + ], +}); diff --git a/apps/main/src/lib/components/app-sidebar.svelte b/apps/main/src/lib/components/app-sidebar.svelte new file mode 100644 index 0000000..f0ed540 --- /dev/null +++ b/apps/main/src/lib/components/app-sidebar.svelte @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/apps/main/src/lib/components/atoms/button-text.svelte b/apps/main/src/lib/components/atoms/button-text.svelte new file mode 100644 index 0000000..207d697 --- /dev/null +++ b/apps/main/src/lib/components/atoms/button-text.svelte @@ -0,0 +1,17 @@ + + +{#if loading} + + {loadingText} +{:else} + {text} +{/if} diff --git a/apps/main/src/lib/components/atoms/icon.svelte b/apps/main/src/lib/components/atoms/icon.svelte new file mode 100644 index 0000000..794c944 --- /dev/null +++ b/apps/main/src/lib/components/atoms/icon.svelte @@ -0,0 +1,11 @@ + + + diff --git a/apps/main/src/lib/components/atoms/title.svelte b/apps/main/src/lib/components/atoms/title.svelte new file mode 100644 index 0000000..ed17511 --- /dev/null +++ b/apps/main/src/lib/components/atoms/title.svelte @@ -0,0 +1,61 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/molecules/max-width-wrapper.svelte b/apps/main/src/lib/components/molecules/max-width-wrapper.svelte new file mode 100644 index 0000000..e3ec428 --- /dev/null +++ b/apps/main/src/lib/components/molecules/max-width-wrapper.svelte @@ -0,0 +1,11 @@ + + +
+ {@render children()} +
diff --git a/apps/main/src/lib/components/nav-main.svelte b/apps/main/src/lib/components/nav-main.svelte new file mode 100644 index 0000000..5968981 --- /dev/null +++ b/apps/main/src/lib/components/nav-main.svelte @@ -0,0 +1,81 @@ + + + + + {#each items as mainItem (mainItem.title)} + + + +
+ + + {#snippet child({ props })} + + {#if mainItem.icon} + + {/if} + {mainItem.title} + + {/snippet} + + + + {#if mainItem.items && sidebar.open} + + {#snippet child({ props })} +
+ + Toggle {mainItem.title} submenu +
+ {/snippet} +
+ {/if} +
+ + + + {#if mainItem.items} + + {#each mainItem.items as subItem (subItem.title)} + + + {#snippet child({ props })} + + {subItem.title} + + {/snippet} + + + {/each} + + {/if} + +
+
+ {/each} +
+
diff --git a/apps/main/src/lib/components/nav-user.svelte b/apps/main/src/lib/components/nav-user.svelte new file mode 100644 index 0000000..0a9e96a --- /dev/null +++ b/apps/main/src/lib/components/nav-user.svelte @@ -0,0 +1,150 @@ + + + + + + + {#snippet child({ props })} + + + + + {user.name.slice(0, 2).toUpperCase()} + + +
+ {user.name} + {user.email} +
+ +
+ {/snippet} +
+ + +
+ + + + {user.name.slice(0, 2).toUpperCase()} + + +
+ {user.name} + {user.email} +
+
+
+ + + + {#each secondaryNavTree as item (item.title)} + + + + {item.title} + + + {/each} + + + + + + + + + + Theme + + + setMode("light")}> + + Light + + setMode("dark")}> + + Dark + + resetMode()}> + + System + + + + + + logoutUser()}> + + Log out + +
+
+
+
diff --git a/apps/main/src/lib/components/team-switcher.svelte b/apps/main/src/lib/components/team-switcher.svelte new file mode 100644 index 0000000..4981212 --- /dev/null +++ b/apps/main/src/lib/components/team-switcher.svelte @@ -0,0 +1,80 @@ + + + + + + + {#snippet child({ props })} + +
+ +
+
+ + {activeTeam.name} + + {activeTeam.plan} +
+ +
+ {/snippet} +
+ + Teams + {#each teams as team, index (team.name)} + (activeTeam = team)} + class="gap-2 p-2" + > +
+ +
+ {team.name} + > +
+ {/each} + + +
+ +
+
+ Coming soon +
+
+
+
+
+
diff --git a/apps/main/src/lib/components/ui/accordion/accordion-content.svelte b/apps/main/src/lib/components/ui/accordion/accordion-content.svelte new file mode 100644 index 0000000..559db3d --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion-content.svelte @@ -0,0 +1,22 @@ + + + +
+ {@render children?.()} +
+
diff --git a/apps/main/src/lib/components/ui/accordion/accordion-item.svelte b/apps/main/src/lib/components/ui/accordion/accordion-item.svelte new file mode 100644 index 0000000..780545c --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion-item.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte b/apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte new file mode 100644 index 0000000..c46c246 --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion-trigger.svelte @@ -0,0 +1,32 @@ + + + + svg]:rotate-180", + className + )} + {...restProps} + > + {@render children?.()} + + + diff --git a/apps/main/src/lib/components/ui/accordion/accordion.svelte b/apps/main/src/lib/components/ui/accordion/accordion.svelte new file mode 100644 index 0000000..117ee37 --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/accordion.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/accordion/index.ts b/apps/main/src/lib/components/ui/accordion/index.ts new file mode 100644 index 0000000..ac343a1 --- /dev/null +++ b/apps/main/src/lib/components/ui/accordion/index.ts @@ -0,0 +1,16 @@ +import Root from "./accordion.svelte"; +import Content from "./accordion-content.svelte"; +import Item from "./accordion-item.svelte"; +import Trigger from "./accordion-trigger.svelte"; + +export { + Root, + Content, + Item, + Trigger, + // + Root as Accordion, + Content as AccordionContent, + Item as AccordionItem, + Trigger as AccordionTrigger, +}; diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000..a005691 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,18 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000..a7b0cf7 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,18 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000..00bdd9c --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,29 @@ + + + + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000..2ec67dc --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000..f78b97a --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000..1835d91 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000..a64ee76 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte new file mode 100644 index 0000000..f0a19a8 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000..7ef2b5f --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte new file mode 100644 index 0000000..b22d1d5 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte new file mode 100644 index 0000000..7ea78bb --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/alert-dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/alert-dialog/index.ts b/apps/main/src/lib/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..269538e --- /dev/null +++ b/apps/main/src/lib/components/ui/alert-dialog/index.ts @@ -0,0 +1,37 @@ +import Root from "./alert-dialog.svelte"; +import Portal from "./alert-dialog-portal.svelte"; +import Trigger from "./alert-dialog-trigger.svelte"; +import Title from "./alert-dialog-title.svelte"; +import Action from "./alert-dialog-action.svelte"; +import Cancel from "./alert-dialog-cancel.svelte"; +import Footer from "./alert-dialog-footer.svelte"; +import Header from "./alert-dialog-header.svelte"; +import Overlay from "./alert-dialog-overlay.svelte"; +import Content from "./alert-dialog-content.svelte"; +import Description from "./alert-dialog-description.svelte"; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription, +}; diff --git a/apps/main/src/lib/components/ui/alert/alert-description.svelte b/apps/main/src/lib/components/ui/alert/alert-description.svelte new file mode 100644 index 0000000..8b56aed --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/alert-description.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert/alert-title.svelte b/apps/main/src/lib/components/ui/alert/alert-title.svelte new file mode 100644 index 0000000..77e45ad --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/alert-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/apps/main/src/lib/components/ui/alert/alert.svelte b/apps/main/src/lib/components/ui/alert/alert.svelte new file mode 100644 index 0000000..2b2eff9 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/alert.svelte @@ -0,0 +1,44 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/alert/index.ts b/apps/main/src/lib/components/ui/alert/index.ts new file mode 100644 index 0000000..97e21b4 --- /dev/null +++ b/apps/main/src/lib/components/ui/alert/index.ts @@ -0,0 +1,14 @@ +import Root from "./alert.svelte"; +import Description from "./alert-description.svelte"; +import Title from "./alert-title.svelte"; +export { alertVariants, type AlertVariant } from "./alert.svelte"; + +export { + Root, + Description, + Title, + // + Root as Alert, + Description as AlertDescription, + Title as AlertTitle, +}; diff --git a/apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte b/apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte new file mode 100644 index 0000000..815aab0 --- /dev/null +++ b/apps/main/src/lib/components/ui/aspect-ratio/aspect-ratio.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/aspect-ratio/index.ts b/apps/main/src/lib/components/ui/aspect-ratio/index.ts new file mode 100644 index 0000000..985c75f --- /dev/null +++ b/apps/main/src/lib/components/ui/aspect-ratio/index.ts @@ -0,0 +1,3 @@ +import Root from "./aspect-ratio.svelte"; + +export { Root, Root as AspectRatio }; diff --git a/apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte b/apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte new file mode 100644 index 0000000..249d4a4 --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/avatar-fallback.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/avatar/avatar-image.svelte b/apps/main/src/lib/components/ui/avatar/avatar-image.svelte new file mode 100644 index 0000000..2bb9db4 --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/avatar-image.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/avatar/avatar.svelte b/apps/main/src/lib/components/ui/avatar/avatar.svelte new file mode 100644 index 0000000..e37214d --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/avatar.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/avatar/index.ts b/apps/main/src/lib/components/ui/avatar/index.ts new file mode 100644 index 0000000..d06457b --- /dev/null +++ b/apps/main/src/lib/components/ui/avatar/index.ts @@ -0,0 +1,13 @@ +import Root from "./avatar.svelte"; +import Image from "./avatar-image.svelte"; +import Fallback from "./avatar-fallback.svelte"; + +export { + Root, + Image, + Fallback, + // + Root as Avatar, + Image as AvatarImage, + Fallback as AvatarFallback, +}; diff --git a/apps/main/src/lib/components/ui/badge/badge.svelte b/apps/main/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 0000000..e3164ba --- /dev/null +++ b/apps/main/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,50 @@ + + + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/badge/index.ts b/apps/main/src/lib/components/ui/badge/index.ts new file mode 100644 index 0000000..64e0aa9 --- /dev/null +++ b/apps/main/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from "./badge.svelte"; +export { badgeVariants, type BadgeVariant } from "./badge.svelte"; diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte new file mode 100644 index 0000000..a178cf5 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte @@ -0,0 +1,23 @@ + + + diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte new file mode 100644 index 0000000..1a84c4c --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte @@ -0,0 +1,20 @@ + + +
  • + {@render children?.()} +
  • diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte new file mode 100644 index 0000000..e6bc17d --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte @@ -0,0 +1,31 @@ + + +{#if child} + {@render child({ props: attrs })} +{:else} + + {@render children?.()} + +{/if} diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte new file mode 100644 index 0000000..1272a37 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte @@ -0,0 +1,23 @@ + + +
      + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte new file mode 100644 index 0000000..5fb6979 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte new file mode 100644 index 0000000..84106a1 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte @@ -0,0 +1,27 @@ + + + diff --git a/apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte new file mode 100644 index 0000000..8f8a3e6 --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/breadcrumb.svelte @@ -0,0 +1,21 @@ + + + diff --git a/apps/main/src/lib/components/ui/breadcrumb/index.ts b/apps/main/src/lib/components/ui/breadcrumb/index.ts new file mode 100644 index 0000000..dc914ec --- /dev/null +++ b/apps/main/src/lib/components/ui/breadcrumb/index.ts @@ -0,0 +1,25 @@ +import Root from "./breadcrumb.svelte"; +import Ellipsis from "./breadcrumb-ellipsis.svelte"; +import Item from "./breadcrumb-item.svelte"; +import Separator from "./breadcrumb-separator.svelte"; +import Link from "./breadcrumb-link.svelte"; +import List from "./breadcrumb-list.svelte"; +import Page from "./breadcrumb-page.svelte"; + +export { + Root, + Ellipsis, + Item, + Separator, + Link, + List, + Page, + // + Root as Breadcrumb, + Ellipsis as BreadcrumbEllipsis, + Item as BreadcrumbItem, + Separator as BreadcrumbSeparator, + Link as BreadcrumbLink, + List as BreadcrumbList, + Page as BreadcrumbPage, +}; diff --git a/apps/main/src/lib/components/ui/button-group/button-group-separator.svelte b/apps/main/src/lib/components/ui/button-group/button-group-separator.svelte new file mode 100644 index 0000000..86ff8ae --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/button-group-separator.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/button-group/button-group-text.svelte b/apps/main/src/lib/components/ui/button-group/button-group-text.svelte new file mode 100644 index 0000000..1be72bb --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/button-group-text.svelte @@ -0,0 +1,30 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
    + {@render mergedProps.children?.()} +
    +{/if} diff --git a/apps/main/src/lib/components/ui/button-group/button-group.svelte b/apps/main/src/lib/components/ui/button-group/button-group.svelte new file mode 100644 index 0000000..34c8d79 --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/button-group.svelte @@ -0,0 +1,46 @@ + + + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/button-group/index.ts b/apps/main/src/lib/components/ui/button-group/index.ts new file mode 100644 index 0000000..7f706e3 --- /dev/null +++ b/apps/main/src/lib/components/ui/button-group/index.ts @@ -0,0 +1,13 @@ +import Root from "./button-group.svelte"; +import Text from "./button-group-text.svelte"; +import Separator from "./button-group-separator.svelte"; + +export { + Root, + Text, + Separator, + // + Root as ButtonGroup, + Text as ButtonGroupText, + Separator as ButtonGroupSeparator, +}; diff --git a/apps/main/src/lib/components/ui/button/button.svelte b/apps/main/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..a8296ae --- /dev/null +++ b/apps/main/src/lib/components/ui/button/button.svelte @@ -0,0 +1,82 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/apps/main/src/lib/components/ui/button/index.ts b/apps/main/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..fb585d7 --- /dev/null +++ b/apps/main/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from "./button.svelte"; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant, +}; diff --git a/apps/main/src/lib/components/ui/calendar/calendar-caption.svelte b/apps/main/src/lib/components/ui/calendar/calendar-caption.svelte new file mode 100644 index 0000000..5c93037 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-caption.svelte @@ -0,0 +1,76 @@ + + +{#snippet MonthSelect()} + { + if (!placeholder) return; + const v = Number.parseInt(e.currentTarget.value); + const newPlaceholder = placeholder.set({ month: v }); + placeholder = newPlaceholder.subtract({ months: monthIndex }); + }} + /> +{/snippet} + +{#snippet YearSelect()} + +{/snippet} + +{#if captionLayout === "dropdown"} + {@render MonthSelect()} + {@render YearSelect()} +{:else if captionLayout === "dropdown-months"} + {@render MonthSelect()} + {#if placeholder} + {formatYear(placeholder)} + {/if} +{:else if captionLayout === "dropdown-years"} + {#if placeholder} + {formatMonth(placeholder)} + {/if} + {@render YearSelect()} +{:else} + {formatMonth(month)} {formatYear(month)} +{/if} diff --git a/apps/main/src/lib/components/ui/calendar/calendar-cell.svelte b/apps/main/src/lib/components/ui/calendar/calendar-cell.svelte new file mode 100644 index 0000000..4cdb548 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-day.svelte b/apps/main/src/lib/components/ui/calendar/calendar-day.svelte new file mode 100644 index 0000000..19d7bde --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-day.svelte @@ -0,0 +1,35 @@ + + +span]:text-xs [&>span]:opacity-70", + className + )} + {...restProps} +/> diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte new file mode 100644 index 0000000..8cd86de --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid-body.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte new file mode 100644 index 0000000..333edc4 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid-head.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte new file mode 100644 index 0000000..9032236 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid-row.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-grid.svelte b/apps/main/src/lib/components/ui/calendar/calendar-grid.svelte new file mode 100644 index 0000000..e0c8627 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-grid.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte b/apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte new file mode 100644 index 0000000..131807e --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-head-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-header.svelte b/apps/main/src/lib/components/ui/calendar/calendar-header.svelte new file mode 100644 index 0000000..c39e955 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-header.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-heading.svelte b/apps/main/src/lib/components/ui/calendar/calendar-heading.svelte new file mode 100644 index 0000000..a9b9810 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-heading.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte b/apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte new file mode 100644 index 0000000..8d88deb --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-month-select.svelte @@ -0,0 +1,44 @@ + + + + + {#snippet child({ props, monthItems, selectedMonthItem })} + + + {/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-month.svelte b/apps/main/src/lib/components/ui/calendar/calendar-month.svelte new file mode 100644 index 0000000..e747fae --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-month.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/calendar/calendar-months.svelte b/apps/main/src/lib/components/ui/calendar/calendar-months.svelte new file mode 100644 index 0000000..f717a9d --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-months.svelte @@ -0,0 +1,19 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/calendar/calendar-nav.svelte b/apps/main/src/lib/components/ui/calendar/calendar-nav.svelte new file mode 100644 index 0000000..27f33d7 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-nav.svelte @@ -0,0 +1,19 @@ + + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte b/apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte new file mode 100644 index 0000000..5c5a78d --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-next-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte b/apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte new file mode 100644 index 0000000..33cfd63 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-prev-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte b/apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte new file mode 100644 index 0000000..226efdf --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar-year-select.svelte @@ -0,0 +1,43 @@ + + + + + {#snippet child({ props, yearItems, selectedYearItem })} + + + {/snippet} + + diff --git a/apps/main/src/lib/components/ui/calendar/calendar.svelte b/apps/main/src/lib/components/ui/calendar/calendar.svelte new file mode 100644 index 0000000..29b6fff --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/calendar.svelte @@ -0,0 +1,115 @@ + + + + + {#snippet children({ months, weekdays })} + + + + + + {#each months as month, monthIndex (month)} + + + + + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + {#if day} + {@render day({ + day: date, + outsideMonth: !isEqualMonth(date, month.value), + })} + {:else} + + {/if} + + {/each} + + {/each} + + + + {/each} + + {/snippet} + diff --git a/apps/main/src/lib/components/ui/calendar/index.ts b/apps/main/src/lib/components/ui/calendar/index.ts new file mode 100644 index 0000000..f3a16d2 --- /dev/null +++ b/apps/main/src/lib/components/ui/calendar/index.ts @@ -0,0 +1,40 @@ +import Root from "./calendar.svelte"; +import Cell from "./calendar-cell.svelte"; +import Day from "./calendar-day.svelte"; +import Grid from "./calendar-grid.svelte"; +import Header from "./calendar-header.svelte"; +import Months from "./calendar-months.svelte"; +import GridRow from "./calendar-grid-row.svelte"; +import Heading from "./calendar-heading.svelte"; +import GridBody from "./calendar-grid-body.svelte"; +import GridHead from "./calendar-grid-head.svelte"; +import HeadCell from "./calendar-head-cell.svelte"; +import NextButton from "./calendar-next-button.svelte"; +import PrevButton from "./calendar-prev-button.svelte"; +import MonthSelect from "./calendar-month-select.svelte"; +import YearSelect from "./calendar-year-select.svelte"; +import Month from "./calendar-month.svelte"; +import Nav from "./calendar-nav.svelte"; +import Caption from "./calendar-caption.svelte"; + +export { + Day, + Cell, + Grid, + Header, + Months, + GridRow, + Heading, + GridBody, + GridHead, + HeadCell, + NextButton, + PrevButton, + Nav, + Month, + YearSelect, + MonthSelect, + Caption, + // + Root as Calendar, +}; diff --git a/apps/main/src/lib/components/ui/card/card-action.svelte b/apps/main/src/lib/components/ui/card/card-action.svelte new file mode 100644 index 0000000..cc36c56 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-action.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-content.svelte b/apps/main/src/lib/components/ui/card/card-content.svelte new file mode 100644 index 0000000..bc90b83 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-content.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-description.svelte b/apps/main/src/lib/components/ui/card/card-description.svelte new file mode 100644 index 0000000..9b20ac7 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-description.svelte @@ -0,0 +1,20 @@ + + +

    + {@render children?.()} +

    diff --git a/apps/main/src/lib/components/ui/card/card-footer.svelte b/apps/main/src/lib/components/ui/card/card-footer.svelte new file mode 100644 index 0000000..2d4d0f2 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-header.svelte b/apps/main/src/lib/components/ui/card/card-header.svelte new file mode 100644 index 0000000..2501788 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-header.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card-title.svelte b/apps/main/src/lib/components/ui/card/card-title.svelte new file mode 100644 index 0000000..7447231 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/card.svelte b/apps/main/src/lib/components/ui/card/card.svelte new file mode 100644 index 0000000..99448cc --- /dev/null +++ b/apps/main/src/lib/components/ui/card/card.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/card/index.ts b/apps/main/src/lib/components/ui/card/index.ts new file mode 100644 index 0000000..4d3fce4 --- /dev/null +++ b/apps/main/src/lib/components/ui/card/index.ts @@ -0,0 +1,25 @@ +import Root from "./card.svelte"; +import Content from "./card-content.svelte"; +import Description from "./card-description.svelte"; +import Footer from "./card-footer.svelte"; +import Header from "./card-header.svelte"; +import Title from "./card-title.svelte"; +import Action from "./card-action.svelte"; + +export { + Root, + Content, + Description, + Footer, + Header, + Title, + Action, + // + Root as Card, + Content as CardContent, + Description as CardDescription, + Footer as CardFooter, + Header as CardHeader, + Title as CardTitle, + Action as CardAction, +}; diff --git a/apps/main/src/lib/components/ui/carousel/carousel-content.svelte b/apps/main/src/lib/components/ui/carousel/carousel-content.svelte new file mode 100644 index 0000000..84c71f8 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-content.svelte @@ -0,0 +1,43 @@ + + +
    +
    + {@render children?.()} +
    +
    diff --git a/apps/main/src/lib/components/ui/carousel/carousel-item.svelte b/apps/main/src/lib/components/ui/carousel/carousel-item.svelte new file mode 100644 index 0000000..ebf1649 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-item.svelte @@ -0,0 +1,30 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/carousel/carousel-next.svelte b/apps/main/src/lib/components/ui/carousel/carousel-next.svelte new file mode 100644 index 0000000..1aaa1f4 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-next.svelte @@ -0,0 +1,38 @@ + + + diff --git a/apps/main/src/lib/components/ui/carousel/carousel-previous.svelte b/apps/main/src/lib/components/ui/carousel/carousel-previous.svelte new file mode 100644 index 0000000..dafe4fd --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel-previous.svelte @@ -0,0 +1,38 @@ + + + diff --git a/apps/main/src/lib/components/ui/carousel/carousel.svelte b/apps/main/src/lib/components/ui/carousel/carousel.svelte new file mode 100644 index 0000000..0492805 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/carousel.svelte @@ -0,0 +1,93 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/carousel/context.ts b/apps/main/src/lib/components/ui/carousel/context.ts new file mode 100644 index 0000000..a5fd74f --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/context.ts @@ -0,0 +1,58 @@ +import type { WithElementRef } from "$lib/utils.js"; +import type { + EmblaCarouselSvelteType, + default as emblaCarouselSvelte, +} from "embla-carousel-svelte"; +import { getContext, hasContext, setContext } from "svelte"; +import type { HTMLAttributes } from "svelte/elements"; + +export type CarouselAPI = + NonNullable["on:emblaInit"]> extends ( + evt: CustomEvent + ) => void + ? CarouselAPI + : never; + +type EmblaCarouselConfig = NonNullable[1]>; + +export type CarouselOptions = EmblaCarouselConfig["options"]; +export type CarouselPlugins = EmblaCarouselConfig["plugins"]; + +//// + +export type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugins; + setApi?: (api: CarouselAPI | undefined) => void; + orientation?: "horizontal" | "vertical"; +} & WithElementRef>; + +const EMBLA_CAROUSEL_CONTEXT = Symbol("EMBLA_CAROUSEL_CONTEXT"); + +export type EmblaContext = { + api: CarouselAPI | undefined; + orientation: "horizontal" | "vertical"; + scrollNext: () => void; + scrollPrev: () => void; + canScrollNext: boolean; + canScrollPrev: boolean; + handleKeyDown: (e: KeyboardEvent) => void; + options: CarouselOptions; + plugins: CarouselPlugins; + onInit: (e: CustomEvent) => void; + scrollTo: (index: number, jump?: boolean) => void; + scrollSnaps: number[]; + selectedIndex: number; +}; + +export function setEmblaContext(config: EmblaContext): EmblaContext { + setContext(EMBLA_CAROUSEL_CONTEXT, config); + return config; +} + +export function getEmblaContext(name = "This component") { + if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) { + throw new Error(`${name} must be used within a component`); + } + return getContext>(EMBLA_CAROUSEL_CONTEXT); +} diff --git a/apps/main/src/lib/components/ui/carousel/index.ts b/apps/main/src/lib/components/ui/carousel/index.ts new file mode 100644 index 0000000..957fc74 --- /dev/null +++ b/apps/main/src/lib/components/ui/carousel/index.ts @@ -0,0 +1,19 @@ +import Root from "./carousel.svelte"; +import Content from "./carousel-content.svelte"; +import Item from "./carousel-item.svelte"; +import Previous from "./carousel-previous.svelte"; +import Next from "./carousel-next.svelte"; + +export { + Root, + Content, + Item, + Previous, + Next, + // + Root as Carousel, + Content as CarouselContent, + Item as CarouselItem, + Previous as CarouselPrevious, + Next as CarouselNext, +}; diff --git a/apps/main/src/lib/components/ui/chart/chart-container.svelte b/apps/main/src/lib/components/ui/chart/chart-container.svelte new file mode 100644 index 0000000..36c0000 --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-container.svelte @@ -0,0 +1,80 @@ + + +
    + + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/chart/chart-style.svelte b/apps/main/src/lib/components/ui/chart/chart-style.svelte new file mode 100644 index 0000000..864ecc3 --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-style.svelte @@ -0,0 +1,37 @@ + + +{#if themeContents} + {#key id} + + {themeContents} + + {/key} +{/if} diff --git a/apps/main/src/lib/components/ui/chart/chart-tooltip.svelte b/apps/main/src/lib/components/ui/chart/chart-tooltip.svelte new file mode 100644 index 0000000..6eb66ff --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-tooltip.svelte @@ -0,0 +1,159 @@ + + +{#snippet TooltipLabel()} + {#if formattedLabel} +
    + {#if typeof formattedLabel === "function"} + {@render formattedLabel()} + {:else} + {formattedLabel} + {/if} +
    + {/if} +{/snippet} + + +
    + {#if !nestLabel} + {@render TooltipLabel()} + {/if} +
    + {#each tooltipCtx.payload as item, i (item.key + i)} + {@const key = `${nameKey || item.key || item.name || "value"}`} + {@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)} + {@const indicatorColor = color || item.payload?.color || item.color} +
    svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5", + indicator === "dot" && "items-center" + )} + > + {#if formatter && item.value !== undefined && item.name} + {@render formatter({ + value: item.value, + name: item.name, + item, + index: i, + payload: tooltipCtx.payload, + })} + {:else} + {#if itemConfig?.icon} + + {:else if !hideIndicator} +
    + {/if} +
    +
    + {#if nestLabel} + {@render TooltipLabel()} + {/if} + + {itemConfig?.label || item.name} + +
    + {#if item.value !== undefined} + + {item.value.toLocaleString()} + + {/if} +
    + {/if} +
    + {/each} +
    +
    +
    diff --git a/apps/main/src/lib/components/ui/chart/chart-utils.ts b/apps/main/src/lib/components/ui/chart/chart-utils.ts new file mode 100644 index 0000000..2decbbf --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/chart-utils.ts @@ -0,0 +1,66 @@ +import type { Tooltip } from "layerchart"; +import { getContext, setContext, type Component, type ComponentProps, type Snippet } from "svelte"; + +export const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: string; + icon?: Component; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +export type ExtractSnippetParams = T extends Snippet<[infer P]> ? P : never; + +export type TooltipPayload = ExtractSnippetParams< + ComponentProps["children"] +>["payload"][number]; + +// Helper to extract item config from a payload. +export function getPayloadConfigFromPayload( + config: ChartConfig, + payload: TooltipPayload, + key: string +) { + if (typeof payload !== "object" || payload === null) return undefined; + + const payloadPayload = + "payload" in payload && typeof payload.payload === "object" && payload.payload !== null + ? payload.payload + : undefined; + + let configLabelKey: string = key; + + if (payload.key === key) { + configLabelKey = payload.key; + } else if (payload.name === key) { + configLabelKey = payload.name; + } else if (key in payload && typeof payload[key as keyof typeof payload] === "string") { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadPayload !== undefined && + key in payloadPayload && + typeof payloadPayload[key as keyof typeof payloadPayload] === "string" + ) { + configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string; + } + + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; +} + +type ChartContextValue = { + config: ChartConfig; +}; + +const chartContextKey = Symbol("chart-context"); + +export function setChartContext(value: ChartContextValue) { + return setContext(chartContextKey, value); +} + +export function useChart() { + return getContext(chartContextKey); +} diff --git a/apps/main/src/lib/components/ui/chart/index.ts b/apps/main/src/lib/components/ui/chart/index.ts new file mode 100644 index 0000000..f22375e --- /dev/null +++ b/apps/main/src/lib/components/ui/chart/index.ts @@ -0,0 +1,6 @@ +import ChartContainer from "./chart-container.svelte"; +import ChartTooltip from "./chart-tooltip.svelte"; + +export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js"; + +export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip }; diff --git a/apps/main/src/lib/components/ui/checkbox/checkbox.svelte b/apps/main/src/lib/components/ui/checkbox/checkbox.svelte new file mode 100644 index 0000000..0a2b010 --- /dev/null +++ b/apps/main/src/lib/components/ui/checkbox/checkbox.svelte @@ -0,0 +1,36 @@ + + + + {#snippet children({ checked, indeterminate })} +
    + {#if checked} + + {:else if indeterminate} + + {/if} +
    + {/snippet} +
    diff --git a/apps/main/src/lib/components/ui/checkbox/index.ts b/apps/main/src/lib/components/ui/checkbox/index.ts new file mode 100644 index 0000000..6d92d94 --- /dev/null +++ b/apps/main/src/lib/components/ui/checkbox/index.ts @@ -0,0 +1,6 @@ +import Root from "./checkbox.svelte"; +export { + Root, + // + Root as Checkbox, +}; diff --git a/apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte b/apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte new file mode 100644 index 0000000..bdabb55 --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/collapsible-content.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte b/apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte new file mode 100644 index 0000000..ece7ad6 --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/collapsible-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/collapsible/collapsible.svelte b/apps/main/src/lib/components/ui/collapsible/collapsible.svelte new file mode 100644 index 0000000..39cdd4e --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/collapsible.svelte @@ -0,0 +1,11 @@ + + + diff --git a/apps/main/src/lib/components/ui/collapsible/index.ts b/apps/main/src/lib/components/ui/collapsible/index.ts new file mode 100644 index 0000000..169b479 --- /dev/null +++ b/apps/main/src/lib/components/ui/collapsible/index.ts @@ -0,0 +1,13 @@ +import Root from "./collapsible.svelte"; +import Trigger from "./collapsible-trigger.svelte"; +import Content from "./collapsible-content.svelte"; + +export { + Root, + Content, + Trigger, + // + Root as Collapsible, + Content as CollapsibleContent, + Trigger as CollapsibleTrigger, +}; diff --git a/apps/main/src/lib/components/ui/command/command-dialog.svelte b/apps/main/src/lib/components/ui/command/command-dialog.svelte new file mode 100644 index 0000000..4bdb740 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-dialog.svelte @@ -0,0 +1,40 @@ + + + + + {title} + {description} + + + + + diff --git a/apps/main/src/lib/components/ui/command/command-empty.svelte b/apps/main/src/lib/components/ui/command/command-empty.svelte new file mode 100644 index 0000000..6726cd8 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-empty.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-group.svelte b/apps/main/src/lib/components/ui/command/command-group.svelte new file mode 100644 index 0000000..104f817 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-group.svelte @@ -0,0 +1,32 @@ + + + + {#if heading} + + {heading} + + {/if} + + diff --git a/apps/main/src/lib/components/ui/command/command-input.svelte b/apps/main/src/lib/components/ui/command/command-input.svelte new file mode 100644 index 0000000..28d3dcf --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-input.svelte @@ -0,0 +1,26 @@ + + +
    + + +
    diff --git a/apps/main/src/lib/components/ui/command/command-item.svelte b/apps/main/src/lib/components/ui/command/command-item.svelte new file mode 100644 index 0000000..5833416 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-link-item.svelte b/apps/main/src/lib/components/ui/command/command-link-item.svelte new file mode 100644 index 0000000..ada6d2c --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-link-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-list.svelte b/apps/main/src/lib/components/ui/command/command-list.svelte new file mode 100644 index 0000000..2d3a01a --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-list.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-loading.svelte b/apps/main/src/lib/components/ui/command/command-loading.svelte new file mode 100644 index 0000000..19dd298 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-loading.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-separator.svelte b/apps/main/src/lib/components/ui/command/command-separator.svelte new file mode 100644 index 0000000..35c4c95 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/command-shortcut.svelte b/apps/main/src/lib/components/ui/command/command-shortcut.svelte new file mode 100644 index 0000000..f3d6928 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/command/command.svelte b/apps/main/src/lib/components/ui/command/command.svelte new file mode 100644 index 0000000..a1581f1 --- /dev/null +++ b/apps/main/src/lib/components/ui/command/command.svelte @@ -0,0 +1,28 @@ + + + diff --git a/apps/main/src/lib/components/ui/command/index.ts b/apps/main/src/lib/components/ui/command/index.ts new file mode 100644 index 0000000..5435fbe --- /dev/null +++ b/apps/main/src/lib/components/ui/command/index.ts @@ -0,0 +1,37 @@ +import Root from "./command.svelte"; +import Loading from "./command-loading.svelte"; +import Dialog from "./command-dialog.svelte"; +import Empty from "./command-empty.svelte"; +import Group from "./command-group.svelte"; +import Item from "./command-item.svelte"; +import Input from "./command-input.svelte"; +import List from "./command-list.svelte"; +import Separator from "./command-separator.svelte"; +import Shortcut from "./command-shortcut.svelte"; +import LinkItem from "./command-link-item.svelte"; + +export { + Root, + Dialog, + Empty, + Group, + Item, + LinkItem, + Input, + List, + Separator, + Shortcut, + Loading, + // + Root as Command, + Dialog as CommandDialog, + Empty as CommandEmpty, + Group as CommandGroup, + Item as CommandItem, + LinkItem as CommandLinkItem, + Input as CommandInput, + List as CommandList, + Separator as CommandSeparator, + Shortcut as CommandShortcut, + Loading as CommandLoading, +}; diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte new file mode 100644 index 0000000..f3b6db3 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte @@ -0,0 +1,40 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte new file mode 100644 index 0000000..20b516d --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-content.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte new file mode 100644 index 0000000..2cb6207 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-group-heading.svelte @@ -0,0 +1,21 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte new file mode 100644 index 0000000..c7c1e06 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte new file mode 100644 index 0000000..4e8d224 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte new file mode 100644 index 0000000..5e96107 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-label.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte new file mode 100644 index 0000000..96b1e3e --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte new file mode 100644 index 0000000..964cb55 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte new file mode 100644 index 0000000..0141b14 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-radio-item.svelte @@ -0,0 +1,33 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte new file mode 100644 index 0000000..7f5b237 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte new file mode 100644 index 0000000..6181881 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte new file mode 100644 index 0000000..2b6ca47 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte new file mode 100644 index 0000000..38d74eb --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte new file mode 100644 index 0000000..a03827b --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte new file mode 100644 index 0000000..3efa857 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/context-menu.svelte b/apps/main/src/lib/components/ui/context-menu/context-menu.svelte new file mode 100644 index 0000000..cfaefb3 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/context-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/context-menu/index.ts b/apps/main/src/lib/components/ui/context-menu/index.ts new file mode 100644 index 0000000..cbeaee1 --- /dev/null +++ b/apps/main/src/lib/components/ui/context-menu/index.ts @@ -0,0 +1,52 @@ +import Root from "./context-menu.svelte"; +import Sub from "./context-menu-sub.svelte"; +import Portal from "./context-menu-portal.svelte"; +import Trigger from "./context-menu-trigger.svelte"; +import Group from "./context-menu-group.svelte"; +import RadioGroup from "./context-menu-radio-group.svelte"; +import Item from "./context-menu-item.svelte"; +import GroupHeading from "./context-menu-group-heading.svelte"; +import Content from "./context-menu-content.svelte"; +import Shortcut from "./context-menu-shortcut.svelte"; +import RadioItem from "./context-menu-radio-item.svelte"; +import Separator from "./context-menu-separator.svelte"; +import SubContent from "./context-menu-sub-content.svelte"; +import SubTrigger from "./context-menu-sub-trigger.svelte"; +import CheckboxItem from "./context-menu-checkbox-item.svelte"; +import Label from "./context-menu-label.svelte"; + +export { + Root, + Sub, + Portal, + Item, + GroupHeading, + Label, + Group, + Trigger, + Content, + Shortcut, + Separator, + RadioItem, + SubContent, + SubTrigger, + RadioGroup, + CheckboxItem, + // + Root as ContextMenu, + Sub as ContextMenuSub, + Portal as ContextMenuPortal, + Item as ContextMenuItem, + GroupHeading as ContextMenuGroupHeading, + Group as ContextMenuGroup, + Content as ContextMenuContent, + Trigger as ContextMenuTrigger, + Shortcut as ContextMenuShortcut, + RadioItem as ContextMenuRadioItem, + Separator as ContextMenuSeparator, + RadioGroup as ContextMenuRadioGroup, + SubContent as ContextMenuSubContent, + SubTrigger as ContextMenuSubTrigger, + CheckboxItem as ContextMenuCheckboxItem, + Label as ContextMenuLabel, +}; diff --git a/apps/main/src/lib/components/ui/data-table/data-table.svelte.ts b/apps/main/src/lib/components/ui/data-table/data-table.svelte.ts new file mode 100644 index 0000000..5b7985e --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/data-table.svelte.ts @@ -0,0 +1,142 @@ +import { + type RowData, + type TableOptions, + type TableOptionsResolved, + type TableState, + createTable, +} from "@tanstack/table-core"; + +/** + * Creates a reactive TanStack table object for Svelte. + * @param options Table options to create the table with. + * @returns A reactive table object. + * @example + * ```svelte + * + * + * + * + * {#each table.getHeaderGroups() as headerGroup} + * + * {#each headerGroup.headers as header} + * + * {/each} + * + * {/each} + * + * + *
    + * + *
    + * ``` + */ +export function createSvelteTable(options: TableOptions) { + const resolvedOptions: TableOptionsResolved = mergeObjects( + { + state: {}, + onStateChange() {}, + renderFallbackValue: null, + mergeOptions: ( + defaultOptions: TableOptions, + options: Partial> + ) => { + return mergeObjects(defaultOptions, options); + }, + }, + options + ); + + const table = createTable(resolvedOptions); + let state = $state>(table.initialState); + + function updateOptions() { + table.setOptions((prev) => { + return mergeObjects(prev, options, { + state: mergeObjects(state, options.state || {}), + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onStateChange: (updater: any) => { + if (updater instanceof Function) state = updater(state); + else state = mergeObjects(state, updater); + + options.onStateChange?.(updater); + }, + }); + }); + } + + updateOptions(); + + $effect.pre(() => { + updateOptions(); + }); + + return table; +} + +type MaybeThunk = T | (() => T | null | undefined); +type Intersection = (T extends [infer H, ...infer R] + ? H & Intersection + : unknown) & {}; + +/** + * Lazily merges several objects (or thunks) while preserving + * getter semantics from every source. + * + * Proxy-based to avoid known WebKit recursion issue. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mergeObjects[]>( + ...sources: Sources +): Intersection<{ [K in keyof Sources]: Sources[K] }> { + const resolve = (src: MaybeThunk): T | undefined => + typeof src === "function" ? (src() ?? undefined) : src; + + const findSourceWithKey = (key: PropertyKey) => { + for (let i = sources.length - 1; i >= 0; i--) { + const obj = resolve(sources[i]); + if (obj && key in obj) return obj; + } + return undefined; + }; + + return new Proxy(Object.create(null), { + get(_, key) { + const src = findSourceWithKey(key); + + return src?.[key as never]; + }, + + has(_, key) { + return !!findSourceWithKey(key); + }, + + ownKeys(): (string | symbol)[] { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const all = new Set(); + for (const s of sources) { + const obj = resolve(s); + if (obj) { + for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) { + all.add(k); + } + } + } + return [...all]; + }, + + getOwnPropertyDescriptor(_, key) { + const src = findSourceWithKey(key); + if (!src) return undefined; + return { + configurable: true, + enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: (src as any)[key], + writable: true, + }; + }, + }) as Intersection<{ [K in keyof Sources]: Sources[K] }>; +} diff --git a/apps/main/src/lib/components/ui/data-table/flex-render.svelte b/apps/main/src/lib/components/ui/data-table/flex-render.svelte new file mode 100644 index 0000000..ac82a58 --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/flex-render.svelte @@ -0,0 +1,40 @@ + + +{#if typeof content === "string"} + {content} +{:else if content instanceof Function} + + + {@const result = content(context as any)} + {#if result instanceof RenderComponentConfig} + {@const { component: Component, props } = result} + + {:else if result instanceof RenderSnippetConfig} + {@const { snippet, params } = result} + {@render snippet({ ...params, attach })} + {:else} + {result} + {/if} +{/if} diff --git a/apps/main/src/lib/components/ui/data-table/index.ts b/apps/main/src/lib/components/ui/data-table/index.ts new file mode 100644 index 0000000..5f4e77e --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/index.ts @@ -0,0 +1,3 @@ +export { default as FlexRender } from "./flex-render.svelte"; +export { renderComponent, renderSnippet } from "./render-helpers.js"; +export { createSvelteTable } from "./data-table.svelte.js"; diff --git a/apps/main/src/lib/components/ui/data-table/render-helpers.ts b/apps/main/src/lib/components/ui/data-table/render-helpers.ts new file mode 100644 index 0000000..fa036d6 --- /dev/null +++ b/apps/main/src/lib/components/ui/data-table/render-helpers.ts @@ -0,0 +1,111 @@ +import type { Component, ComponentProps, Snippet } from "svelte"; + +/** + * A helper class to make it easy to identify Svelte components in + * `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderComponentConfig} + * {@const { component: Component, props } = result} + * + * {/if} + * ``` + */ +export class RenderComponentConfig { + component: TComponent; + props: ComponentProps | Record; + constructor( + component: TComponent, + props: ComponentProps | Record = {} + ) { + this.component = component; + this.props = props; + } +} + +/** + * A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderSnippetConfig} + * {@const { snippet, params } = result} + * {@render snippet(params)} + * {/if} + * ``` + */ +export class RenderSnippetConfig { + snippet: Snippet<[TProps]>; + params: TProps; + constructor(snippet: Snippet<[TProps]>, params: TProps) { + this.snippet = snippet; + this.params = params; + } +} + +/** + * A helper function to help create cells from Svelte components through ColumnDef's `cell` and `header` properties. + * + * This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets. + * + * @param component A Svelte component + * @param props The props to pass to `component` + * @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * header: header => renderComponent(SortHeader, { label: 'Name', header }), + * }), + * columnHelper.accessor('state', { + * header: header => renderComponent(SortHeader, { label: 'State', header }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderComponent< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Component, + Props extends ComponentProps, +>(component: T, props: Props = {} as Props) { + return new RenderComponentConfig(component, props); +} + +/** + * A helper function to help create cells from Svelte Snippets through ColumnDef's `cell` and `header` properties. + * + * The snippet must only take one parameter. + * + * This is only to be used with Snippets - use `renderComponent` for Svelte Components. + * + * @param snippet + * @param params + * @returns - A `RenderSnippetConfig` object that helps svelte-table know how to render the header/cell snippet. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }), + * }), + * columnHelper.accessor('state', { + * cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderSnippet(snippet: Snippet<[TProps]>, params: TProps = {} as TProps) { + return new RenderSnippetConfig(snippet, params); +} diff --git a/apps/main/src/lib/components/ui/dialog/dialog-close.svelte b/apps/main/src/lib/components/ui/dialog/dialog-close.svelte new file mode 100644 index 0000000..840b2f6 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-content.svelte b/apps/main/src/lib/components/ui/dialog/dialog-content.svelte new file mode 100644 index 0000000..ae1a03f --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-content.svelte @@ -0,0 +1,45 @@ + + + + + + {@render children?.()} + {#if showCloseButton} + + + Close + + {/if} + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-description.svelte b/apps/main/src/lib/components/ui/dialog/dialog-description.svelte new file mode 100644 index 0000000..3845023 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-footer.svelte b/apps/main/src/lib/components/ui/dialog/dialog-footer.svelte new file mode 100644 index 0000000..e7ff446 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/dialog/dialog-header.svelte b/apps/main/src/lib/components/ui/dialog/dialog-header.svelte new file mode 100644 index 0000000..4e5c447 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte b/apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte new file mode 100644 index 0000000..f81ad83 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-portal.svelte b/apps/main/src/lib/components/ui/dialog/dialog-portal.svelte new file mode 100644 index 0000000..ccfa79c --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-title.svelte b/apps/main/src/lib/components/ui/dialog/dialog-title.svelte new file mode 100644 index 0000000..e4d4b34 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte b/apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte new file mode 100644 index 0000000..9d1e801 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/dialog.svelte b/apps/main/src/lib/components/ui/dialog/dialog.svelte new file mode 100644 index 0000000..211672c --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dialog/index.ts b/apps/main/src/lib/components/ui/dialog/index.ts new file mode 100644 index 0000000..076cef5 --- /dev/null +++ b/apps/main/src/lib/components/ui/dialog/index.ts @@ -0,0 +1,34 @@ +import Root from "./dialog.svelte"; +import Portal from "./dialog-portal.svelte"; +import Title from "./dialog-title.svelte"; +import Footer from "./dialog-footer.svelte"; +import Header from "./dialog-header.svelte"; +import Overlay from "./dialog-overlay.svelte"; +import Content from "./dialog-content.svelte"; +import Description from "./dialog-description.svelte"; +import Trigger from "./dialog-trigger.svelte"; +import Close from "./dialog-close.svelte"; + +export { + Root, + Title, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Close, + // + Root as Dialog, + Title as DialogTitle, + Portal as DialogPortal, + Footer as DialogFooter, + Header as DialogHeader, + Trigger as DialogTrigger, + Overlay as DialogOverlay, + Content as DialogContent, + Description as DialogDescription, + Close as DialogClose, +}; diff --git a/apps/main/src/lib/components/ui/drawer/drawer-close.svelte b/apps/main/src/lib/components/ui/drawer/drawer-close.svelte new file mode 100644 index 0000000..95c2479 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-content.svelte b/apps/main/src/lib/components/ui/drawer/drawer-content.svelte new file mode 100644 index 0000000..6bb01db --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-content.svelte @@ -0,0 +1,40 @@ + + + + + + + {@render children?.()} + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-description.svelte b/apps/main/src/lib/components/ui/drawer/drawer-description.svelte new file mode 100644 index 0000000..2763a1a --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-footer.svelte b/apps/main/src/lib/components/ui/drawer/drawer-footer.svelte new file mode 100644 index 0000000..1691f58 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/drawer/drawer-header.svelte b/apps/main/src/lib/components/ui/drawer/drawer-header.svelte new file mode 100644 index 0000000..65d2de5 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/drawer/drawer-nested.svelte b/apps/main/src/lib/components/ui/drawer/drawer-nested.svelte new file mode 100644 index 0000000..834af94 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-nested.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte b/apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte new file mode 100644 index 0000000..53f78a2 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-portal.svelte b/apps/main/src/lib/components/ui/drawer/drawer-portal.svelte new file mode 100644 index 0000000..5a0dd74 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-title.svelte b/apps/main/src/lib/components/ui/drawer/drawer-title.svelte new file mode 100644 index 0000000..a2e5761 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte b/apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte new file mode 100644 index 0000000..f1877d8 --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/drawer.svelte b/apps/main/src/lib/components/ui/drawer/drawer.svelte new file mode 100644 index 0000000..0cb57ff --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/drawer.svelte @@ -0,0 +1,12 @@ + + + diff --git a/apps/main/src/lib/components/ui/drawer/index.ts b/apps/main/src/lib/components/ui/drawer/index.ts new file mode 100644 index 0000000..1656cac --- /dev/null +++ b/apps/main/src/lib/components/ui/drawer/index.ts @@ -0,0 +1,38 @@ +import Root from "./drawer.svelte"; +import Content from "./drawer-content.svelte"; +import Description from "./drawer-description.svelte"; +import Overlay from "./drawer-overlay.svelte"; +import Footer from "./drawer-footer.svelte"; +import Header from "./drawer-header.svelte"; +import Title from "./drawer-title.svelte"; +import NestedRoot from "./drawer-nested.svelte"; +import Close from "./drawer-close.svelte"; +import Trigger from "./drawer-trigger.svelte"; +import Portal from "./drawer-portal.svelte"; + +export { + Root, + NestedRoot, + Content, + Description, + Overlay, + Footer, + Header, + Title, + Trigger, + Portal, + Close, + + // + Root as Drawer, + NestedRoot as DrawerNestedRoot, + Content as DrawerContent, + Description as DrawerDescription, + Overlay as DrawerOverlay, + Footer as DrawerFooter, + Header as DrawerHeader, + Title as DrawerTitle, + Trigger as DrawerTrigger, + Portal as DrawerPortal, + Close as DrawerClose, +}; diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte new file mode 100644 index 0000000..e0e1971 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte new file mode 100644 index 0000000..6d9ef85 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -0,0 +1,43 @@ + + + + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + + {:else} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte new file mode 100644 index 0000000..1e96782 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte new file mode 100644 index 0000000..433540f --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte @@ -0,0 +1,22 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte new file mode 100644 index 0000000..aca1f7b --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte new file mode 100644 index 0000000..04cd110 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte new file mode 100644 index 0000000..9681c2b --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte new file mode 100644 index 0000000..274cfef --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte new file mode 100644 index 0000000..189aef4 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte new file mode 100644 index 0000000..ce2ad09 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -0,0 +1,33 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte new file mode 100644 index 0000000..90f1b6f --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte new file mode 100644 index 0000000..7c6e9c6 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte new file mode 100644 index 0000000..3f06dc4 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte new file mode 100644 index 0000000..5f49d01 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte new file mode 100644 index 0000000..f044581 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte new file mode 100644 index 0000000..cb05344 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte new file mode 100644 index 0000000..cb4bc62 --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/dropdown-menu/index.ts b/apps/main/src/lib/components/ui/dropdown-menu/index.ts new file mode 100644 index 0000000..7850c6a --- /dev/null +++ b/apps/main/src/lib/components/ui/dropdown-menu/index.ts @@ -0,0 +1,54 @@ +import Root from "./dropdown-menu.svelte"; +import Sub from "./dropdown-menu-sub.svelte"; +import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte"; +import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; +import Content from "./dropdown-menu-content.svelte"; +import Group from "./dropdown-menu-group.svelte"; +import Item from "./dropdown-menu-item.svelte"; +import Label from "./dropdown-menu-label.svelte"; +import RadioGroup from "./dropdown-menu-radio-group.svelte"; +import RadioItem from "./dropdown-menu-radio-item.svelte"; +import Separator from "./dropdown-menu-separator.svelte"; +import Shortcut from "./dropdown-menu-shortcut.svelte"; +import Trigger from "./dropdown-menu-trigger.svelte"; +import SubContent from "./dropdown-menu-sub-content.svelte"; +import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; +import GroupHeading from "./dropdown-menu-group-heading.svelte"; +import Portal from "./dropdown-menu-portal.svelte"; + +export { + CheckboxGroup, + CheckboxItem, + Content, + Portal, + Root as DropdownMenu, + CheckboxGroup as DropdownMenuCheckboxGroup, + CheckboxItem as DropdownMenuCheckboxItem, + Content as DropdownMenuContent, + Portal as DropdownMenuPortal, + Group as DropdownMenuGroup, + Item as DropdownMenuItem, + Label as DropdownMenuLabel, + RadioGroup as DropdownMenuRadioGroup, + RadioItem as DropdownMenuRadioItem, + Separator as DropdownMenuSeparator, + Shortcut as DropdownMenuShortcut, + Sub as DropdownMenuSub, + SubContent as DropdownMenuSubContent, + SubTrigger as DropdownMenuSubTrigger, + Trigger as DropdownMenuTrigger, + GroupHeading as DropdownMenuGroupHeading, + Group, + GroupHeading, + Item, + Label, + RadioGroup, + RadioItem, + Root, + Separator, + Shortcut, + Sub, + SubContent, + SubTrigger, + Trigger, +}; diff --git a/apps/main/src/lib/components/ui/empty/empty-content.svelte b/apps/main/src/lib/components/ui/empty/empty-content.svelte new file mode 100644 index 0000000..f5a9c68 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-content.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-description.svelte b/apps/main/src/lib/components/ui/empty/empty-description.svelte new file mode 100644 index 0000000..85a866c --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-description.svelte @@ -0,0 +1,23 @@ + + +
    a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...restProps} +> + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-header.svelte b/apps/main/src/lib/components/ui/empty/empty-header.svelte new file mode 100644 index 0000000..296eaf8 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-media.svelte b/apps/main/src/lib/components/ui/empty/empty-media.svelte new file mode 100644 index 0000000..0b4e45d --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-media.svelte @@ -0,0 +1,41 @@ + + + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty-title.svelte b/apps/main/src/lib/components/ui/empty/empty-title.svelte new file mode 100644 index 0000000..8c237aa --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/empty.svelte b/apps/main/src/lib/components/ui/empty/empty.svelte new file mode 100644 index 0000000..4ccf060 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/empty.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/empty/index.ts b/apps/main/src/lib/components/ui/empty/index.ts new file mode 100644 index 0000000..ae4c106 --- /dev/null +++ b/apps/main/src/lib/components/ui/empty/index.ts @@ -0,0 +1,22 @@ +import Root from "./empty.svelte"; +import Header from "./empty-header.svelte"; +import Media from "./empty-media.svelte"; +import Title from "./empty-title.svelte"; +import Description from "./empty-description.svelte"; +import Content from "./empty-content.svelte"; + +export { + Root, + Header, + Media, + Title, + Description, + Content, + // + Root as Empty, + Header as EmptyHeader, + Media as EmptyMedia, + Title as EmptyTitle, + Description as EmptyDescription, + Content as EmptyContent, +}; diff --git a/apps/main/src/lib/components/ui/field/field-content.svelte b/apps/main/src/lib/components/ui/field/field-content.svelte new file mode 100644 index 0000000..1b6535b --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-content.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field-description.svelte b/apps/main/src/lib/components/ui/field/field-description.svelte new file mode 100644 index 0000000..a0c9f06 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-description.svelte @@ -0,0 +1,25 @@ + + +

    a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...restProps} +> + {@render children?.()} +

    diff --git a/apps/main/src/lib/components/ui/field/field-error.svelte b/apps/main/src/lib/components/ui/field/field-error.svelte new file mode 100644 index 0000000..1d5cc5f --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-error.svelte @@ -0,0 +1,58 @@ + + +{#if hasContent} + +{/if} diff --git a/apps/main/src/lib/components/ui/field/field-group.svelte b/apps/main/src/lib/components/ui/field/field-group.svelte new file mode 100644 index 0000000..e685427 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-group.svelte @@ -0,0 +1,23 @@ + + +
    [data-slot=field-group]]:gap-4", + className + )} + {...restProps} +> + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field-label.svelte b/apps/main/src/lib/components/ui/field/field-label.svelte new file mode 100644 index 0000000..2ee431a --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-label.svelte @@ -0,0 +1,26 @@ + + + diff --git a/apps/main/src/lib/components/ui/field/field-legend.svelte b/apps/main/src/lib/components/ui/field/field-legend.svelte new file mode 100644 index 0000000..3f1c50f --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-legend.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/field/field-separator.svelte b/apps/main/src/lib/components/ui/field/field-separator.svelte new file mode 100644 index 0000000..12bcb77 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-separator.svelte @@ -0,0 +1,38 @@ + + +
    + + {#if children} + + {@render children()} + + {/if} +
    diff --git a/apps/main/src/lib/components/ui/field/field-set.svelte b/apps/main/src/lib/components/ui/field/field-set.svelte new file mode 100644 index 0000000..1d8e233 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-set.svelte @@ -0,0 +1,24 @@ + + +
    [data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...restProps} +> + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field-title.svelte b/apps/main/src/lib/components/ui/field/field-title.svelte new file mode 100644 index 0000000..5906044 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field-title.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/field.svelte b/apps/main/src/lib/components/ui/field/field.svelte new file mode 100644 index 0000000..3284203 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/field.svelte @@ -0,0 +1,53 @@ + + + + +
    + {@render children?.()} +
    diff --git a/apps/main/src/lib/components/ui/field/index.ts b/apps/main/src/lib/components/ui/field/index.ts new file mode 100644 index 0000000..a644a95 --- /dev/null +++ b/apps/main/src/lib/components/ui/field/index.ts @@ -0,0 +1,33 @@ +import Field from "./field.svelte"; +import Set from "./field-set.svelte"; +import Legend from "./field-legend.svelte"; +import Group from "./field-group.svelte"; +import Content from "./field-content.svelte"; +import Label from "./field-label.svelte"; +import Title from "./field-title.svelte"; +import Description from "./field-description.svelte"; +import Separator from "./field-separator.svelte"; +import Error from "./field-error.svelte"; + +export { + Field, + Set, + Legend, + Group, + Content, + Label, + Title, + Description, + Separator, + Error, + // + Set as FieldSet, + Legend as FieldLegend, + Group as FieldGroup, + Content as FieldContent, + Label as FieldLabel, + Title as FieldTitle, + Description as FieldDescription, + Separator as FieldSeparator, + Error as FieldError, +}; diff --git a/apps/main/src/lib/components/ui/form/form-button.svelte b/apps/main/src/lib/components/ui/form/form-button.svelte new file mode 100644 index 0000000..48d3936 --- /dev/null +++ b/apps/main/src/lib/components/ui/form/form-button.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/input-group/input-group-input.svelte b/apps/main/src/lib/components/ui/input-group/input-group-input.svelte new file mode 100644 index 0000000..ded2655 --- /dev/null +++ b/apps/main/src/lib/components/ui/input-group/input-group-input.svelte @@ -0,0 +1,23 @@ + + + diff --git a/apps/main/src/lib/components/ui/input-group/input-group-text.svelte b/apps/main/src/lib/components/ui/input-group/input-group-text.svelte new file mode 100644 index 0000000..9c43dc4 --- /dev/null +++ b/apps/main/src/lib/components/ui/input-group/input-group-text.svelte @@ -0,0 +1,22 @@ + + + + {@render children?.()} + diff --git a/apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte b/apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte new file mode 100644 index 0000000..91850ff --- /dev/null +++ b/apps/main/src/lib/components/ui/input-group/input-group-textarea.svelte @@ -0,0 +1,23 @@ + + + diff --git a/apps/main/src/lib/components/ui/toggle-group/index.ts b/apps/main/src/lib/components/ui/toggle-group/index.ts new file mode 100644 index 0000000..12b14b9 --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle-group/index.ts @@ -0,0 +1,10 @@ +import Root from "./toggle-group.svelte"; +import Item from "./toggle-group-item.svelte"; + +export { + Root, + Item, + // + Root as ToggleGroup, + Item as ToggleGroupItem, +}; diff --git a/apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte b/apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte new file mode 100644 index 0000000..6d60b52 --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle-group/toggle-group-item.svelte @@ -0,0 +1,35 @@ + + + diff --git a/apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte b/apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte new file mode 100644 index 0000000..106561c --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle-group/toggle-group.svelte @@ -0,0 +1,59 @@ + + + + + + diff --git a/apps/main/src/lib/components/ui/toggle/index.ts b/apps/main/src/lib/components/ui/toggle/index.ts new file mode 100644 index 0000000..8cb2936 --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle/index.ts @@ -0,0 +1,13 @@ +import Root from "./toggle.svelte"; +export { + toggleVariants, + type ToggleSize, + type ToggleVariant, + type ToggleVariants, +} from "./toggle.svelte"; + +export { + Root, + // + Root as Toggle, +}; diff --git a/apps/main/src/lib/components/ui/toggle/toggle.svelte b/apps/main/src/lib/components/ui/toggle/toggle.svelte new file mode 100644 index 0000000..56eb86b --- /dev/null +++ b/apps/main/src/lib/components/ui/toggle/toggle.svelte @@ -0,0 +1,52 @@ + + + + + diff --git a/apps/main/src/lib/components/ui/tooltip/index.ts b/apps/main/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 0000000..1718604 --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,19 @@ +import Root from "./tooltip.svelte"; +import Trigger from "./tooltip-trigger.svelte"; +import Content from "./tooltip-content.svelte"; +import Provider from "./tooltip-provider.svelte"; +import Portal from "./tooltip-portal.svelte"; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal, +}; diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..788ec34 --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,52 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
    + {/snippet} +
    +
    +
    diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte new file mode 100644 index 0000000..d234f7d --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte new file mode 100644 index 0000000..8150bef --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-provider.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000..1acdaa4 --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/components/ui/tooltip/tooltip.svelte b/apps/main/src/lib/components/ui/tooltip/tooltip.svelte new file mode 100644 index 0000000..0b0f9ce --- /dev/null +++ b/apps/main/src/lib/components/ui/tooltip/tooltip.svelte @@ -0,0 +1,7 @@ + + + diff --git a/apps/main/src/lib/core/constants.ts b/apps/main/src/lib/core/constants.ts new file mode 100644 index 0000000..9f1d02f --- /dev/null +++ b/apps/main/src/lib/core/constants.ts @@ -0,0 +1,56 @@ +import LayoutDashboard from "@lucide/svelte/icons/layout-dashboard"; +import Smartphone from "@lucide/svelte/icons/smartphone"; +import { BellRingIcon, Link } from "@lucide/svelte"; +import UserCircle from "~icons/lucide/user-circle"; + +export type AppSidebarItem = { + title: string; + url: string; + icon?: any; + isActive?: boolean; + items?: { + title: string; + url: string; + }[]; +}; + +export const mainNavTree = [ + { + title: "Dashboard", + url: "/dashboard", + icon: LayoutDashboard, + isActive: true, + }, + { + title: "Links", + url: "/links", + icon: Link, + }, + { + title: "Devices", + url: "/devices", + icon: Smartphone, + }, +] as AppSidebarItem[]; + +export const secondaryNavTree = [ + { + title: "Account", + url: "/account", + icon: UserCircle, + }, + { + title: "Notifications", + url: "/notifications", + icon: BellRingIcon, + }, +] as AppSidebarItem[]; + +export const COMPANY_NAME = "SaaS Template"; +export const WEBSITE_URL = "https://company.com"; + +export const CONTACT_EMAIL = "contact@company.com"; +export const CONTACT_INFO = { email: CONTACT_EMAIL }; + +export const TRANSITION_COLORS = "transition-colors duration-150 ease-in-out"; +export const TRANSITION_ALL = "transition-all duration-150 ease-in-out"; diff --git a/apps/main/src/lib/core/server.utils.ts b/apps/main/src/lib/core/server.utils.ts new file mode 100644 index 0000000..450c7dd --- /dev/null +++ b/apps/main/src/lib/core/server.utils.ts @@ -0,0 +1,25 @@ +import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context"; +import type { Err } from "@pkg/result"; + +export async function getFlowExecCtxForRemoteFuncs( + locals: App.Locals, +): Promise { + return { + flowId: locals.flowId || crypto.randomUUID(), + userId: locals.user?.id, + sessionId: locals.session?.id, + }; +} + +export function unauthorized(fctx: FlowExecCtx) { + return { + data: null, + error: { + flowId: fctx.flowId, + code: "UNAUTHORIZED", + message: "User not authenticated", + description: "Please log in", + detail: "No user ID found in session", + } as Err, + }; +} diff --git a/apps/main/src/lib/domains/account/account.remote.ts b/apps/main/src/lib/domains/account/account.remote.ts new file mode 100644 index 0000000..3dc2e38 --- /dev/null +++ b/apps/main/src/lib/domains/account/account.remote.ts @@ -0,0 +1,168 @@ +import { + banUserSchema, + checkUsernameSchema, + ensureAccountExistsSchema, + rotatePasswordSchema, +} from "@pkg/logic/domains/user/data"; +import { + getFlowExecCtxForRemoteFuncs, + unauthorized, +} from "$lib/core/server.utils"; +import { getUserController } from "@pkg/logic/domains/user/controller"; +import { command, getRequestEvent, query } from "$app/server"; +import * as v from "valibot"; + +const uc = getUserController(); + +export const getMyInfoSQ = query(async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.getUserInfo(fctx, fctx.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const getUserInfoByIdSQ = query( + v.object({ userId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.getUserInfo(fctx, input.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const ensureAccountExistsSC = command( + ensureAccountExistsSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.ensureAccountExists(fctx, payload.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const checkUsernameSC = command(checkUsernameSchema, async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.isUsernameAvailable(fctx, payload.username); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const update2faVerifiedSC = command( + v.object({ userId: v.string() }), + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.updateLastVerified2FaAtToNow(fctx, payload.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const banUserSC = command(banUserSchema, async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.banUser( + fctx, + payload.userId, + payload.reason, + payload.banExpiresAt, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const isUserBannedSQ = query( + v.object({ userId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.isUserBanned(fctx, input.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const getBanInfoSQ = query( + v.object({ userId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.getBanInfo(fctx, input.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const rotatePasswordSC = command( + rotatePasswordSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.rotatePassword( + fctx, + payload.userId, + payload.password, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); diff --git a/apps/main/src/lib/domains/account/account.vm.svelte.ts b/apps/main/src/lib/domains/account/account.vm.svelte.ts new file mode 100644 index 0000000..f17913f --- /dev/null +++ b/apps/main/src/lib/domains/account/account.vm.svelte.ts @@ -0,0 +1,153 @@ +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import type { User } from "@pkg/logic/domains/user/data"; +import { user as userStore } from "$lib/global.stores"; +import { rotatePasswordSC } from "./account.remote"; +import { authClient } from "$lib/auth.client"; +import type { Err } from "@pkg/result"; +import { toast } from "svelte-sonner"; +import { get } from "svelte/store"; + +class AccountViewModel { + loading = $state(false); + passwordLoading = $state(false); + errorMessage = $state(null); + + async updateProfilePicture(imagePath: string): Promise { + const result = await ResultAsync.fromPromise( + authClient.updateUser({ image: imagePath }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to update profile picture", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ).andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: + response.error.message ?? + "Failed to update profile picture", + description: + response.error.statusText ?? "Please try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + return result.match( + () => { + toast.success("Profile picture updated"); + return true; + }, + (error) => { + this.errorMessage = + error.message ?? "Failed to update profile picture"; + toast.error(this.errorMessage, { + description: error.description, + }); + return false; + }, + ); + } + + async updateProfile(userData: { + name: string; + username: string; + }): Promise { + this.loading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.updateUser({ + displayUsername: userData.username, + username: userData.username, + name: userData.name, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to update profile", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ).andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: + response.error.message ?? "Failed to update profile", + description: + response.error.statusText ?? "Please try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + const user = result.match( + (data) => { + toast.success("Profile updated successfully"); + window.location.reload(); + return (data as any)?.user as User | null; + }, + (error) => { + this.errorMessage = error.message ?? "Failed to update profile"; + toast.error(this.errorMessage, { + description: error.description, + }); + return null; + }, + ); + + this.loading = false; + return user; + } + + async changePassword(password: string): Promise { + this.passwordLoading = true; + this.errorMessage = null; + + const currentUser = get(userStore); + if (!currentUser?.id) { + this.passwordLoading = false; + this.errorMessage = "User not found"; + toast.error(this.errorMessage); + return false; + } + const result = await ResultAsync.fromPromise( + rotatePasswordSC({ userId: currentUser.id, password }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to change password", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ).andThen((apiResult: any) => { + if (apiResult?.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult?.data); + }); + + const success = result.match( + () => { + toast.success("Password updated successfully"); + return true; + }, + (error) => { + this.errorMessage = + (error.message as string) ?? "Failed to change password"; + toast.error(this.errorMessage, { + description: error.description, + }); + return false; + }, + ); + + this.passwordLoading = false; + return success; + } +} + +export const accountVM = new AccountViewModel(); diff --git a/apps/main/src/lib/domains/account/sessions/sessions-card.svelte b/apps/main/src/lib/domains/account/sessions/sessions-card.svelte new file mode 100644 index 0000000..36c64a1 --- /dev/null +++ b/apps/main/src/lib/domains/account/sessions/sessions-card.svelte @@ -0,0 +1,182 @@ + + + + +
    + + Active Sessions +
    + + Manage and monitor your active login sessions across devices. + +
    + +
    + {#if sessionsVM.isLoading && sessionsVM.activeSessions.length === 0} +
    + +
    + {:else if sessionsVM.activeSessions.length > 0} +
    + {#each sessionsVM.activeSessions as session} + {@const { os, browser } = extractInfoFromUA( + session.userAgent ?? "", + )} +
    +
    +
    + +
    +
    +
    +

    + {browser || "Unknown Browser"} + on + {os || "Unknown OS"} +

    + {#if session.isCurrent} + + Current Session + + {/if} +
    +
    + {session.ipAddress || "Unknown IP"} +
    +
    + + + Created{" "} + {sessionsVM.formatRelativeTime( + session.createdAt.getTime(), + )} + +
    +
    +
    + {#if !session.isCurrent} + + {/if} +
    + {/each} +
    + + {#if sessionsVM.activeSessions.filter((s) => !s.isCurrent).length > 0} + + {/if} + {:else} +
    +

    + No active sessions found. This is unusual and might + indicate a problem. +

    +
    + {/if} +
    +
    +
    diff --git a/apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts b/apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts new file mode 100644 index 0000000..d6553cf --- /dev/null +++ b/apps/main/src/lib/domains/account/sessions/sessions.vm.svelte.ts @@ -0,0 +1,209 @@ +import type { ModifiedSession } from "@pkg/logic/domains/user/data"; +import { authClient } from "$lib/auth.client"; +import { toast } from "svelte-sonner"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import type { Err } from "@pkg/result"; + +class SessionsViewModel { + session: ModifiedSession | undefined = $state(undefined); + activeSessions = $state([]); + isLoading = $state(false); + errorMessage = $state(null); + + async setCurrentSession(s: ModifiedSession) { + this.session = s; + } + + async fetchActiveSessions() { + this.isLoading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.listSessions(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to fetch active sessions", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Failed to fetch active sessions", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data ?? []); + }); + + const sessions = result.match( + (data) => { + this.activeSessions = data.map((session: ModifiedSession) => ({ + ...session, + isCurrent: session.id === this.session?.id, + })); + return this.activeSessions; + }, + (error) => { + this.errorMessage = error.message ?? "Failed to fetch active sessions"; + toast.error("Failed to fetch active sessions", { + description: error.description, + }); + return []; + }, + ); + + this.isLoading = false; + return sessions; + } + + async terminateSession(sessionId: string) { + this.isLoading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.revokeSession({ + token: sessionId, + }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to terminate session", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Failed to terminate session", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + result.match( + () => { + this.activeSessions = this.activeSessions.filter( + (session) => session.id !== sessionId, + ); + toast.success("Session terminated"); + }, + (error) => { + this.errorMessage = error.message ?? "Failed to terminate session"; + toast.error("Failed to terminate session", { + description: error.description, + }); + }, + ); + + this.isLoading = false; + } + + async terminateAllOtherSessions() { + this.isLoading = true; + this.errorMessage = null; + + const result = await ResultAsync.fromPromise( + authClient.revokeOtherSessions(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to terminate other sessions", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: "Failed to terminate other sessions", + description: response.error.message ?? "Unknown error", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + result.match( + () => { + this.activeSessions = this.activeSessions.filter( + // @ts-ignore + (session) => session.isCurrent, + ); + toast.success("All other sessions terminated"); + }, + (error) => { + this.errorMessage = + error.message ?? "Failed to terminate other sessions"; + toast.error("Failed to terminate other sessions", { + description: error.description, + }); + }, + ); + + this.isLoading = false; + } + + async logout(skipToast = false) { + const result = await ResultAsync.fromPromise( + authClient.signOut(), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to log out", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ); + + result.match( + () => { + if (!skipToast) { + toast("Logged out successfully, redirecting..."); + } + setTimeout(() => { + window.location.href = "/auth/login"; + }, 500); + }, + (error) => { + toast.error("Failed to log out", { + description: error.description, + }); + }, + ); + } + + formatRelativeTime(timestamp: string | number): string { + const date = new Date(timestamp); + const now = new Date(); + const diffInSeconds = Math.floor( + (now.getTime() - date.getTime()) / 1000, + ); + + if (diffInSeconds < 60) { + return "just now"; + } else if (diffInSeconds < 3600) { + const minutes = Math.floor(diffInSeconds / 60); + return `${minutes} minute${minutes > 1 ? "s" : ""} ago`; + } else if (diffInSeconds < 86400) { + const hours = Math.floor(diffInSeconds / 3600); + return `${hours} hour${hours > 1 ? "s" : ""} ago`; + } else { + const days = Math.floor(diffInSeconds / 86400); + return `${days} day${days > 1 ? "s" : ""} ago`; + } + } + + reset() { + this.activeSessions = []; + this.isLoading = false; + this.errorMessage = null; + } +} + +export const sessionsVM = new SessionsViewModel(); diff --git a/apps/main/src/lib/domains/notifications/notification.vm.svelte.ts b/apps/main/src/lib/domains/notifications/notification.vm.svelte.ts new file mode 100644 index 0000000..a9d23a8 --- /dev/null +++ b/apps/main/src/lib/domains/notifications/notification.vm.svelte.ts @@ -0,0 +1,235 @@ +import type { + ClientNotificationFilters, + ClientPaginationState, + Notifications, +} from "@pkg/logic/domains/notifications/data"; +import { + archiveSC, + deleteNotificationsSC, + getNotificationsSQ, + getUnreadCountSQ, + markAllReadSC, + markReadSC, + markUnreadSC, + unarchiveSC, +} from "./notifications.remote"; +import { user } from "$lib/global.stores"; +import { toast } from "svelte-sonner"; +import { get } from "svelte/store"; + +class NotificationViewModel { + notifications = $state([] as Notifications); + loading = $state(false); + selectedIds = $state(new Set()); + + pagination = $state({ + page: 1, + pageSize: 20, + total: 0, + totalPages: 0, + sortBy: "createdAt", + sortOrder: "desc", + }); + + filters = $state({ + userId: get(user)?.id!, + isArchived: false, + }); + + unreadCount = $state(0); + + private getFetchQueryInput() { + return { + filters: { + userId: this.filters.userId, + isRead: this.filters.isRead, + isArchived: this.filters.isArchived, + type: this.filters.type, + category: this.filters.category, + priority: this.filters.priority, + search: this.filters.search, + }, + pagination: { + page: this.pagination.page, + pageSize: this.pagination.pageSize, + sortBy: this.pagination.sortBy, + sortOrder: this.pagination.sortOrder, + }, + }; + } + + private async runCommand( + fn: (payload: { notificationIds: number[] }) => Promise, + notificationIds: number[], + errorMessage: string, + after: Array<() => Promise>, + ) { + try { + const result = await fn({ notificationIds }); + if (result?.error) { + toast.error(result.error.message || errorMessage, { + description: result.error.description || "Please try again later", + }); + return; + } + + for (const action of after) { + await action(); + } + } catch (error) { + toast.error(errorMessage, { + description: + error instanceof Error ? error.message : "Please try again later", + }); + } + } + + async fetchNotifications() { + this.loading = true; + try { + const result = await getNotificationsSQ(this.getFetchQueryInput()); + if (result?.error || !result?.data) { + toast.error( + result?.error?.message || "Failed to fetch notifications", + { + description: + result?.error?.description || "Please try again later", + }, + ); + return; + } + + this.notifications = result.data.data as Notifications; + this.pagination.total = result.data.total; + this.pagination.totalPages = result.data.totalPages; + } catch (error) { + toast.error("Failed to fetch notifications", { + description: + error instanceof Error ? error.message : "Please try again later", + }); + } finally { + this.loading = false; + } + } + + async markAsRead(notificationIds: number[]) { + await this.runCommand(markReadSC, notificationIds, "Failed to mark as read", [ + () => this.fetchNotifications(), + () => this.fetchUnreadCount(), + ]); + } + + async markAsUnread(notificationIds: number[]) { + await this.runCommand( + markUnreadSC, + notificationIds, + "Failed to mark as unread", + [() => this.fetchNotifications(), () => this.fetchUnreadCount()], + ); + } + + async archive(notificationIds: number[]) { + await this.runCommand(archiveSC, notificationIds, "Failed to archive", [ + () => this.fetchNotifications(), + ]); + } + + async unarchive(notificationIds: number[]) { + await this.runCommand(unarchiveSC, notificationIds, "Failed to unarchive", [ + () => this.fetchNotifications(), + ]); + } + + async deleteNotifications(notificationIds: number[]) { + await this.runCommand( + deleteNotificationsSC, + notificationIds, + "Failed to delete", + [() => this.fetchNotifications(), () => this.fetchUnreadCount()], + ); + } + + async markAllAsRead() { + try { + const result = await markAllReadSC({}); + if (result?.error) { + toast.error(result.error.message || "Failed to mark all as read", { + description: result.error.description || "Please try again later", + }); + return; + } + await this.fetchNotifications(); + await this.fetchUnreadCount(); + } catch (error) { + toast.error("Failed to mark all as read", { + description: + error instanceof Error ? error.message : "Please try again later", + }); + } + } + + async fetchUnreadCount() { + try { + const result = await getUnreadCountSQ(); + if (result?.error) { + return; + } + if (result?.data !== undefined && result?.data !== null) { + this.unreadCount = result.data as number; + } + } catch { + // Intentionally silent - unread count is non-critical UI data. + } + } + + toggleSelection(id: number) { + if (this.selectedIds.has(id)) { + this.selectedIds.delete(id); + } else { + this.selectedIds.add(id); + } + } + + selectAll() { + this.notifications.forEach((n) => this.selectedIds.add(n.id)); + } + + clearSelection() { + this.selectedIds.clear(); + } + + goToPage(page: number) { + this.pagination.page = page; + this.fetchNotifications(); + } + + setPageSize(pageSize: number) { + this.pagination.pageSize = pageSize; + this.pagination.page = 1; + this.fetchNotifications(); + } + + setSorting( + sortBy: ClientPaginationState["sortBy"], + sortOrder: ClientPaginationState["sortOrder"], + ) { + this.pagination.sortBy = sortBy; + this.pagination.sortOrder = sortOrder; + this.pagination.page = 1; + this.fetchNotifications(); + } + + setFilters(newFilters: Partial) { + this.filters = { ...this.filters, ...newFilters }; + this.pagination.page = 1; + this.fetchNotifications(); + } + + clearFilters() { + this.filters = { userId: get(user)?.id!, isArchived: false }; + this.pagination.page = 1; + this.fetchNotifications(); + } +} + +export const notificationViewModel = new NotificationViewModel(); diff --git a/apps/main/src/lib/domains/notifications/notifications-table.svelte b/apps/main/src/lib/domains/notifications/notifications-table.svelte new file mode 100644 index 0000000..cc263a1 --- /dev/null +++ b/apps/main/src/lib/domains/notifications/notifications-table.svelte @@ -0,0 +1,566 @@ + + + + +
    +
    + + Notifications + {#if notificationViewModel.unreadCount > 0} + + {notificationViewModel.unreadCount} unread + + {/if} +
    + + {#if hasSelection} +
    + + {notificationViewModel.selectedIds.size} selected + + + + +
    + {:else} + + {/if} +
    + +
    + +
    + + +
    + + +
    + + + + Filters + + + + handleFilterChange("isRead", undefined)} + > + All + + handleFilterChange("isRead", false)} + > + Unread Only + + handleFilterChange("isRead", true)} + > + Read Only + + + + handleFilterChange("isArchived", false)} + > + Active + + handleFilterChange("isArchived", true)} + > + Archived + + + +
    +
    +
    + + + {#if notificationViewModel.loading && notificationViewModel.notifications.length === 0} +
    + +
    + {:else if notificationViewModel.notifications.length === 0} +
    + +

    No notifications

    +

    + {notificationViewModel.filters.search + ? "No notifications match your search." + : "You're all caught up!"} +

    +
    + {:else} +
    + + + + + + + + + Notification + Priority + Time + + + + + {#each notificationViewModel.notifications as notification (notification.id)} + + + + handleRowSelect( + notification.id, + checked, + )} + /> + + +
    + +
    +
    + +
    +

    + {notification.title} +

    +

    + {notification.body} +

    + {#if notification.category} + + {notification.category} + + {/if} +
    +
    + + + {notification.priority} + + + +
    + + {formatRelativeTime( + notification.sentAt, + )} +
    +
    + + + + + + + {#if notification.isRead} + + handleSingleAction( + notification.id, + "mark-unread", + )} + > + Mark as Unread + + {:else} + + handleSingleAction( + notification.id, + "mark-read", + )} + > + Mark as Read + + {/if} + + handleSingleAction( + notification.id, + "archive", + )} + > + Archive + + + + handleSingleAction( + notification.id, + "delete", + )} + class="text-destructive" + > + Delete + + + + +
    + {/each} +
    +
    + + + {#if notificationViewModel.pagination.totalPages > 1} +
    +
    + Showing {(notificationViewModel.pagination.page - 1) * + notificationViewModel.pagination.pageSize + + 1} to {Math.min( + notificationViewModel.pagination.page * + notificationViewModel.pagination.pageSize, + notificationViewModel.pagination.total, + )} of {notificationViewModel.pagination.total} notifications +
    + +
    + + +
    + {#each Array.from( { length: Math.min(5, notificationViewModel.pagination.totalPages) }, (_, i) => { + const startPage = Math.max(1, notificationViewModel.pagination.page - 2); + return startPage + i; + }, ) as page} + {#if page <= notificationViewModel.pagination.totalPages} + + {/if} + {/each} +
    + + +
    +
    + {/if} +
    + {/if} +
    +
    + + diff --git a/apps/main/src/lib/domains/notifications/notifications.remote.ts b/apps/main/src/lib/domains/notifications/notifications.remote.ts new file mode 100644 index 0000000..1c8a8a4 --- /dev/null +++ b/apps/main/src/lib/domains/notifications/notifications.remote.ts @@ -0,0 +1,155 @@ +import { + bulkNotificationIdsSchema, + getNotificationsSchema, +} from "@pkg/logic/domains/notifications/data"; +import { getNotificationController } from "@pkg/logic/domains/notifications/controller"; +import { + getFlowExecCtxForRemoteFuncs, + unauthorized, +} from "$lib/core/server.utils"; +import { command, getRequestEvent, query } from "$app/server"; +import * as v from "valibot"; + +const nc = getNotificationController(); + +export const getNotificationsSQ = query( + getNotificationsSchema, + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + const res = await nc.getNotifications( + fctx, + { ...input.filters, userId: fctx.userId }, + input.pagination, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const markReadSC = command( + bulkNotificationIdsSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await nc.markAsRead( + fctx, + [...payload.notificationIds], + fctx.userId, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const markUnreadSC = command( + bulkNotificationIdsSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await nc.markAsUnread( + fctx, + [...payload.notificationIds], + fctx.userId, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const archiveSC = command(bulkNotificationIdsSchema, async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await nc.archive( + fctx, + [...payload.notificationIds], + fctx.userId, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const unarchiveSC = command( + bulkNotificationIdsSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await nc.unarchive( + fctx, + [...payload.notificationIds], + fctx.userId, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const deleteNotificationsSC = command( + bulkNotificationIdsSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await nc.deleteNotifications( + fctx, + [...payload.notificationIds], + fctx.userId, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const markAllReadSC = command(v.object({}), async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await nc.markAllAsRead(fctx, fctx.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const getUnreadCountSQ = query(async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await nc.getUnreadCount(fctx, fctx.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); diff --git a/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts b/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts new file mode 100644 index 0000000..c5ed5ab --- /dev/null +++ b/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts @@ -0,0 +1,160 @@ +import { session, user } from "$lib/global.stores"; +import { authClient } from "$lib/auth.client"; +import { + startVerificationSessionSC, + verifySessionCodeSC, +} from "$lib/domains/security/twofa.remote"; +import { toast } from "svelte-sonner"; +import { get } from "svelte/store"; +import { page } from "$app/state"; + +class TwoFactorVerifyViewModel { + verifying = $state(false); + verificationCode = $state(""); + verificationToken = $state(null); + errorMessage = $state(null); + startingVerification = $state(false); + + async startVerification() { + this.startingVerification = true; + this.errorMessage = null; + + const currentUser = get(user); + const currentSession = get(session); + + const uid = currentUser?.id; + const sid = currentSession?.id; + + if (!uid || !sid) { + this.errorMessage = "No active session found"; + toast.error("Failed to start verification", { + description: this.errorMessage, + }); + this.startingVerification = false; + return; + } + + try { + const result = await startVerificationSessionSC({ + userId: uid, + sessionId: sid, + }); + + if (result?.error || !result?.data?.verificationToken) { + this.errorMessage = + result?.error?.message || "Failed to start verification"; + toast.error("Failed to start verification", { + description: this.errorMessage, + }); + return; + } + + this.verificationToken = result.data.verificationToken; + } catch (error) { + this.errorMessage = "Failed to start verification"; + toast.error("Failed to start verification", { + description: + error instanceof Error + ? error.message + : "Failed to start verification", + }); + } finally { + this.startingVerification = false; + } + } + + async verifyCode() { + if (!this.verificationToken) { + this.errorMessage = "No verification session found"; + return; + } + + if (!this.verificationCode || this.verificationCode.length !== 6) { + this.errorMessage = "Please enter a valid 6-digit code"; + return; + } + + this.verifying = true; + this.errorMessage = null; + + try { + const result = await verifySessionCodeSC({ + verificationToken: this.verificationToken, + code: this.verificationCode, + }); + + if (result?.error || !result?.data?.success) { + this.errorMessage = + result?.error?.message || "Failed to verify code"; + + if (result?.error?.code === "BANNED") { + await authClient.signOut(); + window.location.href = "/auth/login"; + return; + } + + if ( + this.errorMessage && + !this.errorMessage.includes("Invalid") && + !this.errorMessage.includes("already been used") + ) { + toast.error("Verification failed", { + description: this.errorMessage, + }); + } + return; + } + + const redirectUrl = page.url.searchParams.get("redirect") || "/"; + window.location.href = redirectUrl; + } catch (error) { + this.errorMessage = "Failed to verify code"; + toast.error("Verification failed", { + description: + error instanceof Error ? error.message : this.errorMessage, + }); + } finally { + this.verifying = false; + } + } + + async handleBackupCode() { + if (!this.verificationToken || !this.verificationCode) { + this.errorMessage = "Please enter a valid backup code"; + return; + } + + this.verifying = true; + this.errorMessage = null; + + try { + const result = await verifySessionCodeSC({ + verificationToken: this.verificationToken, + code: this.verificationCode, + }); + + if (result?.error || !result?.data?.success) { + this.errorMessage = result?.error?.message || "Invalid backup code"; + return; + } + + const redirectUrl = page.url.searchParams.get("redirect") || "/"; + window.location.href = redirectUrl; + } catch (error) { + this.errorMessage = + error instanceof Error ? error.message : "Invalid backup code"; + } finally { + this.verifying = false; + } + } + + reset() { + this.verifying = false; + this.verificationCode = ""; + this.verificationToken = null; + this.errorMessage = null; + this.startingVerification = false; + } +} + +export const twoFactorVerifyVM = new TwoFactorVerifyViewModel(); diff --git a/apps/main/src/lib/domains/security/2fa.vm.svelte.ts b/apps/main/src/lib/domains/security/2fa.vm.svelte.ts new file mode 100644 index 0000000..b4c7420 --- /dev/null +++ b/apps/main/src/lib/domains/security/2fa.vm.svelte.ts @@ -0,0 +1,234 @@ +import { + disableTwoFactorSC, + generateBackupCodesSQ, + setupTwoFactorSC, + verifyAndEnableTwoFactorSC, +} from "$lib/domains/security/twofa.remote"; +import { toast } from "svelte-sonner"; +import QRCode from "qrcode"; + +class TwoFactorViewModel { + twoFactorEnabled = $state(false); + twoFactorSetupInProgress = $state(false); + showingBackupCodes = $state(false); + qrCodeUrl = $state(null); + twoFactorSecret = $state(null); + backupCodes = $state([]); + twoFactorVerificationCode = $state(""); + isLoading = $state(false); + errorMessage = $state(null); + + async startTwoFactorSetup() { + this.isLoading = true; + this.errorMessage = null; + + try { + const result = await setupTwoFactorSC({}); + if (result?.error || !result?.data?.totpURI) { + this.errorMessage = + result?.error?.message || "Could not enable 2FA"; + toast.error(this.errorMessage, { + description: + result?.error?.description || "Please try again later", + }); + return; + } + + const qrCodeDataUrl = await QRCode.toDataURL(result.data.totpURI, { + width: 256, + margin: 2, + color: { dark: "#000000", light: "#FFFFFF" }, + }); + + this.qrCodeUrl = qrCodeDataUrl; + this.twoFactorSetupInProgress = true; + this.twoFactorSecret = result.data.secret; + this.twoFactorVerificationCode = ""; + toast("Setup enabled"); + } catch (error) { + this.errorMessage = "Could not enable 2FA"; + toast.error(this.errorMessage, { + description: + error instanceof Error ? error.message : "Please try again later", + }); + } finally { + this.isLoading = false; + } + } + + async completeTwoFactorSetup() { + if (!this.twoFactorVerificationCode) { + this.errorMessage = + "Please enter the verification code from your authenticator app."; + return; + } + + this.isLoading = true; + this.errorMessage = null; + + try { + const verifyResult = await verifyAndEnableTwoFactorSC({ + code: this.twoFactorVerificationCode, + }); + + if (verifyResult?.error) { + this.errorMessage = + verifyResult.error.message || "Invalid verification code"; + toast.error(this.errorMessage, { + description: + verifyResult.error.description || "Please try again", + }); + return; + } + + const backupCodesResult = await generateBackupCodesSQ(); + if (backupCodesResult?.error || !Array.isArray(backupCodesResult?.data)) { + toast.error("2FA enabled, but failed to generate backup codes", { + description: "You can generate them later in settings", + }); + } else { + this.backupCodes = backupCodesResult.data; + this.showingBackupCodes = true; + } + + this.twoFactorEnabled = true; + this.twoFactorSetupInProgress = false; + this.twoFactorVerificationCode = ""; + toast.success("Two-factor authentication enabled", { + description: "Your account is now more secure", + }); + } catch (error) { + this.errorMessage = "Invalid verification code"; + toast.error(this.errorMessage, { + description: + error instanceof Error ? error.message : "Please try again", + }); + } finally { + this.isLoading = false; + } + } + + async disableTwoFactor() { + this.isLoading = true; + this.errorMessage = null; + + try { + const result = await disableTwoFactorSC({ code: "" }); + if (result?.error) { + this.errorMessage = result.error.message || "Failed to disable 2FA"; + toast.error(this.errorMessage, { + description: result.error.description || "Please try again later", + }); + return; + } + + this.twoFactorEnabled = false; + this.backupCodes = []; + this.qrCodeUrl = null; + this.showingBackupCodes = false; + this.twoFactorSecret = null; + toast.success("Two-factor authentication disabled"); + } catch (error) { + this.errorMessage = "Failed to disable 2FA"; + toast.error(this.errorMessage, { + description: + error instanceof Error ? error.message : "Please try again later", + }); + } finally { + this.isLoading = false; + } + } + + async generateNewBackupCodes() { + this.isLoading = true; + this.errorMessage = null; + + try { + const result = await generateBackupCodesSQ(); + if (result?.error || !Array.isArray(result?.data)) { + this.errorMessage = + result?.error?.message || "Failed to generate new backup codes"; + toast.error(this.errorMessage, { + description: + result?.error?.description || "Please try again later", + }); + return; + } + + this.backupCodes = result.data; + this.showingBackupCodes = true; + toast.success("New backup codes generated", { + description: "Your previous backup codes are now invalid", + }); + } catch (error) { + this.errorMessage = "Failed to generate new backup codes"; + toast.error(this.errorMessage, { + description: + error instanceof Error ? error.message : "Please try again later", + }); + } finally { + this.isLoading = false; + } + } + + copyAllBackupCodes() { + const codesText = this.backupCodes.join("\n"); + navigator.clipboard.writeText(codesText); + toast.success("All backup codes copied to clipboard"); + } + + downloadBackupCodes() { + const codesText = this.backupCodes.join("\n"); + const blob = new Blob( + [ + `Two-Factor Authentication Backup Codes\n\nGenerated: ${new Date().toLocaleString()}\n\n${codesText}\n\nKeep these codes in a safe place. Each code can only be used once.`, + ], + { + type: "text/plain", + }, + ); + + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = `2fa-backup-codes-${new Date().toISOString().split("T")[0]}.txt`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + + toast.success("Backup codes downloaded"); + } + + confirmBackupCodesSaved() { + this.showingBackupCodes = false; + toast.success("Great! Your backup codes are safely stored"); + } + + cancelSetup() { + this.twoFactorSetupInProgress = false; + this.twoFactorSecret = null; + this.qrCodeUrl = null; + this.twoFactorVerificationCode = ""; + this.errorMessage = null; + this.showingBackupCodes = false; + } + + copyToClipboard(text: string) { + navigator.clipboard.writeText(text); + } + + reset() { + this.twoFactorEnabled = false; + this.twoFactorSetupInProgress = false; + this.showingBackupCodes = false; + this.qrCodeUrl = null; + this.backupCodes = []; + this.twoFactorSecret = null; + this.twoFactorVerificationCode = ""; + this.isLoading = false; + this.errorMessage = null; + } +} + +export const twofactorVM = new TwoFactorViewModel(); diff --git a/apps/main/src/lib/domains/security/auth.vm.svelte.ts b/apps/main/src/lib/domains/security/auth.vm.svelte.ts new file mode 100644 index 0000000..7c9d622 --- /dev/null +++ b/apps/main/src/lib/domains/security/auth.vm.svelte.ts @@ -0,0 +1,71 @@ +import { authClient } from "$lib/auth.client"; +import { toast } from "svelte-sonner"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import type { Err } from "@pkg/result"; + +class AuthViewModel { + loggingIn = $state(false); + + async loginWithCredentials(data: FormData): Promise { + const username = data.get("username")?.toString().trim(); + const password = data.get("password")?.toString(); + + if (!username || username.length < 3) { + toast.error("Please enter a valid username"); + return false; + } + + if (!password || password.length < 6) { + toast.error("Please enter a valid password"); + return false; + } + + this.loggingIn = true; + + const result = await ResultAsync.fromPromise( + authClient.signIn.username({ username, password }), + (error): Err => ({ + code: "NETWORK_ERROR", + message: "Failed to login", + description: "Network request failed", + detail: error instanceof Error ? error.message : String(error), + }), + ) + .andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: response.error.message ?? "Invalid credentials", + description: + response.error.statusText ?? + "Please check your username and password", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); + + const success = result.match( + () => { + toast.success("Login successful", { + description: "Redirecting...", + }); + setTimeout(() => { + window.location.href = "/"; + }, 500); + return true; + }, + (error) => { + toast.error(error.message ?? "Invalid credentials", { + description: error.description, + }); + return false; + }, + ); + + this.loggingIn = false; + return success; + } +} + +export const authVM = new AuthViewModel(); diff --git a/apps/main/src/lib/domains/security/email-login-form.svelte b/apps/main/src/lib/domains/security/email-login-form.svelte new file mode 100644 index 0000000..cdc24d4 --- /dev/null +++ b/apps/main/src/lib/domains/security/email-login-form.svelte @@ -0,0 +1,78 @@ + + +
    +
    + + +
    + +
    + + +
    + + +
    diff --git a/apps/main/src/lib/domains/security/two-fa-card.svelte b/apps/main/src/lib/domains/security/two-fa-card.svelte new file mode 100644 index 0000000..2cdd255 --- /dev/null +++ b/apps/main/src/lib/domains/security/two-fa-card.svelte @@ -0,0 +1,324 @@ + + + + +
    + + Two-Factor Authentication +
    + + Add an extra layer of security to your account by enabling two-factor + authentication. + +
    + + {#if twofactorVM.twoFactorSetupInProgress} +
    +
    +

    Setup Instructions

    +
      +
    1. + Install an authenticator app like Google Authenticator + or 2FAS on your mobile device. +
    2. +
    3. + Scan the QR code below or manually enter the secret + key into your app. +
    4. +
    5. + Enter the verification code displayed in your + authenticator app below. +
    6. +
    +
    + +
    + {#if twofactorVM.qrCodeUrl && twofactorVM.qrCodeUrl.length > 0} +
    + {#if twofactorVM.qrCodeUrl} +
    + QR Code for Two-Factor Authentication +
    + + {:else} +
    + +
    + {/if} +
    + {:else} +
    + +
    + {/if} +
    + +
    +
    + + {#snippet children({ cells })} + + {#each cells.slice(0, 3) as cell (cell)} + + {/each} + + + + {#each cells.slice(3, 6) as cell (cell)} + + {/each} + + {/snippet} + +
    +

    + Enter the 6-digit code from your authenticator app +

    +
    + +
    + + +
    +
    + {:else if !twofactorVM.twoFactorEnabled && !twofactorVM.twoFactorSetupInProgress} +
    +
    +
    + + + Two-factor authentication is currently disabled. + +
    + +
    +
    + {:else if twofactorVM.twoFactorEnabled} +
    + {#if !twofactorVM.showingBackupCodes} +
    +
    + + Two-factor authentication is enabled. +
    + +
    + + + + + + + + + Disable Two-Factor Authentication? + + This will remove the extra layer of + security from your account. You will + no longer need your authenticator app + to sign in, and all backup codes will + be invalidated. You can re-enable 2FA + at any time. + + + + Cancel + + twofactorVM.disableTwoFactor()} + > + Disable 2FA + + + + +
    +
    + {/if} + + {#if twofactorVM.showingBackupCodes && twofactorVM.backupCodes.length > 0} +
    +
    +

    Recovery Codes

    +
    + + +
    +
    + +
    +
    + {#each twofactorVM.backupCodes as code} +
    + {code} +
    + {/each} +
    +
    +

    + + Keep these codes in a safe place. Each code can + only be used once to access your account if you + lose your phone. +

    +
    +

    + ⚠️ Important: Save these codes before + continuing. You won't be able to see them + again. +

    +
    +
    +
    + + +
    + {/if} +
    + {/if} +
    +
    diff --git a/apps/main/src/lib/domains/security/twofa.remote.ts b/apps/main/src/lib/domains/security/twofa.remote.ts new file mode 100644 index 0000000..a8606d7 --- /dev/null +++ b/apps/main/src/lib/domains/security/twofa.remote.ts @@ -0,0 +1,205 @@ +import { + disable2FASchema, + enable2FACodeSchema, + startVerificationSchema, + verifyCodeSchema, +} from "@pkg/logic/domains/2fa/data"; +import { + getFlowExecCtxForRemoteFuncs, + unauthorized, +} from "$lib/core/server.utils"; +import { getTwofaController } from "@pkg/logic/domains/2fa/controller"; +import { command, getRequestEvent, query } from "$app/server"; +import { auth } from "@pkg/logic/domains/auth/config.base"; +import type { User } from "@pkg/logic/domains/user/data"; +import * as v from "valibot"; + +const tc = getTwofaController(); + +function buildIpAddress(headers: Headers) { + return ( + headers.get("x-forwarded-for") ?? headers.get("x-real-ip") ?? "unknown" + ); +} + +function buildUserAgent(headers: Headers) { + return headers.get("user-agent") ?? "unknown"; +} + +export const setupTwoFactorSC = command(v.object({}), async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.setup2FA(fctx, currentUser as User); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const verifyAndEnableTwoFactorSC = command( + enable2FACodeSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.verifyAndEnable2FA( + fctx, + currentUser as User, + payload.code, + event.request.headers, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const generateBackupCodesSQ = query(async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.generateBackupCodes(fctx, currentUser as User); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const disableTwoFactorSC = command(disable2FASchema, async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.disable(fctx, currentUser as User, payload.code); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const requiresVerificationSQ = query( + v.object({ sessionId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.requiresInitialVerification( + fctx, + currentUser as User, + input.sessionId, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const requiresSensitiveActionSQ = query(async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.requiresSensitiveActionVerification( + fctx, + currentUser as User, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const startVerificationSessionSC = command( + startVerificationSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await tc.startVerification(fctx, { + userId: payload.userId, + sessionId: payload.sessionId, + ipAddress: buildIpAddress(event.request.headers), + userAgent: buildUserAgent(event.request.headers), + }); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const verifySessionCodeSC = command( + verifyCodeSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + let currentUser = event.locals.user; + + if (!currentUser) { + const sess = await auth.api.getSession({ + headers: event.request.headers, + }); + currentUser = sess?.user as User | undefined; + } + + const res = await tc.verifyCode( + fctx, + { + verificationSessToken: payload.verificationToken, + code: payload.code, + }, + currentUser as User | undefined, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const cleanupExpiredSessionsSC = command(v.object({}), async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await tc.cleanupExpiredSessions(fctx); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); diff --git a/apps/main/src/lib/global.stores.ts b/apps/main/src/lib/global.stores.ts new file mode 100644 index 0000000..88b4e38 --- /dev/null +++ b/apps/main/src/lib/global.stores.ts @@ -0,0 +1,14 @@ +import type { Session, User } from "@pkg/logic/domains/user/data"; +import type { AppSidebarItem } from "./core/constants"; +import { writable } from "svelte/store"; +import type { Router } from "$lib/api"; +import type { hc } from "hono/client"; + +export const breadcrumbs = writable>([ + { title: "Dashboard", url: "/dashboard" }, +]); + +export const apiClient = writable>>(undefined); + +export const user = writable(undefined); +export const session = writable(undefined); diff --git a/apps/main/src/lib/hooks/is-mobile.svelte.ts b/apps/main/src/lib/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000..4829c00 --- /dev/null +++ b/apps/main/src/lib/hooks/is-mobile.svelte.ts @@ -0,0 +1,9 @@ +import { MediaQuery } from "svelte/reactivity"; + +const DEFAULT_MOBILE_BREAKPOINT = 768; + +export class IsMobile extends MediaQuery { + constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) { + super(`max-width: ${breakpoint - 1}px`); + } +} diff --git a/apps/main/src/lib/make-client.ts b/apps/main/src/lib/make-client.ts new file mode 100644 index 0000000..999475d --- /dev/null +++ b/apps/main/src/lib/make-client.ts @@ -0,0 +1,21 @@ +import type { Router } from "$lib/api"; +import { hc } from "hono/client"; + +let browserClient: ReturnType>; + +export const makeClient = (fetch: Window["fetch"]) => { + const isBrowser = typeof window !== "undefined"; + const origin = isBrowser ? window.location.origin : ""; + + if (isBrowser && browserClient) { + return browserClient; + } + + const client = hc(origin + "/api/v1", { fetch }); + + if (isBrowser) { + browserClient = client; + } + + return client; +}; diff --git a/apps/main/src/lib/utils.ts b/apps/main/src/lib/utils.ts new file mode 100644 index 0000000..55b3a91 --- /dev/null +++ b/apps/main/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/apps/main/src/routes/(main)/+layout.server.ts b/apps/main/src/routes/(main)/+layout.server.ts new file mode 100644 index 0000000..93ad849 --- /dev/null +++ b/apps/main/src/routes/(main)/+layout.server.ts @@ -0,0 +1,13 @@ +import { auth } from "@pkg/logic/domains/auth/config.base"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutServerLoad } from "./$types"; + +export const load = (async (c) => { + const sess = await auth.api.getSession({ + headers: c.request.headers, + }); + if ((!sess?.user || !sess?.session) && c.url.pathname !== "/auth/login") { + return redirect(302, "/auth/login"); + } + return { user: c.locals.user, session: c.locals.session }; +}) satisfies LayoutServerLoad; diff --git a/apps/main/src/routes/(main)/+layout.svelte b/apps/main/src/routes/(main)/+layout.svelte new file mode 100644 index 0000000..f39d355 --- /dev/null +++ b/apps/main/src/routes/(main)/+layout.svelte @@ -0,0 +1,58 @@ + + + + + +
    +
    + + + + + {#if $breadcrumbs.length > 0} + {#each $breadcrumbs as breadcrumb, i} + + {#if i < $breadcrumbs.length - 1} + + +
    +
    +
    + {@render children()} +
    +
    +
    diff --git a/apps/main/src/routes/(main)/+page.server.ts b/apps/main/src/routes/(main)/+page.server.ts new file mode 100644 index 0000000..f2058f3 --- /dev/null +++ b/apps/main/src/routes/(main)/+page.server.ts @@ -0,0 +1,6 @@ +import { redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; + +export const load = (async (c) => { + throw redirect(302, "/dashboard"); +}) satisfies PageServerLoad; diff --git a/apps/main/src/routes/(main)/+page.svelte b/apps/main/src/routes/(main)/+page.svelte new file mode 100644 index 0000000..dcb920b --- /dev/null +++ b/apps/main/src/routes/(main)/+page.svelte @@ -0,0 +1,12 @@ + diff --git a/apps/main/src/routes/(main)/account/+layout.svelte b/apps/main/src/routes/(main)/account/+layout.svelte new file mode 100644 index 0000000..fb25929 --- /dev/null +++ b/apps/main/src/routes/(main)/account/+layout.svelte @@ -0,0 +1,134 @@ + + + + + + +
    + {@render children()} +
    +
    + + +{#if isMobile} + + + + + +
    + + + {#each secondaryNavTree as each} + { + handleNavigation(each.url); + }} + > + +

    + {each.title} +

    +
    + {/each} +
    +
    +
    +
    +
    +{/if} diff --git a/apps/main/src/routes/(main)/account/+page.svelte b/apps/main/src/routes/(main)/account/+page.svelte new file mode 100644 index 0000000..698c87d --- /dev/null +++ b/apps/main/src/routes/(main)/account/+page.svelte @@ -0,0 +1,261 @@ + + +
    + + + +
    +
    +
    + + {#if user.image} + + {:else} + + {(user.name || "User") + .substring(0, 2) + .toUpperCase()} + + {/if} + + +
    +
    +

    + {user.name} +

    +

    + Member since {new Date( + user.createdAt.toString(), + ).toLocaleDateString()} +

    +
    +
    +
    + + + + Personal Information + + Update your personal information and how others see you on the + platform. + +
    + + +
    + +
    + + +
    + + +
    + + +

    + This is your public username visible to other users. +

    +
    + +
    + +
    +
    +
    + +

    + Last updated: {new Date( + user.updatedAt.toString(), + ).toLocaleString()} +

    +
    +
    + + + + + Password Settings + + Update your account password. + + + +
    +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    + +

    + Choose a strong password with at least 6 characters. +

    +
    +
    +
    diff --git a/apps/main/src/routes/(main)/dashboard/+page.svelte b/apps/main/src/routes/(main)/dashboard/+page.svelte new file mode 100644 index 0000000..37d5813 --- /dev/null +++ b/apps/main/src/routes/(main)/dashboard/+page.svelte @@ -0,0 +1,267 @@ + + + + + +
    +
    + + Devices + + {mobileVM.devicesTotal} total + +
    +
    + + +
    +
    + +
    + + { + mobileVM.devicesPage = 1; + void mobileVM.refreshDevices(); + }} + /> +
    +
    + + + {#if !mobileVM.devicesLoading && mobileVM.devices.length === 0} +
    + No devices registered yet. +
    + {:else} +
    + {#each mobileVM.devices as device (device.id)} +
    goto(`/devices/${device.id}`)} + onkeydown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + void goto(`/devices/${device.id}`); + } + }} + > +
    +
    +

    {device.name}

    +

    + {device.externalDeviceId} +

    +
    + + e.stopPropagation()} + > + + + + + + Delete device? + + + This deletes the device and all related SMS/media data. + Files in storage linked to this device are also removed. + + + + Cancel + { + e.stopPropagation(); + await mobileVM.deleteDevice(device.id); + }} + > + Delete + + + + +
    + +
    +
    +

    Manufacturer / Model

    +

    {device.manufacturer} / {device.model}

    +
    +
    +

    Android

    +

    {device.androidVersion}

    +
    +
    +

    Created

    +

    + {new Date(device.createdAt).toLocaleString()} +

    +
    +
    +

    Last Ping

    +

    + {mobileVM.formatLastPing(device.lastPingAt)} +

    +
    +
    +
    + {/each} +
    + + + {/if} +
    +
    +
    diff --git a/apps/main/src/routes/(main)/devices/+page.svelte b/apps/main/src/routes/(main)/devices/+page.svelte new file mode 100644 index 0000000..f074940 --- /dev/null +++ b/apps/main/src/routes/(main)/devices/+page.svelte @@ -0,0 +1 @@ +Show the running devices list here diff --git a/apps/main/src/routes/(main)/links/+page.svelte b/apps/main/src/routes/(main)/links/+page.svelte new file mode 100644 index 0000000..b3fe352 --- /dev/null +++ b/apps/main/src/routes/(main)/links/+page.svelte @@ -0,0 +1 @@ +everything related to links here diff --git a/apps/main/src/routes/(main)/notifications/+page.svelte b/apps/main/src/routes/(main)/notifications/+page.svelte new file mode 100644 index 0000000..07c1354 --- /dev/null +++ b/apps/main/src/routes/(main)/notifications/+page.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/apps/main/src/routes/+layout.svelte b/apps/main/src/routes/+layout.svelte new file mode 100644 index 0000000..a374c05 --- /dev/null +++ b/apps/main/src/routes/+layout.svelte @@ -0,0 +1,20 @@ + + + + {$breadcrumbs[$breadcrumbs.length - 1]?.title ?? "Dashboard"} + + + + + + + +{@render children()} diff --git a/apps/main/src/routes/api/debug/users/+server.ts b/apps/main/src/routes/api/debug/users/+server.ts new file mode 100644 index 0000000..daf04c8 --- /dev/null +++ b/apps/main/src/routes/api/debug/users/+server.ts @@ -0,0 +1,129 @@ +import { UserRoleMap } from "@pkg/logic/domains/user/data"; +import { auth } from "@pkg/logic/domains/auth/config.base"; +import { account, user } from "@pkg/db/schema/better.auth.schema"; +import type { RequestHandler } from "./$types"; +import { settings } from "@pkg/settings"; +import { logger } from "@pkg/logger"; +import { db, eq, or } from "@pkg/db"; +import { nanoid } from "nanoid"; +import * as v from "valibot"; + +function isAuthorized(authHeader?: string | null) { + if (!authHeader) return false; + const authToken = authHeader.toString().replace("Bearer ", ""); + return authToken === settings.debugKey; +} + +export const GET: RequestHandler = async ({ request }) => { + if (!isAuthorized(request.headers.get("Authorization"))) { + return new Response("Unauthorized", { status: 401 }); + } + const users = await db.query.user.findMany({}); + return new Response(JSON.stringify({ users }), { status: 200 }); +}; + +export const PUT: RequestHandler = async ({ request }) => { + if (!isAuthorized(request.headers.get("Authorization"))) { + return new Response("Unauthorized", { status: 401 }); + } + + const data = await request.json(); + + if (!data.username) { + return new Response("Invalid data", { status: 400 }); + } + + await db + .update(user) + .set({ role: UserRoleMap.admin }) + .where(eq(user.username, data.username)) + .execute(); + + return new Response("Not implemented", { status: 200 }); +}; + +export const POST: RequestHandler = async ({ request }) => { + if (!isAuthorized(request.headers.get("Authorization"))) { + return new Response("Unauthorized", { status: 401 }); + } + const data = await request.json(); + const _schema = v.object({ + username: v.string(), + password: v.pipe(v.string(), v.minLength(6)), + email: v.optional(v.string()), + usertype: v.optional(v.enum(UserRoleMap)), + }); + const res = v.safeParse(_schema, data); + if (!res.success) { + return new Response("Invalid data", { status: 400 }); + } + const resData = res.output; + const email = resData.email ?? `${resData.username}@debug.local`; + const usertype = resData.usertype ?? UserRoleMap.admin; + + const existingUser = await db.query.user + .findFirst({ + where: or(eq(user.username, resData.username), eq(user.email, email)), + columns: { id: true }, + }) + .execute(); + + if (existingUser?.id) { + return new Response("User already exists", { status: 409 }); + } + + logger.info( + `Creating debug user ${resData.username} | ${email} (${usertype})`, + ); + + const userId = nanoid(); + const accountId = nanoid(); + const hashedPassword = await auth.$context.then((ctx) => + ctx.password.hash(resData.password), + ); + + await db.transaction(async (tx) => { + await tx + .insert(user) + .values({ + id: userId, + username: resData.username, + email, + emailVerified: true, + name: resData.username, + role: usertype, + createdAt: new Date(), + updatedAt: new Date(), + }) + .execute(); + + await tx + .insert(account) + .values({ + id: accountId, + accountId: userId, + providerId: "credential", + userId, + password: hashedPassword, + createdAt: new Date(), + updatedAt: new Date(), + }) + .execute(); + }); + + logger.info("Debug user created with credential account", { + userId, + username: resData.username, + email, + }); + + return new Response( + JSON.stringify({ + id: userId, + username: resData.username, + email, + role: usertype, + }), + { status: 200 }, + ); +}; diff --git a/apps/main/src/routes/auth/2fa/+layout.server.ts b/apps/main/src/routes/auth/2fa/+layout.server.ts new file mode 100644 index 0000000..e01db46 --- /dev/null +++ b/apps/main/src/routes/auth/2fa/+layout.server.ts @@ -0,0 +1,14 @@ +import { auth } from "@pkg/logic/domains/auth/config.base"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutServerLoad } from "./$types"; + +export const load = (async ({ request }) => { + const sess = await auth.api.getSession({ + headers: request.headers, + }); + if (!sess || !sess.user || !sess.session) { + return redirect(302, "/auth/login"); + } + // sess.session.id = + return { user: sess.user as any, session: sess.session as any }; +}) satisfies LayoutServerLoad; diff --git a/apps/main/src/routes/auth/2fa/+page.svelte b/apps/main/src/routes/auth/2fa/+page.svelte new file mode 100644 index 0000000..18d6259 --- /dev/null +++ b/apps/main/src/routes/auth/2fa/+page.svelte @@ -0,0 +1,285 @@ + + +
    +
    + + {#if !mounted || twoFactorVerifyVM.startingVerification} + + +
    +
    +
    +
    + +
    +
    + +
    + + Preparing Verification + + +
    + + Setting up secure verification... +
    + + +
    +
    +
    +
    +
    +
    + {:else if twoFactorVerifyVM.verificationToken} + + +
    + + + + Two-Factor Authentication + + +
    + + Enter the 6-digit code from your authenticator app to + continue + +
    + + + +
    +
    + + {#snippet children({ cells })} + + {#each cells.slice(0, 3) as cell (cell)} + + {/each} + + + + {#each cells.slice(3, 6) as cell (cell)} + + {/each} + + {/snippet} + +
    + + {#if twoFactorVerifyVM.errorMessage} +
    + + {twoFactorVerifyVM.errorMessage} +
    + {/if} + + +
    + + +
    +
    + +

    OR

    + +
    + + +
    + + +
    +
    +

    + Having trouble? +

    +
    + + +
    +
    +
    + + +
    +
    + + + This verification expires in 10 minutes for your + security + +
    +
    +
    + {:else} + + +
    +
    +
    +
    + +
    +
    + +
    + + Verification Setup Failed + + +

    + {twoFactorVerifyVM.errorMessage || + "We couldn't set up your verification session. Please try again."} +

    + +
    + + + +
    +
    +
    +
    + {/if} +
    +
    +
    diff --git a/apps/main/src/routes/auth/login/+page.server.ts b/apps/main/src/routes/auth/login/+page.server.ts new file mode 100644 index 0000000..10f5322 --- /dev/null +++ b/apps/main/src/routes/auth/login/+page.server.ts @@ -0,0 +1,13 @@ +import { auth } from "@pkg/logic/domains/auth/config.base"; +import type { PageServerLoad } from "./$types"; +import { redirect } from "@sveltejs/kit"; + +export const load = (async (c) => { + const sess = await auth.api.getSession({ + headers: c.request.headers, + }); + + if (!!sess && !!sess.user && !!sess.session) { + return redirect(302, "/"); + } +}) satisfies PageServerLoad; diff --git a/apps/main/src/routes/auth/login/+page.svelte b/apps/main/src/routes/auth/login/+page.svelte new file mode 100644 index 0000000..7cffc76 --- /dev/null +++ b/apps/main/src/routes/auth/login/+page.svelte @@ -0,0 +1,71 @@ + + +
    + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    + +
    +
    + +
    + + + Welcome Back + + + + Sign in with your username and password + +
    +
    +
    + + + + + + +

    + By signing in, you agree to our + + and + +

    +
    +
    +
    +
    diff --git a/apps/main/src/routes/layout.css b/apps/main/src/routes/layout.css new file mode 100644 index 0000000..5860334 --- /dev/null +++ b/apps/main/src/routes/layout.css @@ -0,0 +1,196 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@plugin "@tailwindcss/forms"; +@plugin "@tailwindcss/typography"; + +@font-face { + font-family: "Manrope"; + src: url("/fonts/manrope-variable.ttf") format("truetype"); +} + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(0.994 0 0); + --foreground: oklch(0 0 0); + + --card: oklch(0.994 0 0); + --card-foreground: oklch(0 0 0); + + --popover: oklch(0.991 0 0); + --popover-foreground: oklch(0 0 0); + + /* --- main theme: lavender/royal purple --- */ + --primary: oklch(0.6 0.2 280); /* medium lavender purple */ + --primary-foreground: oklch(0.99 0 0); + + --secondary: oklch(0.93 0.05 285); /* soft pale lavender */ + --secondary-foreground: oklch(0.25 0.03 285); + + --muted: oklch(0.96 0.01 275); + --muted-foreground: oklch(0.4 0.01 278); + + --accent: oklch(0.86 0.08 275); /* lavender accent */ + --accent-foreground: oklch(0.5 0.15 280); + + --destructive: oklch(0.63 0.18 25); + --destructive-foreground: oklch(1 0 0); + + --border: oklch(0.92 0.02 284); + --input: oklch(0.94 0 0); + --ring: oklch(0.6 0.2 280); + + /* charts — more variety but still within lavender spectrum */ + --chart-1: oklch(0.7 0.16 275); + --chart-2: oklch(0.6 0.2 280); + --chart-3: oklch(0.72 0.18 295); /* slightly more magenta */ + --chart-4: oklch(0.65 0.15 265); /* slightly bluer lavender */ + --chart-5: oklch(0.76 0.1 285); + + --sidebar: oklch(0.97 0.01 280); + --sidebar-foreground: oklch(0 0 0); + --sidebar-primary: oklch(0.6 0.2 280); + --sidebar-primary-foreground: oklch(1 0 0); + --sidebar-accent: oklch(0.92 0.02 284); + --sidebar-accent-foreground: oklch(0.2 0.02 280); + --sidebar-border: oklch(0.92 0.02 284); + --sidebar-ring: oklch(0.6 0.2 280); + + --font-sans: Plus Jakarta Sans, sans-serif; + --font-serif: Lora, serif; + --font-mono: IBM Plex Mono, monospace; + + --radius: 0.69rem; + + --shadow-2xs: 0px 2px 3px 0px hsl(0 0% 0% / 0.08); + --shadow-xs: 0px 2px 3px 0px hsl(0 0% 0% / 0.08); + --shadow-sm: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 1px 2px -1px hsl(0 0% 0% / 0.16); + --shadow: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 1px 2px -1px hsl(0 0% 0% / 0.16); + --shadow-md: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 2px 4px -1px hsl(0 0% 0% / 0.16); + --shadow-lg: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 4px 6px -1px hsl(0 0% 0% / 0.16); + --shadow-xl: + 0px 2px 3px 0px hsl(0 0% 0% / 0.16), + 0px 8px 10px -1px hsl(0 0% 0% / 0.16); + --shadow-2xl: 0px 2px 3px 0px hsl(0 0% 0% / 0.4); + + --tracking-normal: -0.025em; + --spacing: 0.27rem; +} + +.dark { + --background: oklch(0.23 0.01 278); + --foreground: oklch(0.95 0 0); + + --card: oklch(0.25 0.015 278); + --card-foreground: oklch(0.95 0 0); + + --popover: oklch(0.25 0.015 278); + --popover-foreground: oklch(0.95 0 0); + + --primary: oklch(0.56 0.17 280); + --primary-foreground: oklch(0.97 0 0); + + --secondary: oklch(0.35 0.03 280); + --secondary-foreground: oklch(0.92 0 0); + + --muted: oklch(0.33 0.02 280); + --muted-foreground: oklch(0.7 0.01 280); + + --accent: oklch(0.44 0.1 278); + --accent-foreground: oklch(0.88 0.09 280); + + --destructive: oklch(0.7 0.17 25); + --destructive-foreground: oklch(1 0 0); + + --border: oklch(0.34 0.02 278); + --input: oklch(0.34 0.02 278); + --ring: oklch(0.65 0.22 280); + --ring: oklch(0.56 0.17 280); + + --chart-1: oklch(0.68 0.15 275); + --chart-2: oklch(0.62 0.2 280); + --chart-3: oklch(0.7 0.14 292); + --chart-4: oklch(0.65 0.16 265); + --chart-5: oklch(0.72 0.1 285); + + --sidebar: oklch(0.2 0.01 278); + --sidebar-foreground: oklch(0.95 0 0); + --sidebar-primary: oklch(0.56 0.17 280); + --sidebar-primary-foreground: oklch(0.97 0 0); + --sidebar-accent: oklch(0.35 0.03 280); + --sidebar-accent-foreground: oklch(0.65 0.22 280); + --sidebar-border: oklch(0.34 0.02 278); + --sidebar-ring: oklch(0.65 0.22 280); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --font-serif: var(--font-serif); + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --shadow-2xs: var(--shadow-2xs); + --shadow-xs: var(--shadow-xs); + --shadow-sm: var(--shadow-sm); + --shadow: var(--shadow); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + --shadow-xl: var(--shadow-xl); + --shadow-2xl: var(--shadow-2xl); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + font-family: "Manrope", sans-serif; + letter-spacing: var(--tracking-normal); + } +} diff --git a/apps/main/static/favicon.png b/apps/main/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..822f477c02f9aa90349102051c37fe8b8e00abd9 GIT binary patch literal 303316 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񚑿`p-7SC7lL;C|q}Pt|51_`eVh zlUa0{j*qET!2EAU3kb6`|@c(0+`tA`fv)wuIvJV@!<3V}d5`nh}hn z-by5y*n}lQp=gi4tbZ2guA#ebFLv)keBf$5{VI@9$GRtPeGl6`l4~Urvz@iPvp%ic z{n7fn9;9};3#B9cIA@%H>4|IVZF)N1F z+*?dDO%GFb>SLn0lAG((uqhF2_S%Z|$(fLf0vo(F*w|Rk!95v6ns}Dzb4wnD#9udw zOceU({bdLNfe06J*2D^v-$1D98C(a`frc`^A$sp|BcTGx!d(ai-}%m0*xTRrO4MPk zAJ%&nppVY4q(+0q8sKi5IN#RpeeXFYaq|^2;#IQThjo9?d!D#Fa=?()E_&s@2YBL> z1_i|eq~n$T+;)x$|M& zxB14AP2;@w<-U`PBO-R_|9*R$IKEo`4}h#%0__32Op;GBX$<;gWd*}JaZ!+7qYxru z)Jj-qt4wVOpTppnW=l*!d{T8T&R+0srpmqP-1Hs@D%bm2q%krc)Jv!Ci_An16BQB-V8eovG_`~X1F6T7j zW#%CDaKjjd2OY6wjzRz8Lft2TcR2Wr@jzl$Bo2Em7Z(y`V_I1$?ZUsc)%93y+`1QINZ2Wd7(6K4HC7VYV(` zY$HwJ55D5sENfTZcl`sY7Jy#cy?^~SWYzOic5U~#-c{zc?#tgl_rt%}9z1x=TDQHV zEx*Mo{_ko~X`?+@!qR)}Xt%xeJtpso0p@GnC5{hI2@_fK1c_D2;ZDB`Ss`$_887XK z!Pha6VX{1bQ^UiDzjal&tM2ujUR%eD!W`Vwlet-DwtelqEjDPj+Tpx!%7SlW?)%yG zb-ClZkNohct!uuq-hbpvM|yp9|F^3$9F5&w{-5`tOh&Y=Oq^vYejDMiiK(zl2cyIO z=+oQ*LLwwtqY}20RpkS`mive>qn2){sMSD(;(T;{Bp}&X-CFf`RqST3A~8{S(vFny zRj+qgF_UF0;}RSc8{Er)fJqH%AavDhpfTg{g@TMzp_nN4#<(z9Ppz>bIU3W?T24Yk z7$(s+GIG2Juh5W6SZ${Ah4j%Aut!v*X;fmM!6N$s@HM289ckzSj4(cB9&PmlQzyZW-Sskn+DM<* zohe;pwgT>P^YIUV->Ytx1|PGdJtKNt4=Vd|NAK5j*Y=~_bzH0ew)bn@+O`_<(XsB$ zLS2{T)bITxPoNUB@~3*rzXE@mf12pNg&HrLGZfs@a+)#2J#M$e$;7e146_~41c(w_ zqdV6~1ZU_pd>|^gPDP?!xo+eBuEMdN-~=Hf9zOg{yLPN&b01q+%dF*(Z1pzVzN;WGkiC?JNsRVwzavhUMh|g!w zB|_OV0A3NQ0WV4CzLM_r03t5?LQCcf;m)A3e+#qEXEzaE7;D3h;_%7b)>h?Ui!z9>j(!l8)8=0U+k; zS81G2^wzys;4Wc3g#r03B9MgAV4U8w;GnLJOL33~KLg`tJ0es1lTj>A8G(ISg9uyK zAJ|psLxrIKqf?c74a6O`%{B8LW|;(1kJJksxJ*QPLWR)D8i*MLOaCS$vz)i&Q}uv| zK{If{#l*M)`!b|w(E)DCzCHQm*VyHe3}$vGaDO#6!Afn9^uWYNIo3LkFel4OH6S!coJ+mhIuFe5z!=HQZx9om;-Yfcr+TK^!X6=w@>-k3^ zR{J{U`Ssj=j%&7#YxgBa_pN(hiQV(K?*9)T{stf)M2109!am#M)(N2g&Y_)Um1CH! z{HV!q#pX^5yJXKYwqR0h>OK}NSCkKxpVd-%UXu7DVuq1tSWSScj7rW~TK!Z`$yQTo z1Yty=7XIANPl^z2?19e>)ug5hT#P><(`ezmfIwfsgcgwDF{Icn*(ym3S^JW;g;pIz)#ogQ>Bwe)fj!N3FVOt=_{t z+IGfoY>qAs7Mbxu>lGAgsnWx3@QvJ5Pcft}lil6BrAEor;6mVmbG%lvhvaXkZe8lD z9(SnPOFlOR7G7NS|K_y`qYX+E_N=C*xrp9ffw)3 zj9F9@0lUm)SNo^xkqi^uZFlZin1dti)}WxX7>q~AJ&NJ?4K8~^)pf#2kIG-mc&v4H zfSU4N{vJGdk^QkBeA?n*l7LCvW?VE-+B{i}m2VYX#H=CAbZbR9778FsEl5wR?&Xid z0APo;|I2ihen~j6ev}(*3Ls_Jlerpn;cN?>jbG~d^e;SrIHr3$yW93|vyGVs6irV z$QKEWG2F)kHK&Yj40?OwZJo_YN}MOMHfkxaT=>Aj&Nk&*{&9^9K0O~8^LYtcS@jV8rLGM z)5MY)%m63EDnur;cIHaoax#8SoqI3C%{UXRD^#CtJ^F1~ngwV79GBW|z!+5yc#gK? z%_x!4gN=8FtH%UkoLSS&zAj5#-}%m$o6f$EX5vVPUjy9HcUK~^uSr$(q(Mt&uiUUR%O+G9oZ=~gnL%`}ebZhJKLa)#~w|JVnQpz5Jg#$DT$A#dH-wk2CP4fhnsDEVo#U3JMEw*T{Q$>mcMyZyQpSK06&4-JIDnzO`m3dtjpJ z&rK-CEGhge;u>j6unr=eP-eVDDYZuxeSb5*X+vk&-I z0}_#L$U?#pQfJ6WE(Eq05E8Vh2u!Hs*dFPa>FeW4+cLbe9rE&5c*|6>bp$}&_$eB- zJDBR_p`wm#o>kV@NbCY29@K4DQQ*|Uj!fO@t}}SFze^9@x=lkH8P`&7v{NeY8Ci{J z44}%tf9ikqJ8jkPd>>^$*-ZuS6^M_(R$s53Gn+uZw!fB19r%y~dsvQT2?DKqeO<;(edI`nYj%1~{-ZkP-l%=qwJ&;Z_Q|g5X_0!!cDMiE{d(=(X!Yj& zPyXG{jK?kXtRU2#tm9Y;cDyudS327h1zTbT%+M>8APIB@9!oG1EyTC&-_sDd`#377 ziA!m?i5BV~bS44jwCh*@qWkP*gtikV!sA0~v4Thugpll3r@GDTK*`=clMdn{I&=0; z%^_>hj50@{_=<6LPQ;|bI16kW#F-ic7s3%La`7`jG%k7*E>zQ&RcftZ>V~h0SK!~A z<7ADFl+lVkA0E+taxQIfTm8oGM(F| zZ4%+mCRML|#dSZ0kFe51QG+TGE?S`cb!U&Rj4fh9}enStQAj^ldmUPly5ScBzJ zfYm@_2YuMrZP(kphP!%N-Y z_XT~ZB1Q!J?-HnNd`+tB?uL7uh3o}PDV8$rmtbb;m}h|4sP{uZ`33vRj@#|*w&Sky zM>cdjX|YF=<;YG~J6_)(-Lu|*Uw_N<`}^-Y)bHx9-MwEe|NrPmeuZDHZC-%w4sC|s zD-UV=NBwQu({W3nlpES>f{^E|zM#U%Mz%4kLGnOCr9&2t{|9XvCi3O7>i)_r9vU z9f>l|Qa3ql41_3z25^c-PLV5iLJN3Vr$idDX;dYs^nmNe7K7blDFGRpaFZvHa`}m= zP>`|)R-7wBvnaF;NrRqKL6_s~%UBFzWJ~I|zr}$ZZ}lwN(J5mywHiOMb+gI0J5;#f zW>rGcxv}e>*`)Kj!w^R85dkuZ^7GJat?6JvARh*hKEbm3p2KTFGBx*kl@6<(Gi-1r z@Erbcc=Jo^o;a%G)D?!@Irv97jRxNS0pKxL?rGdoRc3 zclItzg{^XAv^Shf+pe1evXJSc!u-f>0VqHGzu<&uQHNImI+wTRNiee9Luh|e>?KbjoANx%^jjLmQt!*U) zgz*S@e_Y3HeLXsE)6aVCKK`rcjxxD--Jds(QoDP<8vg&$k9@(NefCQYdKn2^`2&3G z9Kr|eXbj^({MeO546t4Vt9IO&g)Q>L`0L;b;q@SL0g~t#T_>#bGeYyYMXJXn%A*4r z657|MRwR>7;1lvoIN%uAES-u|w%1yqLOKxEeiT$z0mfA2CQ(R9Uy9bu(^#m>Xz;S| zMgS->M<&i)t^~rcQE$1zkvx$y=N=hzQN{djdY7+OC6oaW7hRlqLW zmqcz$SrC*1vPUooUyeWe1Fsq8cXS*Dj%(LIs_}Oh(Cg=;CuXmI>$Pibto7ej2IW?H z=JkC*IjZ+t-u4PYsKr9m%^JCDEC3QTOTyc^L5-6?Bk<(e=7e>BP8O6L88FS%2Wk7& zVBwJ;4jF$9vmGEd_p`GVm+~Ci zJR&b^9ZkTe4WJ$^vmP#G*K(IVT`DK)(=yj&Ic&?duH*7+fA7lDe)au-`V;*B{qOrF zV|2-}p*fkddeb=PqJP)x24k!m8K;M6z)HyDa9oKK#aqthOhE#}?sWaZ9@77tL>MRY z6t6;hP<{lTyb5xcWGGLcx(9ByF8u|9DcdnKK2(dsQHiymq?frI1uD8`cBMN@Ket{( z#oz=Zj|quVTdpAs6O3{*KG(reF@kf>;6NUAK$!6|QHMj3b(=+67>f_*$Yz^3@4m*nhczih>S$B=UY-Ts?p$c2F znU1ebvh8Z{?6e({lqNTMFJ<;l9uZ_hLPg+-=uN!H%X^7*piFO3p=-swe-h6;D%huw z5(u)3p&k?_KvL~N?a)KLf5V$zYEM4>V#+ZAZ~tD;b)SM|W&n&`J$euHybnxkIbA*- zdK_)HtTx)yK&9u_zqLM$t??5nh+0BNmumcJmFZx`G)r$*@^<&sNIfLAP*sY`x~(I7*E(ZzNV8o%+Rh|F0Hr`$zk9plXN$x7)V8kK@M_0v-&P&!IVG)o`OVhx zzvfe4egF5}xIX*L7wyMy2p0XL-319z+FKcM1G5mMs8g`nnq5|O)pV?Z2NboaSX05y zGyv4(-`RTam5TDU#_z0t`hVFAx>R%Nb`Hd|6yAmB7_-?<@lF{Or%^}t+jBse1)%3; zZ%)xe8rGe{b2w|d$$T{Q-q$2kKi3fh7O~u5Lx( zBQCNT03D8HV~KZkyLvr4xZUf`Y&KEP-j3@M97&{jGX@WBrc%ZRa*Um~hAecf%)%Ft zboS6h(QVIz1hr$M-xZYR@}AB2Jz6ER9q6qs!&~e9xCaTeK_{=abHFe*%w|bnzw=$M z+y%51;Me!-x#OjkFr8*zt7onAuC~|vOmuTwke?6N+^Y^Pe9YWWuzK==5JZ`(TwsCFWleT~Dn%;jOx$ctX zKKCBk@p}E*ed`{rqjPH=|84mH```DB!EQP}wD}?qT|OMN?6Qa2^vg#U?JSAYFk+9H zlh~T?r!zB-&>fs1)p{br_0Y?y3HKPoxY(gD`q!m3tr21-B#=pO~rSdXF|b}o$3ukW=*Mhmk-c{ zD0!#uO_(!1Y>u$1FU6l_LqH5&J}YH2n`2xCBf-{f%%qzXgo!O=;p7*FN$~s%Y!jSy zpH^5K>6+w9#*gg~oU1_g5k|&k>u=Ik8cG9G>BxI=tpkbmvLj2PeQ@4m7v54oiw*!Vn z5le7ckMk{Lyt~|^1eR;~ z(h&_H*!PwFg!zt9#8NMU zE!2SZ*mid7+&TO2L*XE#z8Egjo`%tH84Sc_x6b97wl`)&V(7}tYT9Sd#@OuzQr0Xv zK?!xWfN0Z5v>Ln{@JusGmQ25AyU@t&H(TcVGnbJwdNcrVYJXlWB!tgFRN3ekCeIM- zn<|~w^MWW2im__#b{2O%n#}e*L5O(J&Lr4{>BHz^=q|_agk_x2`f*(PfA%-L>GADn zajNb;F1cL+YWClbjt)k5mst<@>mZN!FXXB3j}kc6`_$FWOR!hHt$ls+sjshZc4E4BnnKQ!PENzmaBMTKxbpHxX z&`k6Vtjl|A7h=5i(qpKytfFDn5jE;{PZ;twWRGAkWh7b^Pk}%h$263?QPFNY584>D z_ZSr{Gvx|C=)H_-Ix8Al8=sl|qcN7Qx7QQCt{1!Iwou1#nJY(N?6TfuFha(hz!yW7 z!ReVXnVZ_%-Yh~lyo)83_k&C}p^^vE^*6Dg+p^9~5G?zwqMdMNF`P4`kgpKeGGD~B z?~*N|{JPr+m=7%GJKpi~3W(kB5y0e7@Hhfq1fRQ?6pU@I^+&Az==W&%sQp+X$STKC z+dW3tWmH*KS^HZ4t#u(_z2Qv{JlecsPATUyU=X~ciCAbeu{e8Ig8_D&P-W`O1$7Pm zGub2?wXj!5GHYhnSHNstJ2~yep^{vd()eE zAu{YajIp)tS}XGUw&1W&7rQsEM>Y?`sfwG>Vc)}`kg+O^OMzz7|4qVn{eOz{M5tKa zBTkt!1C$DoAmQU?MV6otUB>y9|0t$9p;Cg^JkJlnuk0{_`iRZ4eBV5@%UZn6Q2t$eYHxh=ONad3 z4+YA&a&4X?4)qk#u5SppTifdUVkcX_PB3rC+1H|CBqd1W%@F(I+C zu~Ey2G$Pr+TRn2?`L_qvAc?g&iUY6}i{~Dm<6r-m|H1a-zN_8n7_53{eTyyZ1AXjU z=h&GY5rZx}3D8O|_V*miy|w{cKdS4RzZ|``-`IFv+h1C-YkS*nBT7ft_T%#C{{N{@ zKD58~Bfn@So01B{$31q?pT#4ie_5l$5MIW9!I^!5^C>Y1n;Xfb4QKopBE{4b;&!>T z_tOtbJ*Y3xO!_~b_2tUOmcG`6W-EJ==O_i9v|b|g)Y>Zcx^jk`387O84oaq?PMs69 z0w*h(cmk zAH0^CFA0q3Jj+7`9Ig_wdZ9h4aW#xTbpX5khBrO1C!Tyv`j0;DLHDlX+C4}2?}^B9 zfa&-1ToWeWm$l>et_8n&?zm2s@mfH-UT56s_{Enjmi41lqP8MA)+kI7-3paipQl4v zk>(H^bND|1TSz@Bf>s*8YHI-fKa`!(?s3DpAV`22aiM*nZZ#sQWZ()L!N2;;Hn>hg zXwN+J8}aA=mH*hDyCGRa`+nU`{)1C->RUcipzGox82hYs5)q zIx2hH@s;}jv(J3V{@kDaIZInO_wIHgVcKz(?@(0+<~`Y<{ilBpiPDNAhqbo@+#j|#Vji*k4_LIp%|`RyY{IF26bFl zYCJ0bbImE(FECR#aRMPXa>TgTYbGJ%>ei1w2)8RU77*wj(+2q2KFD2h+bPd{h$+kH zKC>=7vl~oPi&!9K=iXW`wvPJoO~cZwDY*(rVjUMLY$J{UTN5<1^@$Lu!i)Ikw==)< zW_j?SUX07FhnCgX{q>`JW4U&GFOCAr<=owY33c5TL>KaWOP$C4>^7>dEB*eKx4qK( zht|~&3~#Rt`@aE? zQ|c~)Xf^h7^gWm3q-ldL5lRTNkCz{T{tGuG>-mSjB|6@Z-B$l0@v=J(zgoV0@LtEv zl!^`9)w=IV#_1{Qpy*{5AV?f950h+`|j0?g^+_F!MUcBzhWM zz_z{je$R4co*mE9jp_%Xx2__7l>2Nm*}c#;d^ymD z%D1*N?j7gWcBAWZ+uv|=y#1Z8H2MII7s81CfDNg@fn8~Mja?>NAgm^0L7_3pliJJQ zIx|*9km&?&$RLGcRqG@Y;WeuPSZjQ1ieR6yaJTIzui!x`UvDA zVgBA&_5%@Yq(w>MoNLVn$Ly3-H6=K2hyiT-Fej@mUSN^sAF1nnL*A>d`=B^g&$j&5e>?C_VH;}5*`aeM0N7w@t*N<2Ck zHDs*;YyUo^oCcE~lQkfaz%Ri^_Tw2c3o4WDpV7LO45O{VU~LEG_M}VEsPAnZ#}dT$ z_2Tcr<1ex|zWHVFhn6^coWP$zYB1Eqp`3;zpcL+OMQ|JI8M0oE))9bq`lczlt4_Im zaj1N^0?Fm1{A5|y%)M8vqWeZ)Kf+530q%^e{+VaLZ2$UAChG$~{yCd29z(as<0++w zJ?-s)_1WGIueGz<{tPH)tWCEL;X~(DXk*f73yr<3{-Xcx>;HwnP|`Xl)1oZT z1EPkL6{$wRB`UysI=Q1!%)%gu8L(o8Jp+hAaVN=17NHa6@XlxilMuEkJ23NV8PE)B zaNQ0Q*4cFoR95U`>Cm*D24%Ol(=3bGpga!0Mx6uKUvVoE*%st3xs$vtAT)^$W}hWn zXtt&0+PuhwfaszZpSLgoc6^CeKichr`JO!3 zYz||#uOvxjbX&}-?amL%vzrJgaA?%efIU%QlHk%WR#Cc}5IMvjkTw!-nnI;(rc%%);q@HvAv#OD3W2j@=9v zm`KsmRWA5H{3X56#iiom%`)MKf9mu8*=K$uUibQ!`h}?8=eTX$o0xxf?e@OMHNScx z_1xz^w-?sW-!A|E*hhcG{^FnesQm{&`*|^JLS^hL6nrbd%NW9t8wiJUvSk%lxaA4e z2riR5bX=*4jF#;r1e;3oX2$s6vD{;pn?7arh!Z{rsN;_BZ$W6{7tk61$w$8TUH=K) z9-R$3UwM^eIuLAIxy?>Z&wDWP7Z{Xx29$*VIb~fxsIKs^!=+e|x?v~QIx3x*2}sO{gzg+=CPt4^1+#T9 z=O%!*x3gIu`fUDgqkXs<2%^eE8J2EznG(i3<%hHX_W$$s_Ta(E9t9is1Y*_S{SUau z(fP}dz1~Jtu2*U_vAf95qt`z3R5-VzghA{z4kq+T~rMq0nA!-)_61%XUD@+#nn)FCavVa)~UOqk7nn+*< zkfUDTU!A1P4Gv_;Ad-;OxRV8J#!qa;O96ZC8s; z*uigYCNSgPs<|J1V%FvfxOL)#Avi^x>5>Iqn9NqHyZn^+#~>F6`W+ZC{J;8{pduJm zQWNRIt+iXc>*^QTuCv|+mRgCR~b(Xu2?H~EdeQZ&$ zclkKHf9>-s_mLkR>tU9AA^!idkNnC;sQ#V5^$&X~&=bzAk_Ijbj((UkrSQf~t35RC ze16)v7r^f(#7gR`a@La-#BehuS!TAw|B*(j0&o_Sm7~xA)S*w^&X@J^Y3Dwf7aBuS z#2Oo_fyWvvB$YqpSDotYpT^?n3Mwv&QXx9~VqjHdx5`+Zb^1DpDRQRoz=EosNMd58 zD6e~BAxcr|Vf?RuWky#n;ee_tFc>b74pmwarV%FHWKbx#(FQ~ENS&4tPRDnwJtwz^_;rAjX|13ylHc);Qf~fLO~88NBs=G-Fn<*L$@5 z+*^YBYkiF5C4NOlU;6H&K9o5QnqlAkwwK=!tmn#lIakwoiLx#;8mo18l_LZr z%Kzc!#_79N{n5nUxe_TXfh!9(_ON(AV^{4sOc>VXn?~Edfb%)Hj$yQ6^Jac^+XzyZ zN6pmpbuJU6Q#ROsic%$ieg61IpNmg?^tr9-3)#A4w%+<}ui8lL>bt3feO3n|1mU7r zr_@%GT%AQLtF7euBiq!rU09C#&T50J58U1t_0j#IZDNddQm~Z1#s7_d)pZ~H|1-~g z$v*gF&)ECl_iQ}-?3Y|^b^1SFL+Th_Q+vCdal_p-ZW($;8>`8L5Oi<90PJ_$H95e& zSTIpJYQ$nB(~MaXpW&ZDy~4=4nbr!3Q?(pNif6u%A;}||=HUhK^ zBzzf;#q~rf*34pMl_5%_Y{hJs*-*>TN>U3DhHFzf0uq|OPCix?6?kAAP%nAhO$PH^ z%WX#RpqZ*6kp0l4N(7M(d7sKO0k}&K+dUu?B3ac{5>cNaQ_r3~xKNK6vM5*>+SF_k z#v-jrvXl`tUXsGOtWXYfwx0E)%cG4w!W1tym7(RUV89vsbbc@Ii2Th#i=F)(9aASJ zvoEtbOPWiWF5>~&du-Yyw+k&#lVm|(iSE$37}JBtPxf#Bm9Mi$Io2hxmT!Ok+Pz2j z-KW0$)P3~+sIB{6yLSCiWVlxD^5}to_J8~jma$p=zw`wwBI;lBf7;4+5&nNd#;XcB zFeH5Of6gZ{xpy1zl@Li^n*<{en1RXDz8l-U1z-J$z9D|tss7sMYT_dkBl>vjx4z20 zcsma`7WgD12?4V<2Tu= zOV$b_TygK<2;5vB(-xT^u)W>W-a~@AV!CykxIdeyjI8fsCjNEshSA0W-osTfY4Hpa z#;jDuYWguqt9O6$TF&Up%~J*qLf*uI6aUtDafcLk+k+J)Tc@fi(2h6-l@#2MLon*d zzdAajzw$8~9jze6Sz0G{(6o~=7~|e(HCC?JsdSM<6w(2F$T@CN$4wQIJcbsfqH?rKW#(fZfIB>RDni5MK$zN{n@6g;=$YOWd=#Zrb7b zY%$5C4DhNv$x@{Z9myiJ{pvIbwC{idgw!VEoF+ViW{ac!vm0%xdfGl3SvahMqt_ooEK;0%y*b15WQ{GrGB2LZCO{B!9g*?m4#9K z#>wI9c65$S1mp_(*X7I!?JeCRk!U)f8LJj$#7H;ATVx&lOKx zuoB3*$gWtLWt!kv4CY`q@OVyw$5pu(6YhmO;6&Hg$%W=QS|49fPh-8s9Si{RAkyC- zt7<&ht;`I-vD<@7p{y$V)txP(B)bXxz-~H`WKe)?mfpN_Yu}uEUdTLyA_9X92|XbX zu{|OpLn(r_Do3&gl4WqgNh5fO+)i>Q41IQz&xtZOS+8c_ge9l!LMYsHyn}-UPEdwD z)@x}6fHY`_U0E%-h0z>O__UZ3YOh zKBQV9{R&M5WQ$`=zoYIg)IOlEC5UjsQ&MoM7S?Wi2N`LDO(fa0_ziFSb*vkvIdn)*#u6b|c`F+UD=A%1V=A?|(E@jsD zzLa}Z|0k*Xsb9Wis(Mv}aS3)z9IE6dv2Uw>ey+4>F$7;rvL68L?w7kuG(p$Kc9IGj z6=%}7>N*KJm-|e*U?BRn1Ed;c{PA|B@>wPQD%fbB%HOi;@>ACGbFMd*bL2PFb zUqW4Ap{9BzX+{b!X-%}rbzN0V+d$3L!pH+E)MwW-@Y`xTUQc2!&>=agmdh?=`H8a! zmq$3Xer!jU=VzHvTdg7FxB&uvn)apf+Fa{42}br#{5uqwFp;vSg9h!mog&WQthc7ri*g%)zUy$+g>5;9)zygH%qy3-Fn>jbW35AGlEsHI_P4 zUpeF2c2C$#Zt?qcwYEB@BhNK z^m7}*(tF2*4fY$lT%z(H*2H0ezV2?f>s+s0&V9!_UTJ^xuRnWvYy-(pnNHbOQSVTM zDFvKs)SU^VNlvzg3d}ECnow_)=eiT?yZ~4Yed-+EWkf*W$#&7gcgos<=djU^e};N` z@>>CrZ6KJenKW+%7VB`ZOCvT_ta|?8Z`>?jeKv0X>}4-|%r8&XfBUc=jbs$brg z0HwVmQX{k!wMwEcm`a>C3*ks%%|cf5;k+BtZE*~J3IV{jhOMNNw`FIzd2f5VjjnHJ z9!PC@8AyYhAy#Wd>kyAvSoJaAf}Su0C$S?f>p_OR?n2rO0jFYdoH6-IeO@j-27xO4 zi*%Q^&T3f!<)63SL?wIAP+a$x=mX-zpkzcI23Pnw_tY?BWs=?DXv)Zy&z089#=se7 zd!4-kDWIv)=u3As9Mh0Q+2d^WAf3Ml*xD4p%=2@=UIETkd8bc89+v=YPWuid%Q{ij zOlBB0j99^!A;4L%^n>w{ezifhU0_wfk@AoBE}7lX-dcsAL?v!6BQ_yI8|~CFomsMf zp(EzqmJBCmY7JuK>{5TYbGdyv3S$^#G-6bTm=Wul*wEnw=H8e01RYCqDkg%d?UEkN-1IUadpV;okLnldRe9 zx+JNA^R{fm@zTov-pk8{AinN(zr#L$$zp9M3)yr+VX?#=`caQzx}nUBP1;Mt$@y_j zu2aSx?;{?DNg%KS@wFrT2iPPw%EFw<4;#k%070TrPQE=@b)I*Vt!%lI(+IkS@$M&I zJ`M%{jPZ27xfL z?^49$g-Lak#<)<8KJ^}g2BsHC(d}rz%D${1#x4E&&Ud~lF5e~uoY-JWg_oFwri}(MH1FA7 zVs~rZFIFDRbcx5JY>_K^maC5u#0di?S*ZZx(Qh)~#@>evhS)3vd&W>t4E#bL13uwP z--wvCxd&qttr`;y&qvpsh)tKPpLyoj!vcIV_+XUY@$`pivs#{(a256HCh276wBHwSH7YfGSDM zM|O$;o@uFN4VT0a>T&M-@>ZD%X>cJ0DQ{pcvPs1@#RC9(IV{#dE4+0|F9yvQ9Sj18 z%#98Mt<(CNwOmSaf?t+%7@N1D5#+ekoCaIEYgp~J)s8s(g9A~s2EA2Z&4g5Bd8@@) zsh@V*on47d%it1P;GC-ixw87M)rDnNNevviZc=pWF`iT$x3hVfp4?nzF;>pKNTzmG^ePzBX@4>Yxry?dx+U;%R{onl7mv2ucfByNi zC46}qUfV$0(Al_81pdvTMRQ8ZTZG`!n_!n&dvLzVD3P;tcvNF?L<1!PK+UP6iCy90 zE}NG1aNLy*hPrAXDi4O2n{z_xx_%E{oj>S@EiYX39Oe%b%d`@ej% z1pQfi;>nl9_rB*T|HJQj+U|YaV%wwGBif|ir?wT0{r9ukdGG$7KmV_M+&=va59WIQ2uvJzMXdP_ZWg1Xlr%5j4VM7t|G{#wj20tHevzp3#ca2bo4f2hZaFg0Er2 z{|jzCWp>7n1J0e@b42cpSeOJ;uUq{g_@q6oWx`$04^DuT1^mAhDL{Pr4aC3s*Z$%5 z(?)vRk!`O8F zj-^xL{U&+6b%HqT(dCnwCJfk7$M~os5dX{XVyt9&wHd;?5opde`l`G@#sOWC3652A zz->%?s*{Mrm*)<`jM3#0|J#7GPTC+QFyIgbbstT{b*%Wjt0vmyTu3 zDWnmsNrl%)yN*QX6^(v71igf)j!sDz*r2p@Cm2$*ip#k(lR!{H zT1$g;X>9ZbLt8*oo5Q0?X!Rq;Ah?VzZHw}IE!i^Dvj9qT#w)troPl?$w}ERCIH)L+ zY|G~Gv+_038 zV;%g{j7-FiWb##m&?cVS1bYja&e;NlGM*UYMGn+S8;Gvwr+?}>d-j>%pr>7C+LvWQ zEsOdf%UZWOySA-1v$owjX!&dfi2AI0JR*IwU9 zmkR-fhNd`BO{_ZaeP$UukVuHO-%?p+M;<;r+owPEaM-2VcaQOGYudhvKee8x&CYU~ zb<#5X@@Oq~b(`R5zISP3|M$Tk|BOkNbNWBD967vEe0w22J#lt~MeqRH3|9zT^wT=4 zI8rzpBOyRB(i*@YC_^SZ3}Zbe%v`-=Z!`%JqQQ;}F`_p7Dy(8dAeKhak=3^}{@2+R z4|4k8?VDIx3dA{44t>hZq6vYe9!}5JK#+0jR&rbLGz~)K$!-X?S<*4X zb89Dqqeb8BB-0FNJzzZNjNXOuI#ZLO2%fecCIV$^cdFmCy?quVi9oD+vX%`)lB0YU z@CXTn$Y&DCT|o(%y581+<*wF`llOWwF{(i-@=;(}8NRc_xnxEFCmua@Sj@%Ojo;3_(;Q4*pD5MsMJtj2K*=*38A*4bgu!O9NV-*R9xWDwG>)Shu$ zH9O@t`a)h7l=Z$DZP#%76HmUF8LVZiqxI%k+g%@|sAAg;2*-q^%kPiA&v#|^gveO- zr$6*-IKkU~`a{o&%-QDs_hZF!@4nnx_UPJL@2a0k?o8S>``XvN#2$a}A{tqnfKEUI zM8y!^_OiwT-+d220sdd+6);~@T-t0i0+s)`{sBo zb~Np7wUOy}^Ju?WWt#G-ocsId=Z|mCu<8)#8sMUo@35&Hk~7asCv7g#$@q9u#-QS6`Kll#ALt%zj5ZqL;Z{J=Q98Bf(g&!#CJp$a zSt~WFe-JwWYi`D6E6Z%;_7rpj5a>XW4Aa4~Egf>q0{WH+WXHJiuwk2>pP9Cv72Jq( zZbUaZx@QXfVLGltplck;Yg}d(y8O|g4zr5RfHpT$r!%Q*9!G%;U@N{>djTW(E%VUK zm=g@SE>8QMd)Q3+WfT)jRluEOHLUMz#G_UY+tAz3$Y0;=H* z+;s+(Fl+KH6Td*E71V}*?0i-x*MbFwnd1K?k72&%z!re_cK^pe`fIf(X@49{s&?1+ zej|d%JioqPJFeGxKRH&pf9mgl?j|$*B^X-AHWc&n)r!@ar2A+~G5c8THnE}*1>?_0Xa-VW(yhH!nH+q{B4_%#TXP})Ms36w4C95_S z*2y2EEk>uDyiJs3(_2z>#LIC`o6RyqfHk=7n;l3#^vnmmqm-}Cot21^ksUA*+9>&!j>+mqepO->@*dSIy$*E*ddt6LAw$FprN(1 z=8{ZQpkX5hv&d{=n8C)jvVZXHFO!CBfbdExOk_JEDmq)9*)F?!sQR?rW&c#%awBB@ znGany-{!-8Rrk+7{7wG^9(8b~J?)Q_u)E|`8MaqCY+TnFRTH-M;i%qA|1ZzPqLmY0 ztSiA*&vCOEX@m95yn~eWGIrq|LDWFnA#P%Bu-d{65rM`uk#u4cCq%Bdtm??yPVkj; zEe%GKK&mUe2juKij}QQpCSvYR)5LFEWcIArtA65dq4MRz^Uwd5eds5@V4B@NsDA|Q z>*dzTZIDE)uh)*f@5WBHyXBbYKm1dl$2ab`c^a|`$n04(b^+7tI^92)?2s5pds>=x zAvnGChdohdnsRi6Es}WRO|rcFV2AA(vDnN^9aV@>#Q=%575@8+7qG#QiL=GE(ki6~#{7>ddU zy}dp)Cw^+`bi>t&>*RVgQk4r7ky-i9K$x3!KZsFjWM#wAWUm7eg>ciP21NCisovur zj7SNNb{Y%OKWD~j7~taE91eE)AS>7HiopU=TBAV)NEPxP1i+fnL!U63DAI*J8oN3U zkIo#CC}G7;xQx>q@lBCn=1OwS(O=}U;xtPnZ9AUMB9D5IvluTB6EmVl^*LLH0rqQ5 zMb^zoH%6z+L^czL$LR}0ee%f{`Q_V#3f?{=*)Blnu@a(j-&w}kcj~J5u@JTpk`f|I zml^iqR?CBQ&fzdvChWFmeOhNMY3L5#k=;Z2X%4-{`# z-F(f$;C_rZP8c?ylm4`SQFslvboIVCX5X4+Z+jm8HMF(3>De1%r|(zvl9@)IhfkbN z?#M`wHst*XFnwCGT9WyS+*q%BBDueY!?DjqAn0cuwtcHmm+ii3){kQrh6 z!cDt`^qHH9?Z1DsJUFvXCcvzJwgc_fb&!yBrVV5wMt$1Wp0I>yZoSrJRL09Uxqjk< zznX3nHKFnz)61h99^NeD%pll%vDSVbk*FEWru-cgXCGI>q=y%An7W*Y>h1DOEb4?r z%7=#%@?cxm_yzkEzw`zGsnRgxz_sFB{z-KKGkD`)Yskolng@ zfw(s9ZCx^)vYY9~bhXOCwalRhzu2v(|jZ*IR#dNRz_~S7||}m0Ne;BoGnnf~0CaWyR82}X{T8v<9?Cx&o&cu}!00LYnOqz1k z72GdDBAFtUvr-c^@^HqIhn+Pm+MKhMc8GBZ*|9=ne$Cp{BXV%&CUO`+%7tYTo31X2SYo4EpFgf{IJv$_KGTuX#)TW^G<{xQJFF zNs@qS@7W?Y4O>9nB+AaEVqXh~*n5=EVit7y`-9)v4;K}IX0P3M=dTfiyY`6Ob^m?J z>A9cz@Fhb<5chRfefhT6FMjGvLpGK1HhGWweI(0W8-ZksghzqBx00?xosD-cW2g7=#X_FQHSYctnzqGNEslTDiifTgX-0oPx z%xs-_wQj`!J4jcR^rpVSr*EiUvS>x*7}^*^J_B37bk@t_x#!OD;Sc?a>7l-KG}dY} zO3bGJAKQY~dt}e(yq3M|{VFq#pZ?J2tsuR(8if&HhWBO?da>|*TUgV^+ViH&KN=M2>m z$matJL(--uz{~zvP6#nuW=qmWhz{CO&0L;eMS>H0TTL5`cmr!XRSaa^ojNuNL>Nz! zWdr!=BO%HxFhGdCN8KG9 zw~?Hqd$j!J@eCDfgVxnZvGw`i`{1ut`>~VrDnSaf`;x%j(&#>R|k@In(7iSS3KqK}7Cw(=(3>aFLTE2-^WX zbr+RqpThE41>Xzbr4 z+OP#18y0m&TF<1i&NMX`f%wLbcjEpt`>uMBr_x6g)DeN~Xk&ND6skT14#?Uw>lmb@ z?dTNUp05bX?3fa?>~q>nNc(BRXaD`*@?eZDN&SArcgG~B`var{{BO=q>E46*Tx(|~ zcF@uDH`%G5`DyI2YS2=|y?nmx(aI;HAGfi~Fr3?@Z;4&Jj((}EaZqmGuHD*KJ-*`| zuNvT7vJjvxPVN70E)xao`64sSfr$}W0#l%!nNBl13;eOkO%8Nb1oF8@NjOdUKXDv3 z8>qW6&Dk8cWmE*=t&S{j5;wR~1)k^$9dweuQSBj^$C#Q3ekk^d|ARCV^U@zb@v+;! zO=t*82X=gmom_W`rF_^e?z8jLYxb$kdN~+;C077*qjTcO=bUoLdUp|oo@rsMpwQ?yHkKCJPDn080wW$1 zk-G4Ot^sT4>q`LWx^2K4{`FBthHjG)-yZCYPFw+C+P3YpK>b-?8`x3rysrsY*qTvV z|In_!zr6nRho3|5+mpPvhk!6VU(1A(;LSG1Iq8$OU0^isQTes})Me|b+nG9=W3ZR& z54`o|#HoT8Z~rwj*;w0ty7zxenC*Cgbk%v060=|%4oY=%jNNilRrbf{7Zu4-7*n07 zex+svhr;gxvb`N~Pmtv7ZU8;cL>J#nQ>Zf=*oVj1;Z2 zPL@+E9Scd8U=h6y!^<9`23!aL7!8L~QmQQ~Yz6_aI)qfPv)^TR+cOmdlaW{-_ATS* zsHXt{|F-HV+mNWS-FX`@JoiLGp_urbxzZGzeM^m7-%rJ$y#2)MqAY=x+d^npL8Au< zI))?ZENZ|9Dv?8im(O#~!4Jv^?D5JNxeQvR=VbuMVy)7Q`6<|@8gDghIuL&5bvXM5 z5L`osZ{TbC7#K8Zt2Y=7HkgqSiANR`xCtEm&uB^jo2-Khl8J^EwTnFAAG z_1-^2%Zz$;)PZplGyzejt%M*8PO;Zw*zwd;kJ<10+OM%A-L7D}E(kjKm}OjPEC|t^N?WEnf@z&h}6H|LRxyy6(fec3k(P>$W-j<#x0z zsDyt#ciR&D(Q(obMx+0$ti)I|IcvdqfkM=W}@7*Q5ss>t~R#tk4BW0S4+Y*8S(vn-BnYK#9KzD9~{Use~09 z-dR)5%EES;Dd+^TAw*ZT2hM6+pqb!E>PKNkg})5JWqEJ~>LVatXUs=L>FA!d&ZF7) zUB~%+z09-E{<{5xPkhPLfEnxqvTh$X;Qqx=e`(wBeogM%h~Kqxx7~AJ{oF$hJig-{ zucps;1puF@y)5DKa~@u{y%pR&uArCQO|iFM>KZ3J1Jw%rWt#>V5ikmV81M-OMsIvC zSxj2o3gI^mQ@Ha8vct56cL5(X&`tkumH_|Hvc1j1Lqstf6PqC=8QTSrKJl^V;^DzE zp^oo;Z11k)HWD_8Zydf~+dcBt5C7CJ8s8$V5qCpnfW*>7hAD;sgVryY33snScf z;9ES?=g5^x+!hWK)KSYdv^XEhm__2;4tBI!ka=TLOg8UdwB9w$cQd_GUJS zh0Ag!x>ZZFs(~Iq%&<}dGbtDH$OB&99RT6#BY{_oXkQrR;3Y0{&Q_@I2pEtMWow;G zfI2(pG-(?H@bM~7WG#QGF^Nv&SN#S~pU!Rm-GBHM1w=OVd~_TE_deruz5H73TJ{>r zSmRDefBwP8zle^ZY|jhHvx0M`%QE55e&{)of2}(X+gWAQ`)fRT{ixh}tN~YT=b9dH z{+ln$gz)(w%!#v2|4&D@-isQw5ys2socFoSjBB|768C76{3QLK*=P2svc(8mCQTUZ zK4dStb3bG^ZuxC0sW)aocLZTJi`UMk>%K;P))2_1;&c15&fUB6EBjGvziIpbPki8W zyAf0yUTyC#TS1xY{;?PLnjaq59f!XC!S{cL6O;rf?B(+*yAL_SI{$%EtXd>22kpFv zV((^i757ON$Tli=wk@#**P*r9tV{eiV04MmhCl!9cpcR)vS%QhnH{J5HWCqN;Q(#F z+;-zFnC|=W6_uR9*3E|^LVR}_=txEk=fBCT43^0l7BZO8nYWvCP|$wO@!IZn9;|8r-dIj>;iKo3_od=O*)E&* zvrMrYW&y>m`u`!@0=`vG-NJJkA98%7NK@~eZ&(l7tQ=LRtT0&PXlF#L2IMM@;w}51 z`i19yW&FQV)0EhH6Te3$+l-9!w{YSK9w5(n8*%Pc)Fs^{`Lw_c@#7BqO5zFEb| z%4U{9CU=#ZOQ%ftH@q6i5*#&UJNGzy>kDksVgztNbnDkko~#mg5v-y|jCJVr&g4pA zLsUO{kR`RaMnsSY>Ow4);}wYP(1}CV?5}M4c55(n_P59t(zIfj-Es=pKvjci)|*>Y zZY!sR6pR>l1P{2yIuWxkK*%qm)VgW#vG!|G)4A|3{FWKwviGb%oX=gvY^Iq@xe?LX zxi;QTD1@k?1AQa^(4GQ+S0j64xYSX$n9UG`%*ISxgG+ZVZ~jNW<8ebBE6M3|aX_p> z0)7Vz>A(Q@qt99o_99sYtJywvZBt2MpZ&~l-YgG(QBS(!kXwI4sk*+M|HWVY^77D8 zTiQ{Xuj{5|x1B!reQdd}mC?_8ndb4`-}9O(TQFpurH2`YR=ov=Fa{JK6M~SXY`31E z?MQ-HYu>W~0GESG%~rv}DhQ0uSo^iJM}Nhre{bxZLA!5hHj1(yqp+pIYoS1hLxeOY?H5=@JK)|hOE@f} zRqzrLWrKJcSv_YUn;~Sm-WsAts52wh6*y77!F~W{RPowDjFTT`sC^^|Zh>q~pT z|63kVj&=XX(Y~TFLtVyvl<1E?KPN8_OKe+WsZTbfTvNn;}ySLxX+Bxy;<-AmfUq znasdDFcE&{fJNssjvAieD-rblp82c)pJRd}afJS#@iE3kqh2fE(hfo=P{HiG`fU6E z#2lNGlgSy3zK-{1h4X*s{hzbjj@9Sx<0nU-ubsQC%-Zge|Nq1XJ`3EGH9Y93V^GIa zr{c0NXWy215$daKEb=tIny?3JTcn)T7Gt}a1jZZ}&NgON3O7W@BvW&cqO8h_NEj** zHZB6x)@#*2B6F>x>3y}#7id%`3Cy-)FIlTM@RaEg)1q08`Wo)QB zX~q!;0EG2k@dTW$s;sE}r~Jwp`&)4a1T>NuMnWMc+qJV}8W_eD5U5v2%aQ}bWFt6g zfJf@p3OMRi0*PUhD$;bpev2MCDENaW0{nhPW)kKqj}kEmg3&Ef9@!+@7#r3;RWm2P zwr<$p3UWCBNf7keIty-9*ihnD6;}bIggaYj9IKL{JK{FTTxZt+tlMKh=-QzYS3ARJ zlRVfEIW|y%I)ic=+HU<>D}5JRNM^-vgWrxT``?Tv`Ug+(#Rg1bYW;+NrOQI8n~Hzm z*S^@Fc=9ouvtbP`VYeOY#`F6Ui1pk(V#C$rzb5YAgZ5po@-S?zZ*L*(EeT0`Zy7KM z+-X2Sd;*MPj*=aS1?J?~$(X6>q;$qf>bAj+p@FvG+U_LNbUNedbfz=?gU%#POB@`I z#wNiabix=R;RcHv%z3FE@(sSAb*nkTK8Inu29liY@J#FXm-aqySIq-F<``Wrj=FUj1KC=<5 zCq8Za_9rXj5yRKbHSJC8*iV;khyxN@+9K8E43%3^q_PmHv^$eJPapMa4X^y+(}@iX zfpl;0tSCZAT4PkQI4tRL3qa|tiD&-QO2HNPCzReVm&6p=#>jx{;{VXQ3CGYQgHVs> z7k>V??d({;?9tC3+Z7e>{`P!`|6Rx0@9O{C^58H3zdjPy`=0on7D$+Q6#Q0Oe#Z7G ziJuLs3cIwm>Q|QPj#y2|#A*+KZYLZ`I?yL<+^h+G^&ee>Y2ek3=nZ{Xwg|U@hv8L0 z&D$L@&a8_#)(T5xH%d@`zzC347g#NeHuOHXt{fUiWLoTFI|d%c#X9#g{pv3NR8Dtx z86{ovT1h*|5=CqKhV01F;wIcyLId;VxBprhlIzcES1>|BVYsI^-#4H+b^^APd&ihm z4;xjKO147Y>-~We|ANrFW9pQTc~X z$`L3Bl|uj{A85z0&Sd((E}-KTP?XdK2~>1LAa^}e=JL61EjPE@AUJ3E9dZ7d*siXw!OW6_3K_?g>?`e{vb%+E|*FGb|pl#)v&T2?V{m}4D`ET z(aS@l=deLjn+x1CN<#DVO)Q4G`2WaGIyU7zyZW6-?PlE81B_teTJOELG{6_>k~02a z--gBL{XCW?x~jhIU&>|Z@hs==s$JOmvB`Gr+q8H2*V$)1f9C(c{NH@oytl5zu?-LX zhJ5gIG-=T-BJ$6y_f!?|6aRyz7q!$TvKnp>zU3?;=uKYjr2p5?YB&>7iIb*Xc(vVp z!i5gGBkn};hv>>)F;Nt7w%1GHPS72kLtsHf9YqPHvbA!mOOPMXLy8MPchp5d0U&^- zp&Jg>TJx<8OW$yl08Ce~LGi3NJGsk>Ur_(SE}$j`Yo|9Fz}e9`wBnp#BvLif9IHd; zflACYg!-&yN9!zc+>744N?pB7C7D(Q_Yg#kn6ccr2~MN{KO?XhtS(T_4dsz5If7^o z`I~h4;vjfL=PqR^;Hk;!^OTovihvaI_Z7N7NBg#t8Ew;<6}V`Z;}6L%1i*W)I%DpF zkmba1xuk&H8h}r;&*&iMi%t`S=wtv5lPzH89U9%*Xxg^Zts%3Us6G6(U+@wRSAzfu zn}|6KiG+^&o!7J-j!Dk1)vG41p;p0mA)f@q{r$V&^(;W4jKsxQ#CVEQVQmUxY;WH5 z#t)fRid4QHXN>;!IzGFvy6S%pDEH&|)jm|ule~uXq1U%R(EVsB*qYn)vlYmkAnyv5 zL}<$5d__`PV#zbEzMK7`2p>NEac18+AGw=dJztihJ4Q2z&kBdB63eljyOX^nJHw68p&t z$G~Zhzrq{j22^tes%)t@kJnddw^xr*?sf17;!P*n4v0w3;A%s?e5fLitv9+HQ;mfH z910wvqR^nC7W0!{%wm+CG-$D- zw8n_gdD8n7*v*WY+l5QJWq*xzbc)ds06>Rc?T7c6s8`dyMId15{E#Q8Kf|1u9Z13F zf8k5)iB~@~RI>(wBN~!pUthRJEwqp=QaF0WqO&0yC-p}>ZI@*pe{Fh&vBn#|Y`?m65 z;t>$oLJhF8>Ac)vi)w$~BaVRYiqJm+szef(B{^j69$L*kjwy>s0vFuFI0jxi1+006 zH~tlg6(i~~clvCuZYXgPjin&nmr2!OPJvGh?HD8DvD+%vhfjTIJ-`u@yc z@oW<|RsFfm|K|4X!C(Hc6f(SmyGCZ(iVV5Du((U&4j|Nvpx9ho2-;vv%#8 zdO2Z@-!ha2Q`%CYKoXJy%L==t7MgB=8^)aQb*YAN;UYan4LsW4tdm?qzqOEeY03>loc5C>iAJ35mJ#@oAQ(+Hfdsz6-@HXu`4d3ezMh_5gT&Y%E znK$%|EM9|LpzLcPj`5YRdl?_Qk6g42etQ0lKwP^A@2`=Ov-{5uetU4euL7MHcbgt-^U*r#R58rb2e>0P69Ditc zV^+`z1GrRd`7A~Cq0*0k+*5=|+4Z8YhxrV?PwlHTEC_4xp2@?Uo~oP+`_<3kJeN zKx&Xh_K*q~G8cCs$#Cf_8h;p|IOooaR*|Ei4(pYBTy5B=lA*L@0!Z0=PD~lEw%dT6 zq|k>Q=KVJ8L!6Da08(eHg4NGUKrX|chtxp>JisXUq2>cN#fmKU57_PU;ENgLuN@Sl zj&=V>gZ6yp?Abmahjab;x-9Ea8`Q7(meK6$toVNn|KHwk%Y?l1(hjYAWtKh;@6SX( zdlu(c9yhBIcJG0@pyU;x0#~ifZW08g`Q`U@YsRID8v+1sk~b0k zxday#X3F23Poh=&KQlQ@Qg5ALA{oQ~N9jTc6ax(7Fa{-VL@XHd;1W~H|0zTFyEJCj zgE7-pou0)v{`6C)b}}$zygyGsY+}4M&Yd4;Rk77S&-!`eKllJ2tCy-RENsS)ct-Pe zsTkFm)LsmV^B^Z@MJ9qQ%^3OHo<#r!f2&~JGP9@fZhAX^={-UlACUBSmlO4kA~t2y zt&O_>CfOPymnza>dWE|(O9hn@wqf1~=MXS)qyOjUbOx7|lN*yK zJCVXTBm-(C1{5D8m8Sk=UA7FDNJ<;oy_qr7DUg%j);IY?e^g3?5Y@-ex)et2u+9K| z09JB6k`dTk>o6K^kST(Ah>1w#9+_T~hVoDZ}OAn$ag50bzo zVDxGuD=buLh}fM6DWF75e zg{rp#B6z4={W*HMnVP_M5(O{XTZSyD2ff#R{)_kJ!6m@hu}VX-cpv~$ zPCa>^X9meolBM%>g7KrRvmo&1H-5xW$)y7(Kse~jd{ZnFvH0j7K zHv&&pK>^x2tWE^Ainp#Q&ij z^?zPd%SI)^=ArYhdyat5S^0m{Ex|hTv$l=({c0bpU)ss@{oLILW8ta)*N=XhPp1eKv|JhXCxCKaiQI(0kQQe zf!%bcy~v%jXR5Ml$OI%DKSIWm39d;Iz+x}~FYZ;!H}t{L*Z(I|)+T&uOgIp+zQijf zw?hh1P?lNO>v`7+>)8~|M_cR-dJ=F7mmqKT>IJwxVsCyZfU1zS_ufA_(8uMXNF(~_ z)ye?aw9J&n*7FV89=d5Bc!T#gI_%}Zh$)E7iGp}v?a$oz)W721#9N;&Y#|d~+YJD> z4-y>w4VDs?xMC*ND^s78y%5dQ*uL?;76e{c&qYkT|H{|R9~V6XaRu{ALIjNGwFYfm z3sY}tG7ECxhi9(KF!zmQ5cs&g|5tw}h8=DHZV*y)9~SV(CZ}Rn4gY`FFF$L~K6_c@ zwjl!p_P8D=fwS9Ks|(uJQHOocO04Ew_;N1q{roR{)G{)};{PW856-`bhdGKxzV1minws}{}FxaUKjtDj4rGbcZ0pR{Q#4pmpKSt$C;g| z-BvEUtTM2QJgjgseR+~1!H24-ec%K6i|EIBcbiq2p3l{WR)2&aO>*D%b?u+6<6o}* z7eDY%ce!oyHpT~#kp;kWhWZNuhGN0cZiOilYY@CJolXv2n0d2%aSNM@tG};HrVYz5 z3IDLw`lJagD@5%EyI?L8&cGy{QXH4eput^uj7cPkSb#>z7+?H&Zxy8~o``+IFRCb1 zmH3hhVqNt@s^>bQMx{lY3v z(^@2&#~3V1nJR-22>^oElAU*d^)JLc6tpDCk7=Re0%5GOJ~Ru0&E+Mr;tSbdDs_Rx(=cz0yo@LQfugJ_TO0JAB*aX6!G>rne354|8Jgz=d`b1yj>o2+Fx_rNz5)T<`qryLvx7~;r9IB|MA~C?em&!D;Zq& z!`-19x3lqe8|T+55{x;}_TTXJkJUgsA?epk;j|6ZTEY_pL)|j<)V?X6ZrtyY8|tei zA96?gDhONkvsACD8iV`GuGyygbyXHP-WakhYCMohR(|ztRYOqGC^E3#M^e?8Q%n0@ z1uKPEo2|zW{=old$aIbLT=NIqmdn=D_SW~G%<=AD{s>xRTx3}upIg^$yCq}rndqfA z!mi-3OSxTdg5FKySQ@{r25ZFBd?F;8B01_1Vab{$ z-$Mrc3VIwL3t-KBrBWz@d1Fd-NL0)-8DyoY z#y@-EW^08*Hd3LV=xT~ihT^M-Kc3s}(H=*c$;pVZ( zEp5t6G=NvON26y&N!#%9>$ZEN%5vs+kzRXCu3r87Uewx|k?ZsjW4j9E_v3q<-t&9{ z18)7vpZGAF%om#u$U0w|GzdAexrhcdJpP_{KX`XteJydYTi+q2k60M;TlcfUdg@^Eif^Z^lxeS6Ap-CFY>|4ICt`QO!v6mBnItM@h z0=I-7^I!P8Zix&2uHRk9N~oZpF$WKLT(d#-clG}t{DJ?@e)FGw%qrrAnI?laKZB1e z;q)f6iQguxH(|wsL_lDOL}N79e5B%%^E`xPsHMIWuWH;;!q`gRinJ@-kaIB3SKwrG`Fg_#=r!Uw zWW}WsZoD)_T9+<4>I%|nB+gC(iwGbaU9xSqyOS{dXeejKbyzxQ5ZAkTj9P9E8B0xS zu~i4o-nf2M-%0>X5J<%h0ZN=wqxBNmg8xMtm)i?Gmgo8H0<9diGvviu9!&eQn#hps z!LCFlclNPY@}iTv|2*i`l2cYSpK zW}?cV^F1pEcJ)>DaW*C;QnQlReM*oY?OrZ%5_sG1zvN3_2L7WhpK7z7FYvnwr~|6$ zJ(_iY>B+>i1)CfI&_4z&cwikTn(-&8QU4#xz@`wcMC2Z)kSrZbo{+&xA6ZR<|N9z; zhX12o+>P18ET|TMs6N<17HRCgb*ozw@8}e;>&HXWMEAcRw%_p`%aY{F(o6-*kNA zPk+GZGOpBy(v;rCiE@O6`z1r2WE`1VuXz=j&}g&&tcG9!tqY&?M_;$@0@zB@YzX90 z)zP*HkH%YHLQ1kPgarI(C;-~rly*e36=c+?u$v7IfI^Ff_s^09iTXC|OBZ3Xjj8|@ zeG%JSzI=yQ77n0?K5kkRwA0TODa#S+{vNLZjX5&aqg!W4rH7)aY!A-BOvw?mD^OXuAttuBZ*=d%UQ}EUV`&)P zW=ibG%)$~c@cp)jZFz7mF~H{gAI>ehKHTdr!;TpD^xPfa>+>p0_Zf?f#{1v$vrpG& zMuLit%m+%hCdrLd#a#^{hX23mXFfFgnmTCNeNnWI`COm2=*5TL&h);X*FGV`_V04J zu&u9r-77I*otfz#G9LOrZ3Ku2V&N)wv@aOHDF{qNf)s?ij<@@0FPyRxE&bn%h|DT# z7-qLPmiQlsXAu|9Yk5USb*itNq5tbbpiKPPgOA685fli{dDJGDUgJ1@H>Z2AV#QDV z_&*yqvCj2s3)A2H%Dr}KXKItUXZ1PU_k%z18ym?AE`45Xj54arJ+3Zu-+cDE<*+mF6_xx=C_%jXk z)k244JHc6RrFDjcQFLcGCo={yB9zBTS*zGCkV%}hWeYR{6A8+Lc8!x{00)C(A(A8gd3o;kRXqlTA++=K_fk<1>^U)e$js{dc->cd0=2A|Edm-1E8U z3f(UI#RVqOmpWoNb?0n9$IiYeOBqUmaF_f2Jb5=67KmNN3(SedLQ>g&kJV#f*b3Yg z0BPvnPCe8JGl5q~VJ~m4m1Y(^rAk#YUV?N3fiX35Q&~mH{zZT6QG5J}he`)W-K@Elkpt1uf0^4r$Wtq^rzx)vFU^lg! zt`_dLiN0F~@&DLU{=J(UM5%F|z8p0kld3oVteGh-s@w9Ji-zmMQ13uY|F?D6ru}Ez z4s63StjZ0w%>tEkn?@K(l>&|~y)~}6ZB?u%Kkz$+t@qo)nZK>aU4FLO;F;b2y&wP0 zc;in$g?LxbA^tb!nW4y`)fZfD;}96sInj1-y+kRzyn)WKbQerkInN|#C@P?M(s6dQ zUv?a;8$@pjvZdI8_$tYRG|E5U7h$NNxvWG^;>BTRH$9t(^JD7DZi|joRk#zRy7$I= z`7+SInIueJTc@liv4p;{7SN5LQiMXnG8Gu;C{MVQL3{+9DsR`Cv3OWDTT$1gG`6u? zjxKNL0IoA8*5-r*nM|LXmPmoua^hMrH>k&m6#D_@7SXfLMP~$6$nEA znmUJ7HyASVTj9U>OCGVa10a0;@0`Wy`)l`|-I8-@cOLh({cG?4m_7MqE#rk9fHN!@ zwD+Ut$icz(n=pT7WWhBr8^Hp}I+MpoIMr7!dCw|xRP zANeO9rcSbp!bl~9c~NSk|HXaC9f|JoiWw{K4-9j0C!5W<0wDPZqe84?m`Yy9eX7E&DXSgZhk zxty7dHgHGdEPz)bfTXFWcf|i)9IiBp19%9urw_2QN&`#YP-LbAAlGtG03U;*xRNk{ zl{;xXt;pK}z+s|;GvoiIeH+>-9dc_5D_iWioGO2&fwPLDAAJRu0&R?4r1~# zcqWj&vayR<|9rh3VBZ&Sr^$bl@zW}e`VWb{A`npDW>o|MGc%s~ObB_1Dcev%$>Oar zy9nSY7{Xo$`&GLpfI)`^mN;+X-3@(9AxBYBHP#Bk=W zO#;6!Dg12(>q{TCv!g3WXV>fmwCC&DeP_>{wSTSMv*&;E?|#_Y$Dpjnuo#6+IULwV zGl7E|vS*OBcfYGPyT_S)(=pfaoXdTUSRRW&_vv}qk)nVIvM&?zN1=8Lo1{rsQGkU) zeku}CM*)`+|Br5RJ*wW|Y;QuxgBN@(C!TR5Q->=jkf)3ivgCrr6S{q9)(ZNh)~ zQOVqB`E5T|pNXf=-wSCUNMMG|@0aQSja8v~Z^4W{Pp?@0a~IgnqLaHtUpP-Es&j7Y zU+{m$hjx~pk%1A$l7;CxseW18t&Jp<{vu>c{hzA>sr*o^M8)3YwsI7I^ZR}!o_z9m zhP`86r>&?j_5QoI7h7z@1{v2jV6X50C(qh<|J8TdR#B6un7k08FpeFYY8yw5v0wSP zOEsgZ5%p+V>8~ec-r0z?K}3+eIw4O8Qr%Kv*8_fmKHUMSZGRpk*fMs{N)X4v)&k+4 z^%=2tKa^;_KMO8t*Oh$usI*ik!*#rR4P(r&y~5T>qS|SY6hp^(?S`F>GH|TPqPqlA zRFCM^9cF?XA*rY!EX#N@K^jyyWhgws9(%^|P2;FotC4(SGQdLOBqF0 zSAeO2XYlY2G}ij1qah>92fpY_USiN!+My4bMbC$8=U^Wa^nJ9^pX~tr>$ClQ_kETP zZ%cRbEk6>>(yCMKIlb9VDWPN|$X0BQo|21W4S4&8;=6zO*`X^vdasAdJ>TnC&&GLe z>^kOadvEl)GhJ=3U;HI6wSkY&(f*3F7vOW>L+HSnn;5vScnmu$zR@RjW@dFqMg(P= z#!=XMl=*8wgMTOpu{mV-hFIe^q^dXyy>SuV1M)b-n)>i?Y@im%m6zoMq~^U{uG?E zd}9AkOj`@@;IC{dr;>9kS48hM)h@)1F>XBJx_T61R=JOhvY1q^ZLEG&nD0suLAA^o z+`&yM4Al?$#gb^4#th)njdv1goS?af+o_^$SWYcaX*K3GfTCHDU@WjPx3~Ro5tbyO zhH0u#Y9NGfSA+OSAsEBTDM*@(->#ucfGpS`Aq>zXH!{O!Xy^9+?7PS&6lJVZ0Kftx zs8C}@0xnrlVDf_Vi^@O9h$;QCLY9E4aFaTo`dPGA-G<(bOgP6skc^**scs91U|zJU zRE{1B0^3ITfTKbbM>hyUGQyy2Y2OZy2aty;{-%ziCwX8k4JNv6R!>+_bNvPxc`O4S7(jIyArU?o>LIa{%n~=$JAYW0@VO1jy zGn$OmW?Y0b=cF8#?oEl7$lv~tpE*FKciMwXGr3%nw|e_41fP&$)`^Rm>3k7$Le7c* zn_V;YXdF+eBxGI`7fJ_Xp0Z{7|F&HCJ>U5r+rC}+$s9`Fm9c2w&p!LR_Md$JuiO9S z|NN`=G|5#3@nj~^Ar}c zID0L*p}idvWf{(Z2LSmC{hwit!I&eJ)hpN-PdN5tRiniJ09=A?O6esD6AMh#b?ej4 zNLx@I^Z~z_I#KMCA}4{u z^($!wLuGjSto6ov}FT{Wh6NLRDCcp9FB3VdJLxiFACADi+8M z#AsUzPO$UEV8kVS$$)FM2WbJITn7YjZEtePu^gd+m-K7<6Jd$`p6 z6^O}lj#O*ni@xL$zbjrtjrlL`x$gw<;y4a{=7k( z1d378#L0^g?K;){CP1fX8PW!UnlAon1EU?mAAX;H7rq1=sfFbXjcOfnp233wZNn7C zTmjjTU{6E_)$cyb~>&h3w)T*e{<7oYw3vV-f*m`K<0C}bZlg_c5 z3??JcP@z?+EGC!Y0|LUSNKuINZ6=HXQ)8&ikf?EOG_?UE8_QeHEKh@cX)-~tl%7Hy zlZP}mXDXL^SOPUQKwL7eq|i1_4o@(&@k?k1(@i@q5@iG)leqE$wi6vRKPLC&%?LBOSlob&;Pthcfae`to^8~1#>9_7;XhvCL}#CC;^%#-L9+4 zXem=j!Zx)SRk5;?$2y^^YwhFR05CSBG}1V1Z4!*Tl}Rya$)_&j|JMF3m@oQv%wx;c z)$LUk2-anP9m2%zTL&UXwf+8o@?3n!|Kz{%uX_Ds_EoQc+`ixoU(V5=`I)wTW@p4+QTGPASQ~|${yow$6j<8x#_56501H7tk~z)coy<($4JG~ z{ZGqqv+DKuU)3&Kzfe_T)D8@hsq7{v$mM2+=-J1}X-h!`x|rWV3L|79G9TgC!lbm# zIV(Z}fB+Gqg3$+CB(X8n%^-YObEpohZHw*y+ogwFQA0VVKH9{}7)8ASK-_XpfT#g8 z$J!ubGe9>_*8pA88ow#$vGBEo>E+@3Z4@{sW;32WyW++`lCTaEhE0T~E3xabt)wC$ z*Q~2cDpNHEMK0^VgY=p%Ma5CiwgRCbJ)ar-6P!yi2H?zTuj|sLN1p>w&!w>*eIcmD zD6*<7xDWkd-$dKPI&QP(1ewt@$c+AD4V9rt(mw#(#6QHAozFCQ<%1;frQh68YQBsL ztg>FWZ@%1=!jW?K@bmrSuYSls@AF@5$E7Y~_E%%MdQX!D*oEGU0E=g05n-L8*2~%^ z{BS+@=AZotpH1x+A!YLtUqjh;&lg+wVXIC;YP6}n-(UQNXY93K@Dh>34&$HuuBIMV zg4y?Xz&xu|o$2{ZS2^9QuBoB@k3Vlq!?(*qnTJuF`HVB_ru~j76TFC4pS5p@QwfNh z^>;#I_`lKrc@Dlqg4W4~8!@m%}88XG2TnXAhEj?oB^)ONxH_gZAp*_bK)zfBY5p$G-HHxAXZjm1o$X z*z_%_`h@-3uRdqL@bkaDRju~V9I;H53{NuL==(gk3hQJvS(vH^o^>EcL7+K;$>AMG z2H9hwpu?iil0ZJK#42E%ZkXa3E96idbK0CN2%x&Os2NPdfg)naGz$Ul>xh z&M!RxZ&H7?(gGk0_(iqF1V#8wcjNR)BoY$(z4cOaAzv_gcqj*K5Hoc#P9DD;sQ%3$ z7c!as8s?$IaX(|*TIDo)@n%?)03^$YP(sH+D6#CX5ow#V$Y00p?240NgQrf@jZua1 zT?ZEnv`bx3o>rhqb}-6YTDd)7#p-@Ji5?+F9q1dTL=*5HbPR%LtY#UP@Hm2#@7L=$ zdU_nbL?k+TBUw4b`0X*y>n^Q0NVz2p7GOX}jf%S*Ysk2*#v3qn)f>UdmLiMw7Oj-0 zcI`*+eb2i- ziUordE0a^XevOQt>2m#i z*3a7RwfooOU;DaO`49czleGU~pP~NW7I$h(>I3W(1O+M>B;xj8g7tnO)c@Vbr0r7@ zx}l)PX1d=~T}DC6#4qc<834P{=nl;^L6+5pN`%}5cilt$f2<}1u`HwlF8xrR;p-+1 zWFG??Ih<{|@bCTD2ljt2ed$B?Isd&!?2+yDa~?4iN4DeH+e+2;?_KZuNG>sw)HHV* zalww$Yia>uLJSH6%?B|~sLeTN|6$U-+GIYfLylDixdq0t z6#`_Aqeeog6FR(?Nj!@>w-v0A2act-Jn1ABF0oDqmtBSFRweMY_KOW6-7pF5Xh@}W@|=cRFbew`z1J8m?X;S;v8l|AiZZ~2J2ALMCa}X z30hY%yhtI4E+!>HvrLq8M{p2**XtqMzLpH#VhyawUtDy@AX&-!}fXmM&DCUe%xV?+u&}Hm>`*T0#QrT4v)f-__BUgha2u=6fsQXGq|xv08|!-QNUo-C&t*71uO#k^zx3s{6D;XZU~Q0d&6cah z;s3=aq)!Pb3AKu$p8j%S4!Km_yptUaSGI6)B5ElK@8>ckOn=3FWYN-EHlVNWviy(H z)w%TVOTF)Vc07ZHgoJ?Vx0MUBG^_q5cu&C!ON0mLG&r#HnP-3Z))zjqlZ?0j!{2UQ zH=9}X0>yg%P0~}vbwb_*Z|^UZWEpH7Z2;Spl%2|v1vUi0j%!BfEsV~y=-;RdCc-_a z8ULeaZ;ac@oSjR9L~?7wPoXuUqHFQ(U}2zI<3o@?br~wJ1}-yX&)Q2B8p>Q(F0(&_ z_%VKBq!gLA$B@BdnFTW$#r87|f&d0E&2-ZpP8i9N7=-t?`h!|EfvND)V-12m2DnU- zftnx(SFE-@r6&T=Q@X+uu+;Jx#aw_MvZWRV6UmaHfdt`R&Iv1QFxcomcU{-UP45}N zfW>+!l_JfIf6?OxuwILxc^jaMRNEavYC9=UzWE~?)Scr zW!ga6)5wx4TjgR4`A-}_xOCc}DAt{hW}(wuKwUQ6{T9}Rm(hmm)jL-5jVwKbV@$os z22K0&uYS}YfAz!me2-Xg;DsDY0>n>F`{~c~#6~LK`le^-SIO7PS78=#Me5wl+Sbzl zgE2nsDF(!FCE4K;CHuBaSnb@Y3%z!iyWrlDW8Bf!wf4J>z7E2xbNAVw^Ws~A_)>iC zq@(S&2J7M%aF^izqy2gLZ~H|Pg{VpndqQ~;-Q6O@zWYVrRCgUmVs z?dz1GV#&}Y{J$}<{}j(c4B1i2L~S^25^{%P7R7d88ghIqqMtfsr7L4ajR`XE0aDkc z9o-w+l0IvGDgx>u(WV*-!#>$2@xgK)szu$it0ByZtZq-s)EU7rWepqQKS*p! zclp18g0k{6J4dj*_~ z@j8tez`%9jU%kTS-G1~nF6UaU3F;hGyLoH<&?rx~5^3`BzWA zV^Ez6w{`+V#!RxOyhiI`dSjEhq>qJU@X_T&R?l+WE@O2#8WK0_dBy`&9k(*2MhmQz z_(i~hQb!G7<=Co9DBa9@sg33s?ZlY1CeltBaGy~%UG34Vu^Z8`H0aCV-IaU|GuM}p z4X6cHjmhmVz_*4&c!q0Z7Kd}Gm+m@h-VAN;D=7oc0_|%UV8bAC|tO2m5&m%m88x0^BP3cK!j&v8Cl!ye14wOK0^pVe?F^j!PnlsEf2!~o#QS6 zC}AbT9_pUz{|Gc(iWQw<86>77*jwKCk(lFN$Ek9!uW_pLZsT%#&rC9m^Pz;*2-qb)F< z`WgGxv?{Z$Q?K`GUlkYz@pD*R0U5J6)2}|$F{I;`J=?v|h|+lgW}-^U6wxO-&{X1E zysUu>fSqG$zy#o`vz9!N0tWAGBN41Nns@ozoFPa`d`AxqEKy;;IKQ1WU}V*9U?NT6 z&nNh1CGoRA=Tq#pU+`iZmoQqNOFd_Mf7U%(L-YgDTK5?p-)rJM+wXhVHs0~}TpsLM zz)J&~{vAe>x?}KE`%$#W8d%y8!fKyKUViqu-}UVqjI!Kh=VU85u6~ssg!JpP6en6I zXQWF%A1lhMVaQ48Nf}!UC`cWfysXg zn*u)|s2JS-Xv*`B$yWP6WGOO5JZub_{y*|D_Q&FlSp#r1@Jy`hb5lDYKh7!?u2Y_2 zD)9d}IIK8#`xO13xR#?u(9GBflF6nVFlIKE*aD3iz>~QN<7h7#T1b3yonxVs#Cv0h z92;|HZ@v<(5Za`~Ebk_3d9(I0ad6-YL`|cOu;(T_f&RUfTz-NXaqiRI$8jkUy7&=E zqC$1imeTRK#uxL;O(BD-SfYg_!TSC6VzNA12~B8~D|}L$V$GSn?1p#)K|?Z9H=)~<1za7xLk;ixFBeO z$>(Xct{O%u$^chn3Jkr4ddN(ekp9QLC&@}SSEzrXYcPD_k-vM z_uhBy?4Gmv)AL{P)t}lZB9HrtsK@^H|0X`#`9Z%zfk3ahF;T5$L;8Ql6B40d)Mq?4 z%76fl%F`liEPWC}5Tmq;22$17W!}MXzvR+ysjhL0I-x(d|M#_QrwLv2CM2h&m*xlM z{~E5+&0YLoxKjJRRj@7DyBf1@Jf+SwJ2uRr_v%N2?6QJkBQ>c66BsE(#1B>C9M2^Q zK|KRmN%SOITM?H8fDo+9Yi@}D7ql)hLoq>S1F;3v4y2@vYxK`C;(T~l6y-f*2Mb&x zPc==_nH;6#gVkrNN|)X%aMzx|IoFW4y}#)?QJzN9Zn{d9-#Xvl)W(z!hAla(23E1l zy?u(y`9@;}8%klKt4}g1VI#;on5oSpgB~yfL7#)K=>(ZDlYL*#7!t7xGEA%q z(wL8~-5Rsyl?@oHtRQC!X1IrZ+Wds#mh8xSLaKC=OK-R3lieRg9npw+z+CYi2LuqubsvNzdBD} z`MOuwtAF2%NK)XJ0^r$(DJV8v0UrYaW0DtZJOiQFPe$}sq~sK?{$Jo6sY|a21G$6@ zW(yJBY!zFLVlX zfVWoQ7+a4F-?)i%3<04<#~QJO=Jq$0G0HYkt*m{f9ycb&jNych^0gIXHUy!Nk!pl= zvSTdwLf5ETu?II2FAOp>G7>dPllP+?Sl$x+ZD#Cd%6@T9`!z&lc$x*6oY#A+uQ|if z869@rUD~0i4g<^x?qjY+BEl?5iis1V0guJXs(+V^($wJrYz;(RD#pL)i2-o&-ho*p z)Ye&^XEn^5aFo7CgfJWhT@rEVkGAg1p3&By>mD#VcbIkMK*tHJz;L1qT?C>UpOg~!1 zPshCKZEfqWd-R$ucZeD!D&gK%QAL2~Pu~8G{ccG8^Ot2}oEBVuF1| z*@O@OuRuG34JS_gKdk*9(%u^Rr6RtNr)nD&BjOQoFM#u+qm&~oboHXgnPnLdAN$Hl z<_5lZ49BxlJwX(=k#OcfJJCZm*jcp=_5Wzy*zBuh&^UF$6+loBZ>{=p^i!)aOdOHb zHU8x}uVKJV0j$MN0e|8nOC)sbOD$?@}6q*GR4r-stwUKbaSrNN_t*i2rx^ zN8sXa6Z(H7#CQu8Hi#MY3G}oD zkTtLgJa5QsMuo4kT2tW}D6||M4LmS4kv1I`;7)nkg)!Atm7K(34!?lh`|q5tdVuWZ zqJ6!D!G8jrE10N3*zQe86XQdJ3H0f$zw9Z7RpP(FJpn21*5LN7!7uyDM+}jLM1pfT z_xk8PsvoBe>PqMLU=4yq|M1`&#w79bfx~SK1S= zei40ze8vQj6P+}Or*lQO53K0SdH+RnVYQWH)c@Pf8|S91>`JR!ip-6tlI2)e1Wqvx zK}(hdwXX%sYAL_OpbS)>lDvk1U_T;ipu&}RjG)UsPu*U`4VOl1Xzq*uTVr{?C!-7c zfBqc9WH$&QE`$K`74XzFM907S8-1=7LW>bkY=D9$)#1|~6}<6SiM+zCb%N_ffJDiJ z={|eIK;8sk&S5K~DyX#Yl+Dpw>B#xXc&sN*iCB$=Hs7bjUTcZ+VtPjp zQT>IgnQ@f5LlI)1Ul2>!&~6F)81m1zC)JD`WD&@k^dgOe`1C{u9i|b!jIzbdp}8Hb z(Kz#>jcnFJA7%#V6-l%@(e3YoFpK8g6my5|mO}!V1WX6wYbZt0teONk;&OzRMMTN4 zF&XfXH_G!5kp`vBHCwbMC^42s(dW|6_PM|9BKx7FAARj>E}^hIZ8LIjB4und$xSRE z3ewfhsG$o87klNh(YP1FYoH?DB;LA0uwy>ZIWuStnCCY6{?yW!j3_-Hcv8>m7z7t3q+tQ^9 ziOVpvj9XGBYkGpiXzMkDPtK!%D0%^Waj&J_g zA9I(=ilW>NBaPF>dC}qj6;cHwvf_dO?n$;%!KME6!=8tUAvF<@CTs<2x}b|*fVcT2 z9mOk6%!1?3c6-jWqLZ=;Ub@KQkV(FHGZNL6Bs!`A(jLA5V*I|moxf#g5%ysD4#&q zT+I9*;ed)vGan+&*uFhzSU&CSG#!#~ za;PvGF{3+>$^=3jpJ_ai`%N-j_~o;=OO?O={@)ow-$ksR&v92XcaygVzZb{07wa{D zKz?{uyrh9(grTsJL^k-f5S@?^Cx&=#NO-W+eoo%K*YD|{PE(Jn0}uz`zD(#Empb_= z3B>;!%SZs7LvN|{T>VFqCi{vL!%vg$Qe#%FJ zNKECBgLAa6-atH5he&Rl6z%`JtX_j<&yqW$2fr3`(lGa)f^*e)B}I-XtHv;`hi|6w zppgzb#G`uNtZf-GBj_PShXnL0@KgskY+QV1?1vH|hmnI(76eyrI*kgR49T(1YF#B; zR1s(+2jrrp&IGNmWe05N(&h%G_FLx|dJxqvQpMIRjLo4mI|lesae)r|2wSR3U4;q> zI&D9tE6Z<}r1v|@%Qe#e;k3X;ay(C2@Or7e6LxyUfdr*|x^&i#1RLpZ_h?th>hetX z+aV+ir6vH9`ix1o{766@nk_);Y%@?YiGjob>LAC${6sHfugf}(qeLj0Y+(Gs?FoUQ z{^ft~F}v@$7KhJ?SiiqU!1DaSirI&soHIJg_zXR~;}@PCjisL+nY&A|1xZZmN+Ol` ze;I*mK(i$nuPU&NYyQJO_(3Fja}1RIT(_q>nPa&3ID7Wmb9eQ(o%`0m`09K<|079b z&EtHI$}Rk>xfsDW9nQnsTein8BnKJXy~;CZ{-4t5)&mAC!zEqx-5N z*4dpn*nM5_-3Q)e!qRMn5>ov=`wGKKkx14~Ugn)aClhG|9Pjv0HZ+;r#e+n%6A78; zP)Z>W)^&nrt@>Orr$%Kv3K|_#94b&9Hi8l8n;)}VH~Oj)la7oGYolr)QoD^wW1M!A zDqWf*5P)JvGn1hIH=cTh3^(u(MdfUeGI8%2mH~kqrC2(B-WNzloH5E}aHTHv6HXag z;bsh56SxHE46#v8fDD088)d5~j9)>yp(BfAxhYR{pYK8@fpbyWf`>BDyn_>TC@IH* zEtxJuFyJ!~OL@bGE?k6(c4W-4GF&@7q>OF&riD{U@Y2-==}yvwx*KdO=E=&0+6bK_ z0=MXLW(Sepb9Hl5nTF!f(htNPKZkH~V3`paZ)aVi(VCAVF-}>)EFX+Mx+1ae!CK|$w$JXtvrm5DnnBn+L=Cybfn$^5SG zTc#<`*3SOBSYPl6MY0i_-4^Bs+>k^jm4+K~RdC?m?MdARK%XrlaOVGN52I4lE?f5@ zDRh-N<%pGJVeGTdf}(Ypb+WKN`&t-P&0%!=^)F-(k9)glHWdz-^&YBwXOogA?0DKj zDFhV8QQXFFx)wP=69sOk!N?`DdNDn4SDLLrz#RR4YJ$U1VUnvTpc?wCeN>l9fOKdG zSBQCXGC0m|hcPHslwjIdsi=Yz?SwEKJPM731#e4(8{YJ2}A>M$8tf0fQYCd z{k;^wDjDTcC`zg|M%A!Xhbo(lschO!*BY%#6eDBk>EDqvNeNFpj#Slrp zv2FC5f23!6^fl&wXFeQ*EQ7-dNO;n*_wHX<(lKV@;AZ2#}L8n*BZA>6b(qk#-PWE&A}Pfdx( z1c`72ww(SV)Q)CYjSI2+HbW1Yzg_;DV!p-frEpdmlvsa>ed)Ei$Z;JGZ#~cdMCw%o zB?W^av+9CfgO04gNTRI&G(b<7sdWZ_adjI&N|hAvl2i4L$NScnAxFy?0Xt{%n7y(zzDz%65ximDysc~NPiS`OVGLJ{0yH_-tYxB?sHQ2^P$EMDXn=pm`*kXKJhu!u4h>S^2+J-*vd z-}o&rx5uA&NN!mBdGK@Bjt9B-?4C&w?tkX3Kl?PDaR%IrH*zUhb|o-lMD?pObRs2o zo=?+j1tV(S+?e&|T<%MkSLD7nwog{a60jfOo(F6Di6>sP{cM96pT-PT-SK+*bS5SJ zuPU4F5m>j;7$;+>C8t$4Rlrj{hsxiDF$7Q-Rl*TO{6HxfRYPX|XI@ilz4J(aQsT|m*}Xi<^eRt&GuW2W)n zdReh5K{*ntY^4cFG^Wc%+__okcZ41`H~^g>g8q^UeYrmKY(67aHHi? z8YhC44gHC#3XHo3yBLjWMUNmtK<<$#a2xM01;O~#*1!z$%|6p=gg%EEsT+|ADksDo z@uMLtL?wWmr3wB}EFT%!Ihejl@W=;RF&Jm=BkeB!imbVv!SdL(j)N0jixqc%G zU=2Gf(jyX}M2ifEl|8~sXcnQOPjXXwtF?`2J$R|X32-HUk}oe}DXrsq54IJCicOcT z4G6ak$X2w)-PRcxay-{dxpcNa`1q?I^0j((*Ks`-H^48vaeP)ntAAkl?4J9`Ro|1( zKly=A*!HJsI8tn!-T!|hwgyZMYO$v1K)~<>GiDb7;d>lFC@~*XwoUze-~F6F^UUQ` z{`-zqxA#4By#4^4Ts@95e(l%2YX8}WybAIrNLW|cHEq9=Gx&e0Za5u-zhc$io?z2z z`?${wmcx|lIeg!+MId}u?<_j`L)#s=@y{yKHMKPHPH&6+<2QRdYt znaJ$8A*6wENOjNsB#s%jv}haIPkpT1SE8Z$tQOYp24R-^%}8fU*67o#0L-cbKQB-L zzVOmA4u>gL=*o$hfcdP#l|M3aLf?Lj{{#9h=>MjWVT$jF|IPXXES*$G^c~$&9v24G zCd#R}3RuM~!cp(YPTQVBj6NjJi9e_nojJU73X4=(s$SEg3S@?Ot}A`{GYuqGH)Cd| zdg1ITCr!f_tBs`!hrs{?5V@R;JqOCoZI6F23cdHDn3u1LExU^`yfv}gJ ztiUUJ45B>sH?vr)k+=W-_y2>($M`#d$2hl=rsHe#&>1Xx-e+cnP}%nf9OLNQn*b2kAju_UwN+`-Mr*5okha1=#A$w;igL+OkjLeXwYu_*MI** zi|*ne$Xz6A_->r(rA2}w=D!&CJ;*WVDvw7Wy|HiqOP_AW;E6St3LvU0MU1cotSS_{ z9k3VUplu}v;)s19EufGfml6mL~UH&gPaZY0ioYa_^k)CaY? zmhW%+f=`2YCLrb#k%mi@L?%mTT+SQFN6>U^Bo|?W`yBR<8f`9GAyEZUw%N`QO+mj0 zlbu9gX)(K+Qup6Bd2{3S)l*p}Ay_CXir8*vl^yG^AKN{26AL2a#0Q}L@W>Y=6g+nAZL868UX^@#;eBhsf z2n|-zt=C){tD;?k+~;aMkRY{@Yua)Ni>1N#lMbY?VE`}!_UbV>H#18kIck!m@1=#4 zK#zctf!&vOj&k7hd*Ag@ggbx>NGw!SkY#oN-Z)`W<^QYd@unK&a!OsO`h85?E4lvb zpZHKr-O1G&hwA!Fe<3z;)N|`zcSSc*aa&2&+Q#}%$*=y~HoSlB8(x{~<+(2=HA7#8 z`Fi<(+0lMpmd?umC&U1D+s_e=J2~rMrFP_}%am4RWhQAL{|JWq`#?PpD#DP_t{>@! z4eU+Sq~EMGTX3IMj@hCyU|1yV&U&dyVoH;iFeG0A|7VqCA$pV8J{Stz`Pa?5^rv)C47O&BtFwsOR!p+FER!HkJTnjsx)PTC6P!O27O^3 z205g}!FWpl@17b9Lc&d95bh0)zZR3%RO9Fi?;$QRymuNYjgm1JwF?7SIb*rU*e1eN zKH(LqYwUH%#j+dWBr<>;cntiM3TFo;Q3NIC7;XbZIj|KtxW*v*UV)MR z0A)K^kpnnh>)4G;R>m@QKA-^J)$qsxd4rU`F1iyn%&`r+6D)CaU*?{Z0XO3RW;zo9kZLsjy(W7uhgdINj+{NGX?&s{9F4p&-%yHJ{nLeLSJL~aH|N7(GQlTpV0Jr)Fa(fA> zF(H43;pQfW|C{)~3(5p9f{9gsn4g%-h)qJpQ`k>XSb-8gYo?XzY+oLo`(D#xB5c(w zWJg<7m9`!eNL>*ZFB`R^|JyJ-woQ0%DDE6i{NJGew%H-8!LN)vCPff#PTr%i=9@8Nk)%JGH*ot*FY)CbDUvv9Uyv)LhyWo#Hv>*s z*f|R~+e0^DtNokV2-=vdPPZljognT~S-_tX2pD-8B|tsDfkw}6p|w7+fuJ#3L-24E97onZpfUX)j#mE9%)e~gunmF9hW*Ij`7Ov$ z?Y1$V>F!^|5rWS9f1TBWar9E5063B-qc2t)46v-iv|kpsxd6=!6DnP`8-25@?;<#9 zF?Sw~NoE=!no#$0nHeg{9oTytD*W6B5fUtei;mI2jPvuw+ZI@9=fD=xIi?V>Cb7Jj zN=_9WY)C8O-r$V-Xg*%+WJk1g}WIwynXB zx--hp0u7dy5v!UYS{GiLqt!D|Z=a-Y@Q|6Dho3^mn+iZx{-_D@!R8mT6J+lb@I-kg zjrClFszE1HK~mp_Oif-xhC=dgBj*NKsIv*F3;tl<_MeMKZ<;GSFCu2xs2Qu>05`Ig zB5avun&_;aVBqjBam_JWG{dD0DZyEYmdc}u zu=Re`OUS+bi_HaAViQdaHp7>SX>>{vHD+!&ubBfeZh;pO42faLx|Wp)^|Ag_y2vUF z`bm3rASjAH+v_)QX%O<-DFFgGy>`TvXRlQ}?rIB%+S0Z6-Fx=+)V8_(Js)j&E*tEi zH#hu-=(h~tqzcH8sF*MZQGh1dN9P;L;@o;ad{sejpL^eJ1?$;uv%4D!{Y0$XJ!AacoP4-}Gmn$Wc~Bg1@4DD#r|HjflKTaPLz}0^H(cwZ4P4 zVTsF^eqGxQQ^I&KVnK=a|9x|Nxzz}K?PG&zNtkf6Muq&~r1`GBg~pZ@V*xFEnHVJj z-wewgTu5k^g~r;}J2-Wu%s0N^CJf$)mS@vv+Eqo=WC)_Gj7xkFdPxNH!9&=Re(|XZ zWhd6iJiAZCf5to!5M`;Z>R8Jry<&9m|HWVk09H=Y1A=^AdSaUnag)fl!w6ifX5kl= zWiM6MP>`$rfhaHG&c?74{3&IuiTcXvVn_CFXcm+JWn{7GKd ztc!X%pftd6#OEogv>dpI5P82Hnk`Dwj zAn|l;cTV~-?4Flcrs5t>ypQ!wDXgtn!<+EnVOWEv8q z^0Dwg^2INFb^*v|*dXCrgHi$#%Pes4lrar&k2c6G$&3@#EKHP*8 z_*4@O|7UwpTyIJZ`dz@@t2#^&YQ2O8F+^F{uC4bKK`$INmx2wO-Br)4Zl)ev&vK{D zL+5ZlbM~(jk5z^^)xFO9di|UK+~fAz&wr$i4I?hwR@q_n)R}x4pyE_PK`EC35}YYU zvIsF!e#yua7^j9PGX2BoeCEr(SNMEThM+MICd1*@ICR%^U}NzLz>dF#SaRjFHC|D3 z4jhlUhEbp$=YwaU>l-Pucvn?RUj;gJfZh0kfQA(k2_sMKXnm}93@wJT%A1b*mE1Z;zhE#C^v0J8w zV|2-8Fj!|db7w+;Ueds}e!FH9cvS%s+)d_~}sxXliAKJtS zxj$-Hf9t{}h@iiExHhh?xpirwG7kW+)6RBc0sJwSBy?^{ z+JuAbO>ci|@Zb2>{$tKFSW^&NN!EI}5Z5L?zC06$c6G!0S)1$g>wU*(W8Zi6wl_VU z`XTQ3tlIGZe$GwCh9FW2zW8b2j~KJ6or3V*YOcl*gU+uZSnXgVSbG&qbiR(SNvzAO zo!+B5S>Ml!x*uLw0(HK`8~1+x_J8}+x60^J&DDu1nP3NI3$YwJ^7?6w(GmWPO?_H0 z<+y}7pEcxML%wc{ZKwIbfMTvn*hqkmK9mu#DXC|z*QwPp&2>5!J&b&+GHOhf3QzC%f0qAMcx=AAjOS_U(V^GfckfZ@|(d0f-;s3#Z-S zWN-A=^Zh49P>C7UJ1KPJq1%|8D)fS07Dreki?Mq0Gj7Ko}Cb}_J7h0#H zAj;4Rt_i^y97L=A1fE!9)b=^XJ*y{XR~&Pi2LkuNl<4>aGG&a1DZUHFO1ujnppJrK;v>$2yjUc%@9(R)Q}#v|b9sD)TYwM|2TXZ-2zg z&74Xrb{}#hFow)gNa0wm97?*7zFnshZ(qUIF+qu-q%*+R%Kj(*-WfT#GG!oUYQtn zXl6nZZQpYT%K|eivJW|AWYN(p?BD#>m-}OnAIVh)3;R7it_5-Qe|oQ&hWjA+&v}P92)*# z*Xg@*k4-#dWdbvOSRu%qr?F)FnH%7aM!Cd?N|dKwF`a>+M&sx(+>wT43!lkmRQ41& zY7A=AF8!{UyU$7Z;DwE*-JUmDF_TDdQ@1uNSE?t&mX2073dst^#zq}nsko35A{K^3 zsb;N~_Hpg5o&~9halff9`?@Md2y2-5e-!rDWRV>79!qQK0yuB?YwQnbl*iO*)cx=Qf8P#$!2rv1Ao>4 z^e}uSl4_6^0Lm;q)lh*#oz-5(d{n$0OC}|KuO&(1W0KA2ILDk+sW{kpJu~|74m`L2 z`-X3Rx&5hs?a_m55=;)~&j`?cLHI%LMHNQQjQi2I%D4TPs6LNl`)P(JpZvJlgh~y2 zvUC!Rq_)^bpbFgR@7DlX{T~?0+5pcx`C7HGPgdJlHoyOU!k_(!P5smzuDS@(PmJ>% zxae&~xt`NwA5@jE^LKr+k8?PGmH%u0^s8?Fz0#w-$QvKnAESmykteFUllKkR@%3KL zkpnY3*K`SrKJb+|sxlCps0)c00o#f}dK}n5jFlnPr2D?tO5(Y7iFFbOBdK#X-?6Jy z1Diu*+qFyERwVTC{0b(jay{uHo>E|c z!AEk4@z)QqPUlnuCwf}G!_g{~gc5zTrOmp_2op2ZR$)Xc?4`USG<;ezX>|abE0xju zm_^G}OHr0Wp<;E=AJ~N^e!ww$M2*0xVPj&}q%A7RPZ37~(#!CsB=5$eqGpf4#jz#3 zC=c^xr@mpcF{>J-4lMz4Uqpef8Sstztn)^p#lt&z;Ri}u3zoLP67BY%8^l>XQV>;& z5}Rn9w$Z7poF14&mdcmFhkjXZvzaxbZ@mXt?J9(sT@zyxH2BB(LQoqpvcmii@9?0o zu4AL!w}S)t4@9pFQp?W!E2^8BttbKN>H>_&hJ#{1kI542CEyPsSnHD?Hu}_3%Wi)` zMWg{@c0vHl{=`>2!r!~n4CCdA@at`v%_Dm^|M0=BE!j z>~SN0tQEXfcX$}x^$ z0x^7g2wF)`q#ma(uskT<7iS6M#91H%br#*P62jh((APp{)+>l0#_h)By-lJ^H3^m= zHVhF$FmsT-oaJPeGM=(O>Iw3@jC<^hD_?QR-MT4LeD4P_5Lgpp0)jubrjV;(jx-d* zVGJt z`7vn24cIt?o13*U3&t5rW65Etc-Q@mqCo4C?)Zh>+XpW7E4hA9K`8YIn;BC%X7ps` zNMkA!+rVlVdwkl9jMFmJ@~()&SNe@p1?S!{wQDM2tb=TKm*^)cX{~ddc0c%MoVE_GU(FZjf+M&Z=rVIJ|#rI=1(`F7cog& z_av3OJLI1Wlzaopo^CcQ-So&smOitY{{A~y8k7%*yT-Ay7^L>ept#1eq%4%S!dq> zH-iDYVzeuXZNur;2C;J89U$GhPuO@+UK`^FV5+{u#5p{ektu_pWA5^{00Sc^%H~Ee zb@q*` zMl*UMyQiIKGSGpVCn0M_fFJoUf6HFTahI+h^tt;4C zw_X&sZx|#Ew$s7Q`vSnd#~N<4nAy-S=#`vvu0Nr(OfLP^CR4Ab8JsmlXQ9`6${2$f7jAN~L zgF!BYxs8{)q8(Y0JWhYtKuf1V=abRWjHcbUp9TUWbYfT<^o(}OL7IEeobw9_Fod9z zW7rb<9+h9;7SsA;pLbb8g93u&-?WOy^BP@)o#?Z`0 zzBLkRt|+H6s}Tb**5_ht1g|C;p84Ko8B zuhGLn9m<&+*uvCoN7X099{XOG2l6Ra5;!HnQmRNqO_&nX&CqTnb1jv6E}81HL2!37 z0e`C@HBPC_sH&->cE-ZUV!#{ftxXhK6ao-M@F2tEuYTz2HwHmm*@q13?EQH4<2h|l zpFMs4q#u<_U)T0buD|Wg&+b3ZkdqIvnAt(kwr3FAeR$d|K>dO}9)VDleuE&a0+Za* z53;R35AxLhZ>Y_Wf%miTd(X$>9dCbDbm3#{_v!bbZ$H=ZwKmrCdbr=;XP)Z-t#kO; z<1e!B{GQL+{+tdg+#Q5G)Rz_7lce++Jd^EfqBg2J#$VJSZWLC!HB63ihE~0-v9$@A z^H+lR>-`}0qoz0BAN`*dRzgNJUi)MT?eq}~mQsf<{y)sCFiq36fDe?$`3w_0=+Tj= zL=(r%BIf7{JCW#0g($E;sl;K33)P*Q^3u#)1q;!nm%@69)OZuHdQDQD5r==}Lx~|= zG?qCco(K5I=r7bjQzuXbZlV82k)dVmZLEAnQWm8EA9sPA_Mn>^F~EbQMDZ~qT#|4(qOMGW^jh#c2$_}lA${+~U?@ot;?p^KFi==|VcRZNDiG T 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℘KR6W@%RWH626TfFT$x~jq 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%0DUtME!~oNTk;9n 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 M1-! literal 0 HcmV?d00001 diff --git a/apps/main/static/images/avatar.png b/apps/main/static/images/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..09892098aa943af5fe95e7dd434a07fe03e7ccd1 GIT binary patch literal 2011 zcmV<12PF83P)D!y%g4vZ*x1<2%*?Q`u)x5;+S=OH)zz}HvdGBD zw6wIs!oteR%FWHqrlzLJ$;r^r(7(UGt*x!9s;a}o!>FjJ#>U3Iy}i%R&$YF+x3{;h zuCCtR-nO>3udlDMv9Z$9(zv*|ySuyA*4EqG+ZlWNivR!y*-1n}RCr$O+*gj>NDPM2 zpNco<96RTn9rwSi8F&oCZFlz#Qq{xgJ4+x4LZrx&UjP6A00000000000000000000 z0000000000h)13Iv~gIg9X6)(&Ma2$kko6>k^LchuGJIe1?9VT%ih&$-<6da;(GtY zRygU`V`YKSGPJdX%aOK)>O)}bd#Gv~*uA$6-S28s*y-CA`a9YL;;WKvrF0c*ZKyul z)*h=`7wH^W2Elqsjqd}?Xz*Ptp%PkVVMQxo5?Bs`i9Upe<)onxU}?Eo>Ro7Cj@nv* ze0H1D=vvOYdIRQ`ySe^?YRPg}s_G9&F3o|+E;KJf72K|Qva4ddGB4O%8ki>o6?HI`i*6#+w#5|Y?<y|LwN+|wJ-DOs*c9lZ(lz;YDS^$r~880p|x z??Sy~IVsik0el`?F2Y>=KFtcZ1R zaGELyc&axa?H!NJ`jhg5v~%-y@6vvAx%c&^lgbGsy=v!Q)n20Y000000000000000 z0000001(G=0_9$3(l{U9f4Ydqf&CDSql-`X!}G?Z(<>_@q?M!EmqlnRgo`h=qe`mH zp^F!L#dKMP=0-j66q)X!m2_UKU< z?=gcl`PrjD-Z_UQ!Oo*l-Y$R@ac4on7M&O-j9L_~HG+QG8x@#l(9NwQst~Om6uTU# z#^D0B?jkA@-9e`x4^-yi2NWuOs^gbXXX{ZRZwp;qmr8Y=vc4+;?DuSiV=6Wdp+vB! zYWn~xgnKHt51~MOqTGjPD@PoaW3S(TV1qAdpLGPU;{}n+CNZ&z1&l zAk)yBhI<>K$Szko#25&!NK0f~qZEphQ8{ z3aIf)#a^Mvnrf|~$}^REhB95M)P*|TX3iCPq&kn#$aR|+5*6uFl|Ix;s7M0Ewp3#a z)kYpw@J3LsOsPOxLcOd<@p>5y2p=fiLkJUGpQ800tayqjR`dizEyTFw96Gb|` z!YX_?aV~Q~zz%VD}8+~-=U|TkA>Od+oZ}G}nbJRjS zi+DxREJj@nPrRDb5Owl07n$q)ia(q8)f$NXg*KhY?6EB-U9U5WqIxO65S z|C%@HT<{BxaK1b6FYI^o5WlAp7RhA6Kem`8MTkGt$mX+;7JpOgV>ZtqfL8IK*!Dm6 zzQ>>P{QXCNTMXj=+Nnb$0D%|)f;hy>mx(1hlm3f?BA8dZ4V=5bdfd6DDtbSk-BC3w tPksUb00000000000000000000fEW70#Mag`cL)Fg002ovPDHLkV1gNB=am2e literal 0 HcmV?d00001 diff --git a/apps/main/static/robots.txt b/apps/main/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/apps/main/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/apps/main/svelte.config.js b/apps/main/svelte.config.js new file mode 100644 index 0000000..2afacd0 --- /dev/null +++ b/apps/main/svelte.config.js @@ -0,0 +1,20 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; +import adapter from "@sveltejs/adapter-node"; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter({ out: "build" }), + experimental: { + remoteFunctions: true, + tracing: { server: true }, + instrumentation: { server: true }, + }, + }, + compilerOptions: { + experimental: { async: true }, + }, +}; + +export default config; diff --git a/apps/main/tsconfig.json b/apps/main/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/apps/main/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/apps/main/vite.config.ts b/apps/main/vite.config.ts new file mode 100644 index 0000000..1f01308 --- /dev/null +++ b/apps/main/vite.config.ts @@ -0,0 +1,46 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vitest/config"; +import tailwindcss from "@tailwindcss/vite"; +import Icons from "unplugin-icons/vite"; +import { resolve } from "path"; + +export default defineConfig({ + plugins: [sveltekit(), tailwindcss(), Icons({ compiler: "svelte" })], + + ssr: { + external: [ + "argon2", + "node-gyp-build", + "sharp", + "@img/sharp-linux-x64", + "@img/sharp-linuxmusl-x64", + "@img/sharp-wasm32", + ], + }, + + resolve: { + alias: { + "@core": resolve(__dirname, "../../packages/logic/core"), + "@domains": resolve(__dirname, "../../packages/logic/domains"), + "@/core": resolve(__dirname, "../../packages/logic/core"), + "@/domains": resolve(__dirname, "../../packages/logic/domains"), + }, + }, + + test: { + expect: { requireAssertions: true }, + + projects: [ + { + extends: "./vite.config.ts", + + test: { + name: "server", + environment: "node", + include: ["src/**/*.{test,spec}.{js,ts}"], + exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"], + }, + }, + ], + }, +}); diff --git a/apps/orchestrator/package.json b/apps/orchestrator/package.json new file mode 100644 index 0000000..5b0eda8 --- /dev/null +++ b/apps/orchestrator/package.json @@ -0,0 +1,33 @@ +{ + "name": "@apps/orchestrator", + "type": "module", + "scripts": { + "dev": "PORT=3000 tsx watch src/index.ts", + "build": "tsc", + "prod": "HOST=0.0.0.0 PORT=3000 tsx src/index.ts" + }, + "dependencies": { + "@hono/node-server": "^1.19.9", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/auto-instrumentations-node": "^0.70.1", + "@opentelemetry/exporter-logs-otlp-proto": "^0.212.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.212.0", + "@opentelemetry/sdk-logs": "^0.212.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-node": "^0.212.0", + "@pkg/db": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/logic": "workspace:*", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "hono": "^4.12.8", + "import-in-the-middle": "^3.0.0", + "valibot": "^1.2.0" + }, + "devDependencies": { + "@types/node": "^25.3.2", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } +} diff --git a/apps/orchestrator/src/index.ts b/apps/orchestrator/src/index.ts new file mode 100644 index 0000000..2777468 --- /dev/null +++ b/apps/orchestrator/src/index.ts @@ -0,0 +1,29 @@ +import "./instrumentation.js"; + +import { createHttpTelemetryMiddleware } from "@pkg/logic/core/http.telemetry"; +import { serve } from "@hono/node-server"; +import { Hono } from "hono"; + +const app = new Hono().use("*", createHttpTelemetryMiddleware("orchestrator")); + +const host = process.env.HOST || "0.0.0.0"; +const port = Number(process.env.PORT || "3000"); + +app.get("/health", (c) => { + return c.json({ ok: true }); +}); + +app.get("/ping", (c) => { + return c.text("pong"); +}); + +serve( + { + fetch: app.fetch, + port, + hostname: host, + }, + (info) => { + console.log(`Server is running on http://${host}:${info.port}`); + }, +); diff --git a/apps/orchestrator/src/instrumentation.ts b/apps/orchestrator/src/instrumentation.ts new file mode 100644 index 0000000..6b3ec48 --- /dev/null +++ b/apps/orchestrator/src/instrumentation.ts @@ -0,0 +1,50 @@ +import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; +import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"; +import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; +import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto"; +import { createAddHookMessageChannel } from "import-in-the-middle"; +import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { settings } from "@pkg/settings"; +import { register } from "node:module"; + +const { registerOptions } = createAddHookMessageChannel(); +register("import-in-the-middle/hook.mjs", import.meta.url, registerOptions); + +const normalizedEndpoint = settings.otelExporterOtlpHttpEndpoint.startsWith( + "http", +) + ? settings.otelExporterOtlpHttpEndpoint + : `http://${settings.otelExporterOtlpHttpEndpoint}`; + +if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) { + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = normalizedEndpoint; +} + +const sdk = new NodeSDK({ + serviceName: `${settings.otelServiceName}-processor`, + traceExporter: new OTLPTraceExporter(), + metricReader: new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter(), + exportIntervalMillis: 10_000, + }), + logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())], + instrumentations: [ + getNodeAutoInstrumentations({ + "@opentelemetry/instrumentation-winston": { + // We add OpenTelemetryTransportV3 explicitly in @pkg/logger. + disableLogSending: true, + }, + }), + ], +}); + +sdk.start(); + +const shutdown = () => { + void sdk.shutdown(); +}; + +process.on("SIGTERM", shutdown); +process.on("SIGINT", shutdown); diff --git a/apps/orchestrator/tsconfig.json b/apps/orchestrator/tsconfig.json new file mode 100644 index 0000000..fce7dfe --- /dev/null +++ b/apps/orchestrator/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "verbatimModuleSyntax": false, + "skipLibCheck": true, + "types": [ + "node" + ], + "baseUrl": ".", + "paths": { + "@pkg/logic": ["../../packages/logic"], + "@pkg/logic/*": ["../../packages/logic/*"], + "@pkg/db": ["../../packages/db"], + "@pkg/db/*": ["../../packages/db/*"], + "@pkg/logger": ["../../packages/logger"], + "@pkg/logger/*": ["../../packages/logger/*"], + "@pkg/result": ["../../packages/result"], + "@pkg/result/*": ["../../packages/result/*"], + "@pkg/settings": ["../../packages/settings"], + "@pkg/settings/*": ["../../packages/settings/*"], + "@/*": ["../../packages/logic/*"], + "@core/*": ["../../packages/logic/core/*"], + "@domains/*": ["../../packages/logic/domains/*"] + }, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + "outDir": "./dist" + }, + "exclude": ["node_modules"] +} diff --git a/dev/README.md b/dev/README.md new file mode 100644 index 0000000..23b0c33 --- /dev/null +++ b/dev/README.md @@ -0,0 +1,42 @@ +# Dev + +Self-contained local development stack. Spin up all shared infrastructure on a per-project basis. + +## Services + +| Service | Description | Port(s) | +| ------------------ | ---------------------------------------- | -------------- | +| **PostgreSQL** | Primary relational database | `5432` | +| **Valkey** | Redis-compatible cache / message broker | `6379` | +| **SigNoz** | Observability UI (traces, metrics, logs) | `8080` | +| **OTel Collector** | OpenTelemetry ingest (gRPC / HTTP) | `4317`, `4318` | +| **ClickHouse** | Telemetry storage backend for SigNoz | — | + +## Run + +```sh +cd dev +docker compose -f docker-compose.dev.yaml up -d +``` + +## Stop + +```sh +docker compose -f docker-compose.dev.yaml down +``` + +To also remove all persisted data volumes: + +```sh +docker compose -f docker-compose.dev.yaml down -v +``` + +## Connection strings + +| Resource | Default value | +| ---------- | --------------------------------------------------------- | +| PostgreSQL | `postgresql://postgres:postgres@localhost:5432/primarydb` | +| Valkey | `redis://localhost:6379` | +| SigNoz UI | `http://localhost:8080` | +| OTLP gRPC | `localhost:4317` | +| OTLP HTTP | `localhost:4318` | diff --git a/dev/clickhouse-cluster.xml b/dev/clickhouse-cluster.xml new file mode 100644 index 0000000..8b475ff --- /dev/null +++ b/dev/clickhouse-cluster.xml @@ -0,0 +1,75 @@ + + + + + + zookeeper-1 + 2181 + + + + + + + + + + + + + + + + clickhouse + 9000 + + + + + + + + diff --git a/dev/clickhouse-config.xml b/dev/clickhouse-config.xml new file mode 100644 index 0000000..1965ac3 --- /dev/null +++ b/dev/clickhouse-config.xml @@ -0,0 +1,1142 @@ + + + + + + information + + json + + /var/log/clickhouse-server/clickhouse-server.log + /var/log/clickhouse-server/clickhouse-server.err.log + + 1000M + 10 + + + + + + + + + + + + + + + + + + 8123 + + + 9000 + + + 9004 + + + 9005 + + + + + + + + + + + + 9009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4096 + + + 3 + + + + + false + + + /path/to/ssl_cert_file + /path/to/ssl_key_file + + + false + + + /path/to/ssl_ca_cert_file + + + none + + + 0 + + + -1 + -1 + + + false + + + + + + + + + + + none + true + true + sslv2,sslv3 + true + + + + true + true + sslv2,sslv3 + true + + + + RejectCertificateHandler + + + + + + + + + 100 + + + 0 + + + + 10000 + + + + + + 0.9 + + + 4194304 + + + 0 + + + + + + 8589934592 + + + 5368709120 + + + + 1000 + + + 134217728 + + + 10000 + + + /var/lib/clickhouse/ + + + /var/lib/clickhouse/tmp/ + + + + ` + + + + + + /var/lib/clickhouse/user_files/ + + + + + + + + + + + + + users.xml + + + + /var/lib/clickhouse/access/ + + + + + + + default + + + + + + + + + + + + default + + + + + + + + + true + + + false + + ' | sed -e 's|.*>\(.*\)<.*|\1|') + wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge_$PKG_VER-1_all.deb + apt install --no-install-recommends -f ./clickhouse-jdbc-bridge_$PKG_VER-1_all.deb + clickhouse-jdbc-bridge & + + * [CentOS/RHEL] + export MVN_URL=https://repo1.maven.org/maven2/ru/yandex/clickhouse/clickhouse-jdbc-bridge + export PKG_VER=$(curl -sL $MVN_URL/maven-metadata.xml | grep '' | sed -e 's|.*>\(.*\)<.*|\1|') + wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm + yum localinstall -y clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm + clickhouse-jdbc-bridge & + + Please refer to https://github.com/ClickHouse/clickhouse-jdbc-bridge#usage for more information. + ]]> + + + + + + + + + + + + + + + 01 + example01-01-1 + + + + + + 3600 + + + + 3600 + + + 60 + + + + + + + + + + /metrics + 9363 + + true + true + true + true + + + + + + system + query_log
    + + toYYYYMM(event_date) + + + + + + 7500 +
    + + + + system + trace_log
    + + toYYYYMM(event_date) + 7500 +
    + + + + system + query_thread_log
    + toYYYYMM(event_date) + 7500 +
    + + + + system + query_views_log
    + toYYYYMM(event_date) + 7500 +
    + + + + system + part_log
    + toYYYYMM(event_date) + 7500 +
    + + + + + + system + metric_log
    + 7500 + 1000 +
    + + + + system + asynchronous_metric_log
    + + 7000 +
    + + + + + + engine MergeTree + partition by toYYYYMM(finish_date) + order by (finish_date, finish_time_us, trace_id) + + system + opentelemetry_span_log
    + 7500 +
    + + + + + system + crash_log
    + + + 1000 +
    + + + + + + + system + processors_profile_log
    + + toYYYYMM(event_date) + 7500 +
    + + + + + + + + + *_dictionary.xml + + + *function.xml + /var/lib/clickhouse/user_scripts/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /clickhouse/task_queue/ddl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + click_cost + any + + 0 + 3600 + + + 86400 + 60 + + + + max + + 0 + 60 + + + 3600 + 300 + + + 86400 + 3600 + + + + + + /var/lib/clickhouse/format_schemas/ + + + + + hide encrypt/decrypt arguments + ((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\) + + \1(???) + + + + + + + + + + false + + false + + + https://6f33034cfe684dd7a3ab9875e57b1c8d@o388870.ingest.sentry.io/5226277 + + + + + + + + + + + 268435456 + true + +
    diff --git a/dev/clickhouse-custom-function.xml b/dev/clickhouse-custom-function.xml new file mode 100644 index 0000000..b2b3f91 --- /dev/null +++ b/dev/clickhouse-custom-function.xml @@ -0,0 +1,21 @@ + + + executable + histogramQuantile + Float64 + + Array(Float64) + buckets + + + Array(Float64) + counts + + + Float64 + quantile + + CSV + ./histogramQuantile + + diff --git a/dev/clickhouse-users.xml b/dev/clickhouse-users.xml new file mode 100644 index 0000000..f185620 --- /dev/null +++ b/dev/clickhouse-users.xml @@ -0,0 +1,123 @@ + + + + + + + + + + 10000000000 + + + random + + + + + 1 + + + + + + + + + + + + + ::/0 + + + + default + + + default + + + + + + + + + + + + + + 3600 + + + 0 + 0 + 0 + 0 + 0 + + + + diff --git a/dev/docker-compose.dev.yaml b/dev/docker-compose.dev.yaml new file mode 100644 index 0000000..1c69f76 --- /dev/null +++ b/dev/docker-compose.dev.yaml @@ -0,0 +1,212 @@ +x-common: &common + networks: + - signoz-net + restart: unless-stopped + logging: + options: + max-size: 50m + max-file: "3" + +x-clickhouse-defaults: &clickhouse-defaults + !!merge <<: *common + image: clickhouse/clickhouse-server:25.5.6 + tty: true + labels: + signoz.io/scrape: "true" + signoz.io/port: "9363" + signoz.io/path: "/metrics" + depends_on: + init-clickhouse: + condition: service_completed_successfully + zookeeper-1: + condition: service_healthy + healthcheck: + test: + - CMD + - wget + - --spider + - -q + - 0.0.0.0:8123/ping + interval: 30s + timeout: 5s + retries: 3 + ulimits: + nproc: 65535 + nofile: + soft: 262144 + hard: 262144 + environment: + - CLICKHOUSE_SKIP_USER_SETUP=1 +x-zookeeper-defaults: &zookeeper-defaults + !!merge <<: *common + image: signoz/zookeeper:3.7.1 + user: root + labels: + signoz.io/scrape: "true" + signoz.io/port: "9141" + signoz.io/path: "/metrics" + healthcheck: + test: + - CMD-SHELL + - curl -s -m 2 http://localhost:8080/commands/ruok | grep error | grep null + interval: 30s + timeout: 5s + retries: 3 +x-db-depend: &db-depend + !!merge <<: *common + depends_on: + clickhouse: + condition: service_healthy +# ====== +# Main +# ====== +services: + valkey: + restart: always + image: valkey/valkey:9.0.3 + networks: + - signoz-net + ports: + - 6379:6379 + volumes: + - iotam_dev_valkey_data:/data + postgresql: + restart: always + image: postgres:18.3 + networks: + - signoz-net + ports: + - 5432:5432 + environment: + POSTGRES_DB: primarydb + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - iotam_dev_postgresql_data:/var/lib/postgresql + + init-clickhouse: + !!merge <<: *common + image: clickhouse/clickhouse-server:25.5.6 + container_name: signoz-init-clickhouse + command: + - bash + - -c + - | + version="v0.0.1" + node_os=$$(uname -s | tr '[:upper:]' '[:lower:]') + node_arch=$$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) + echo "Fetching histogram-binary for $${node_os}/$${node_arch}" + cd /tmp + wget -O histogram-quantile.tar.gz "https://github.com/SigNoz/signoz/releases/download/histogram-quantile%2F$${version}/histogram-quantile_$${node_os}_$${node_arch}.tar.gz" + tar -xvzf histogram-quantile.tar.gz + mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile + restart: on-failure + volumes: + - iotam_clickhouse_user_scripts:/var/lib/clickhouse/user_scripts/ + zookeeper-1: + !!merge <<: *zookeeper-defaults + container_name: signoz-zookeeper-1 + # ports: + # - "2181:2181" + # - "2888:2888" + # - "3888:3888" + volumes: + - iotam_zookeeper_1:/bitnami/zookeeper + environment: + - ZOO_SERVER_ID=1 + - ALLOW_ANONYMOUS_LOGIN=yes + - ZOO_AUTOPURGE_INTERVAL=1 + - ZOO_ENABLE_PROMETHEUS_METRICS=yes + - ZOO_PROMETHEUS_METRICS_PORT_NUMBER=9141 + clickhouse: + !!merge <<: *clickhouse-defaults + container_name: signoz-clickhouse + # ports: + # - "9000:9000" + # - "8123:8123" + # - "9181:9181" + volumes: + - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml + - ./clickhouse-users.xml:/etc/clickhouse-server/users.xml + - ./clickhouse-custom-function.xml:/etc/clickhouse-server/custom-function.xml + - iotam_clickhouse_user_scripts:/var/lib/clickhouse/user_scripts/ + - ./clickhouse-cluster.xml:/etc/clickhouse-server/config.d/cluster.xml + - iotam_clickhouse:/var/lib/clickhouse/ + # - ./clickhouse-storage.xml:/etc/clickhouse-server/config.d/storage.xml + signoz: + !!merge <<: *db-depend + image: signoz/signoz:${VERSION:-v0.113.0} + container_name: signoz + ports: + - "8080:8080" # signoz port + volumes: + - iotam_sqlite:/var/lib/signoz/ + environment: + - SIGNOZ_ALERTMANAGER_PROVIDER=signoz + - SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN=tcp://clickhouse:9000 + - SIGNOZ_SQLSTORE_SQLITE_PATH=/var/lib/signoz/signoz.db + - SIGNOZ_TOKENIZER_JWT_SECRET=secret + healthcheck: + test: + - CMD + - wget + - --spider + - -q + - localhost:8080/api/v1/health + interval: 30s + timeout: 5s + retries: 3 + otel-collector: + !!merge <<: *db-depend + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1} + container_name: signoz-otel-collector + entrypoint: + - /bin/sh + command: + - -c + - | + /signoz-otel-collector migrate sync check && + /signoz-otel-collector --config=/etc/otel-collector-config.yaml --manager-config=/etc/manager-config.yaml --copy-path=/var/tmp/collector-config.yaml + volumes: + - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml + - ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml + environment: + - OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux + - LOW_CARDINAL_EXCEPTION_GROUPING=false + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000 + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_REPLICATION=true + - SIGNOZ_OTEL_COLLECTOR_TIMEOUT=10m + ports: + # - "1777:1777" # pprof extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP HTTP receiver + signoz-telemetrystore-migrator: + !!merge <<: *db-depend + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1} + container_name: signoz-telemetrystore-migrator + environment: + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000 + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_REPLICATION=true + - SIGNOZ_OTEL_COLLECTOR_TIMEOUT=10m + entrypoint: + - /bin/sh + command: + - -c + - | + /signoz-otel-collector migrate bootstrap && + /signoz-otel-collector migrate sync up && + /signoz-otel-collector migrate async up + restart: on-failure +# Peripherals +networks: + signoz-net: + name: signoz-net +volumes: + iotam_dev_valkey_data: + iotam_dev_postgresql_data: + iotam_clickhouse: + iotam_clickhouse_user_scripts: + iotam_sqlite: + iotam_zookeeper_1: diff --git a/dev/otel-collector-config.yaml b/dev/otel-collector-config.yaml new file mode 100644 index 0000000..88d395b --- /dev/null +++ b/dev/otel-collector-config.yaml @@ -0,0 +1,124 @@ +connectors: + signozmeter: + metrics_flush_interval: 1h + dimensions: + - name: service.name + - name: deployment.environment + - name: host.name +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + send_batch_size: 10000 + send_batch_max_size: 11000 + timeout: 10s + batch/meter: + send_batch_max_size: 25000 + send_batch_size: 20000 + timeout: 1s + resourcedetection: + # Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels. + detectors: [env, system] + timeout: 2s + signozspanmetrics/delta: + metrics_exporter: signozclickhousemetrics + metrics_flush_interval: 60s + latency_histogram_buckets: + [ + 100us, + 1ms, + 2ms, + 6ms, + 10ms, + 50ms, + 100ms, + 250ms, + 500ms, + 1000ms, + 1400ms, + 2000ms, + 5s, + 10s, + 20s, + 40s, + 60s, + ] + dimensions_cache_size: 100000 + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + enable_exp_histogram: true + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default + # This is added to ensure the uniqueness of the timeseries + # Otherwise, identical timeseries produced by multiple replicas of + # collectors result in incorrect APM metrics + - name: signoz.collector.id + - name: service.version + - name: browser.platform + - name: browser.mobile + - name: k8s.cluster.name + - name: k8s.node.name + - name: k8s.namespace.name + - name: host.name + - name: host.type + - name: container.name +extensions: + health_check: + endpoint: 0.0.0.0:13133 + pprof: + endpoint: 0.0.0.0:1777 +exporters: + clickhousetraces: + datasource: tcp://clickhouse:9000/signoz_traces + low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING} + use_new_schema: true + signozclickhousemetrics: + dsn: tcp://clickhouse:9000/signoz_metrics + clickhouselogsexporter: + dsn: tcp://clickhouse:9000/signoz_logs + timeout: 10s + use_new_schema: true + signozclickhousemeter: + dsn: tcp://clickhouse:9000/signoz_meter + timeout: 45s + sending_queue: + enabled: false + metadataexporter: + cache: + provider: in_memory + dsn: tcp://clickhouse:9000/signoz_metadata + enabled: true + timeout: 45s +service: + telemetry: + logs: + encoding: json + extensions: + - health_check + - pprof + pipelines: + traces: + receivers: [otlp] + processors: [signozspanmetrics/delta, batch] + exporters: [clickhousetraces, metadataexporter, signozmeter] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [signozclickhousemetrics, metadataexporter, signozmeter] + + logs: + receivers: [otlp] + processors: [batch] + exporters: [clickhouselogsexporter, metadataexporter, signozmeter] + metrics/meter: + receivers: [signozmeter] + processors: [batch/meter] + exporters: [signozclickhousemeter] diff --git a/dev/otel-collector-opamp-config.yaml b/dev/otel-collector-opamp-config.yaml new file mode 100644 index 0000000..7267607 --- /dev/null +++ b/dev/otel-collector-opamp-config.yaml @@ -0,0 +1 @@ +server_endpoint: ws://signoz:4320/v1/opamp diff --git a/dockerfiles/app_builder.Dockerfile b/dockerfiles/app_builder.Dockerfile new file mode 100644 index 0000000..96defbc --- /dev/null +++ b/dockerfiles/app_builder.Dockerfile @@ -0,0 +1,47 @@ +FROM node:25.6.1-bookworm-slim AS app-builder + +ARG ANDROID_SDK_ROOT=/opt/android-sdk +ARG ANDROID_CMDLINE_TOOLS_VERSION=13114758 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash \ + ca-certificates \ + curl \ + git \ + openjdk-17-jdk \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +ENV ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT} +ENV ANDROID_HOME=${ANDROID_SDK_ROOT} +ENV PATH=${JAVA_HOME}/bin:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/platform-tools:${PATH} + +RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools /tmp/android-sdk && \ + curl -fsSL -o /tmp/android-sdk/cmdline-tools.zip \ + https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip && \ + unzip -q /tmp/android-sdk/cmdline-tools.zip -d /tmp/android-sdk && \ + mv /tmp/android-sdk/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest && \ + yes | sdkmanager --licenses > /dev/null && \ + sdkmanager \ + "platform-tools" \ + "platforms;android-36" \ + "build-tools;36.0.0" && \ + rm -rf /tmp/android-sdk + +RUN npm i -g pnpm + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./ +COPY apps/app-builder/package.json ./apps/app-builder/package.json +COPY packages ./packages + +RUN pnpm install --frozen-lockfile + +COPY apps/app-builder ./apps/app-builder +COPY mobile ./mobile + +EXPOSE 3000 + +CMD ["pnpm", "--filter", "@apps/app-builder", "run", "prod"] diff --git a/dockerfiles/main.Dockerfile b/dockerfiles/main.Dockerfile new file mode 100644 index 0000000..41eb13d --- /dev/null +++ b/dockerfiles/main.Dockerfile @@ -0,0 +1,27 @@ +FROM node:25.6.1 AS production + +RUN npm i -g pnpm + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./ + +COPY apps/main/package.json ./apps/main/package.json + +COPY packages ./packages + +RUN pnpm install + +COPY apps/main ./apps/main + +RUN pnpm install + +RUN pnpm run build + +COPY scripts ./scripts + +EXPOSE 3000 + +RUN chmod +x scripts/prod.start.sh + +CMD ["/bin/sh", "scripts/prod.start.sh", "apps/main"] diff --git a/dockerfiles/migrator.Dockerfile b/dockerfiles/migrator.Dockerfile new file mode 100644 index 0000000..f7d7b23 --- /dev/null +++ b/dockerfiles/migrator.Dockerfile @@ -0,0 +1,18 @@ +FROM node:25.6.1 AS base + +RUN npm i -g pnpm + +FROM base AS primary + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./ + +COPY packages/settings packages/settings +COPY packages/db packages/db + +RUN pnpm install + +COPY scripts scripts + +CMD ["/bin/sh", "/app/scripts/migrate.sh"] diff --git a/dockerfiles/processor.Dockerfile b/dockerfiles/processor.Dockerfile new file mode 100644 index 0000000..97f6d63 --- /dev/null +++ b/dockerfiles/processor.Dockerfile @@ -0,0 +1,21 @@ +FROM node:25.6.1-alpine AS deps + +RUN npm i -g pnpm + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./ + +COPY apps/processor/package.json ./apps/processor/package.json + +COPY packages ./packages + +RUN pnpm install --frozen-lockfile + +COPY apps/processor ./apps/processor + +RUN pnpm install + +EXPOSE 3000 + +CMD ["pnpm", "--filter", "@apps/processor", "run", "prod"] diff --git a/package.json b/package.json new file mode 100644 index 0000000..b4000fa --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "@illusory/mapp", + "version": "1.0.0", + "private": true, + "engines": { + "node": ">=24" + }, + "packageManager": "pnpm@10.30.3", + "scripts": { + "build": "turbo build", + "dev": "turbo dev --concurrency=8", + "auth:schemagen": "source .env && cd packages/logic && pnpm run auth:schemagen", + "db:migrate": "./scripts/migrate.sh", + "prod": "turbo prod", + "test": "turbo test" + }, + "devDependencies": { + "prettier": "^3.8.1", + "prettier-plugin-sort-imports": "^1.8.11", + "prettier-plugin-svelte": "^3.5.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "turbo": "^2.8.16", + "typescript": "^5.9.3" + }, + "prettier": { + "arrowParens": "always", + "singleQuote": false, + "jsxSingleQuote": false, + "semi": true, + "trailingComma": "all", + "tabWidth": 4, + "plugins": [ + "prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss", + "prettier-plugin-svelte" + ] + } +} diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts new file mode 100644 index 0000000..98dd550 --- /dev/null +++ b/packages/db/drizzle.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "drizzle-kit"; +import { settings } from "@pkg/settings"; + +export default defineConfig({ + schema: "./schema", + out: "./migrations", + dialect: "postgresql", + verbose: true, + strict: false, + dbCredentials: { + url: settings.databaseUrl, + }, +}); diff --git a/packages/db/index.ts b/packages/db/index.ts new file mode 100644 index 0000000..7bf9e59 --- /dev/null +++ b/packages/db/index.ts @@ -0,0 +1,11 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import { settings } from "@pkg/settings"; +import * as schema from "./schema"; + +const db = drizzle(settings.databaseUrl, { schema }); + +export type Database = typeof db; + +export * from "drizzle-orm"; + +export { db, schema }; diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 0000000..3f6ba23 --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,27 @@ +{ + "name": "@pkg/db", + "module": "index.ts", + "type": "module", + "scripts": { + "db:gen": "drizzle-kit generate --config=drizzle.config.ts", + "db:drop": "drizzle-kit drop --config=drizzle.config.ts", + "db:push": "drizzle-kit push --config=drizzle.config.ts", + "db:migrate": "drizzle-kit generate --config=drizzle.config.ts && drizzle-kit push --config=drizzle.config.ts", + "db:forcemigrate": "drizzle-kit generate --config=drizzle.config.ts && drizzle-kit push --config=drizzle.config.ts --force", + "dev": "drizzle-kit studio --config=drizzle.config.ts --verbose" + }, + "dependencies": { + "@pkg/settings": "workspace:*", + "dotenv": "^16.4.7", + "drizzle-orm": "^0.45.1", + "postgres": "^3.4.8" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/pg": "^8.11.10", + "drizzle-kit": "^0.31.9" + }, + "peerDependencies": { + "typescript": "^5.9.3" + } +} diff --git a/packages/db/schema/auth.schema.ts b/packages/db/schema/auth.schema.ts new file mode 100644 index 0000000..0001481 --- /dev/null +++ b/packages/db/schema/auth.schema.ts @@ -0,0 +1,52 @@ +import { + integer, + json, + pgTable, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { user } from "./better.auth.schema"; +import { relations } from "drizzle-orm"; + +export const twoFactor = pgTable("two_factor", { + id: text("id").primaryKey(), + secret: text("secret").notNull(), + backupCodes: json("backup_codes").$type(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const twofaSessions = pgTable("twofa_sessions", { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + sessionId: text("session_id").notNull(), // Better Auth session ID + + // Verification Tracking + verificationToken: text("verification_token").notNull().unique(), // Unique nonce for this attempt + codeUsed: text("code_used"), // The TOTP code submitted (prevent replay) + status: varchar("status", { length: 16 }).notNull(), // "pending" | "verified" | "failed" | "expired" + + attempts: integer("attempts").default(0).notNull(), + maxAttempts: integer("max_attempts").default(5).notNull(), + + verifiedAt: timestamp("verified_at"), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").notNull(), + + // Security Audit + ipAddress: text("ip_address").default(""), + userAgent: text("user_agent").default(""), +}); + +export const twofaSessionsRelations = relations(twofaSessions, ({ one }) => ({ + userAccount: one(user, { + fields: [twofaSessions.userId], + references: [user.id], + }), +})); diff --git a/packages/db/schema/better.auth.schema.ts b/packages/db/schema/better.auth.schema.ts new file mode 100644 index 0000000..d162dc1 --- /dev/null +++ b/packages/db/schema/better.auth.schema.ts @@ -0,0 +1,75 @@ +import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core"; +import { relations } from "drizzle-orm"; + +export const user = pgTable("user", { + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").default(false).notNull(), + image: text("image"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + username: text("username").unique(), + displayUsername: text("display_username"), + role: text("role"), + banned: boolean("banned").default(false), + banReason: text("ban_reason"), + banExpires: timestamp("ban_expires"), + onboardingDone: boolean("onboarding_done").default(false), + last2FAVerifiedAt: timestamp("last2_fa_verified_at"), + parentId: text("parent_id"), +}); + +export const account = pgTable( + "account", + { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("account_userId_idx").on(table.userId)], +); + +export const verification = pgTable( + "verification", + { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("verification_identifier_idx").on(table.identifier)], +); + +export const userRelations = relations(user, ({ many }) => ({ + accounts: many(account), +})); + +export const accountRelations = relations(account, ({ one }) => ({ + user: one(user, { + fields: [account.userId], + references: [user.id], + }), +})); diff --git a/packages/db/schema/general.schema.ts b/packages/db/schema/general.schema.ts new file mode 100644 index 0000000..b8cf6ab --- /dev/null +++ b/packages/db/schema/general.schema.ts @@ -0,0 +1,49 @@ +import { + boolean, + json, + pgTable, + serial, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { user } from "./better.auth.schema"; +import { relations } from "drizzle-orm"; + +export const notifications = pgTable("notifications", { + id: serial("id").primaryKey(), + + title: text("title").notNull(), + body: text("body").notNull(), + priority: varchar("priority", { length: 12 }).default("normal").notNull(), // "low", "normal", "high", "urgent" + + type: varchar("type", { length: 12 }).notNull(), + category: varchar("category", { length: 64 }), + + isRead: boolean("is_read").default(false).notNull(), + isArchived: boolean("is_archived").default(false).notNull(), + + actionUrl: text("action_url"), // URL to navigate to when clicked + actionType: varchar("action_type", { length: 16 }), // Type of action ("link", "function", etc.) + actionData: json("action_data"), // Any additional data for the action + + icon: varchar("icon", { length: 64 }), // Optional icon identifier + + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + + // Lifecycle management + sentAt: timestamp("sent_at").notNull(), + readAt: timestamp("read_at"), + expiresAt: timestamp("expires_at"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const notificationsRelations = relations(notifications, ({ one }) => ({ + userAccount: one(user, { + fields: [notifications.userId], + references: [user.id], + }), +})); diff --git a/packages/db/schema/index.ts b/packages/db/schema/index.ts new file mode 100644 index 0000000..241f3ee --- /dev/null +++ b/packages/db/schema/index.ts @@ -0,0 +1,4 @@ +export * from "./auth.schema"; +export * from "./better.auth.schema"; +export * from "./general.schema"; +export * from "./task.schema"; diff --git a/packages/db/schema/task.schema.ts b/packages/db/schema/task.schema.ts new file mode 100644 index 0000000..535609f --- /dev/null +++ b/packages/db/schema/task.schema.ts @@ -0,0 +1,35 @@ +import { + integer, + json, + pgTable, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { user } from "./better.auth.schema"; +import { relations } from "drizzle-orm"; + +export const task = pgTable("task", { + id: text("id").primaryKey(), + type: varchar("type", { length: 32 }).notNull(), + status: varchar("status", { length: 16 }).notNull(), + progress: integer("progress").default(0).notNull(), + payload: json("payload").$type>(), + result: json("result").$type>(), + error: json("error").$type>(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + resourceId: text("resource_id").notNull(), + startedAt: timestamp("started_at"), + completedAt: timestamp("completed_at"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const taskRelations = relations(task, ({ one }) => ({ + userAccount: one(user, { + fields: [task.userId], + references: [user.id], + }), +})); diff --git a/packages/keystore/build.queue.ts b/packages/keystore/build.queue.ts new file mode 100644 index 0000000..8cb79c7 --- /dev/null +++ b/packages/keystore/build.queue.ts @@ -0,0 +1,17 @@ +import { getRedisInstance } from "./index"; + +export const APK_BUILD_QUEUE_CHANNEL = "queue:apk-build"; + +export async function publishApkBuildSignal(payload: { + taskId: string; + buildId: string; +}) { + const redis = getRedisInstance(); + await redis.connect().catch(() => undefined); + await redis.publish(APK_BUILD_QUEUE_CHANNEL, JSON.stringify(payload)); +} + +export function getApkBuildSubscriber() { + const subscriber = getRedisInstance().duplicate(); + return subscriber; +} diff --git a/packages/keystore/index.ts b/packages/keystore/index.ts new file mode 100644 index 0000000..ce48f0c --- /dev/null +++ b/packages/keystore/index.ts @@ -0,0 +1,18 @@ +import { Redis } from "ioredis"; +export * from "ioredis"; + +let redis: Redis | undefined; + +let defaultRedisUrl = process.env.REDIS_URL ?? ""; + +export function getRedisInstance(url: string = defaultRedisUrl) { + if (redis) { + return redis; + } + redis = new Redis(url, { + lazyConnect: true, + connectTimeout: 5000, + commandTimeout: 5000, + }); + return redis; +} diff --git a/packages/keystore/package.json b/packages/keystore/package.json new file mode 100644 index 0000000..9299919 --- /dev/null +++ b/packages/keystore/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pkg/keystore", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.9.3" + }, + "dependencies": { + "ioredis": "^5.6.1" + } +} diff --git a/packages/logger/index.ts b/packages/logger/index.ts new file mode 100644 index 0000000..47565fb --- /dev/null +++ b/packages/logger/index.ts @@ -0,0 +1,281 @@ +import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport"; +import { settings } from "@pkg/settings"; +import type { Err } from "@pkg/result"; +import winston from "winston"; +import util from "util"; + +process.on("warning", (warning) => { + const msg = String(warning?.message || ""); + const name = String((warning as any)?.name || ""); + + if ( + name === "TimeoutNegativeWarning" || + msg.includes("TimeoutNegativeWarning") || + msg.includes("Timeout duration was set to 1") + ) { + return; + } + + console.warn(warning); +}); + +const levels = { + error: 0, + warn: 1, + info: 2, + http: 3, + debug: 4, +} as const; + +const colors = { + error: "red", + warn: "yellow", + info: "green", + http: "magenta", + debug: "white", +}; + +const level = () => { + const envLevel = process.env.LOG_LEVEL?.toLowerCase(); + if (envLevel && Object.prototype.hasOwnProperty.call(levels, envLevel)) { + return envLevel as keyof typeof levels; + } + return settings.isDevelopment ? "debug" : "warn"; +}; + +const consoleFormat = winston.format.combine( + winston.format.errors({ stack: true }), + winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }), + winston.format.colorize({ all: true }), + winston.format.printf((info) => { + const { level, message, timestamp } = info; + + const extra = Object.fromEntries( + Object.entries(info).filter( + ([key]) => key !== "level" && key !== "message" && key !== "timestamp", + ), + ); + + const formattedMessage = + message instanceof Error + ? message.stack || message.message + : typeof message === "object" + ? util.inspect(message, { depth: null, colors: true }) + : String(message); + + const formattedExtra = + Object.keys(extra).length > 0 + ? `\n${util.inspect(extra, { depth: null, colors: true })}` + : ""; + + return `[${level}] ${timestamp}: ${formattedMessage}${formattedExtra}`; + }), +); + +winston.addColors(colors); + +const logger = winston.createLogger({ + level: level(), + levels, + format: consoleFormat, + transports: [ + new winston.transports.Console({ format: consoleFormat }), + new OpenTelemetryTransportV3(), + ], + exceptionHandlers: [new winston.transports.Console({ format: consoleFormat })], + rejectionHandlers: [new winston.transports.Console({ format: consoleFormat })], +}); + +const stream = { write: (message: string) => logger.http(message.trim()) }; + +type LogLevel = keyof typeof levels; +type ErrorKind = "validation" | "auth" | "db" | "external" | "unknown"; +type FlowCtxLike = { + flowId: string; + userId?: string; + sessionId?: string; +}; + +const REDACTED_KEYS = new Set([ + "password", + "code", + "secret", + "token", + "verificationtoken", + "backupcodes", + "authorization", + "headers", + "hash", +]); + +function sanitizeMeta(input: Record) { + const sanitized = Object.fromEntries( + Object.entries(input).map(([key, value]) => { + if (value === undefined) { + return [key, undefined]; + } + const lowered = key.toLowerCase(); + if (REDACTED_KEYS.has(lowered)) { + return [key, "[REDACTED]"]; + } + return [key, value]; + }), + ); + + return Object.fromEntries( + Object.entries(sanitized).filter(([, value]) => value !== undefined), + ); +} + +function classifyError(error: unknown): ErrorKind { + if (!error) return "unknown"; + if (typeof error === "object" && error && "code" in error) { + const code = String((error as { code?: unknown }).code ?? "").toUpperCase(); + if (code.includes("AUTH") || code.includes("UNAUTHORIZED")) return "auth"; + if (code.includes("VALIDATION") || code.includes("INVALID")) return "validation"; + if (code.includes("DB") || code.includes("DATABASE")) return "db"; + } + return "unknown"; +} + +function errorMessage(error: unknown) { + if (error instanceof Error) return error.message; + if (typeof error === "string") return error; + if (error && typeof error === "object" && "message" in error) { + return String((error as { message?: unknown }).message ?? "Unknown error"); + } + return "Unknown error"; +} + +function formatErrorDetail(error: unknown): string { + if (error instanceof Error) { + return error.stack || error.message; + } + if (typeof error === "string") { + return error; + } + if (!error) { + return "Unknown error"; + } + if (typeof error === "object") { + const candidate = error as { + code?: unknown; + message?: unknown; + description?: unknown; + detail?: unknown; + error?: unknown; + stack?: unknown; + }; + + const parts = [ + candidate.code ? `code=${String(candidate.code)}` : null, + candidate.message ? `message=${String(candidate.message)}` : null, + candidate.description + ? `description=${String(candidate.description)}` + : null, + candidate.detail + ? `detail=${ + typeof candidate.detail === "string" + ? candidate.detail + : util.inspect(candidate.detail, { + depth: null, + colors: false, + }) + }` + : null, + candidate.error + ? `error=${ + typeof candidate.error === "string" + ? candidate.error + : util.inspect(candidate.error, { + depth: null, + colors: false, + }) + }` + : null, + candidate.stack ? String(candidate.stack) : null, + ].filter(Boolean); + + return parts.length > 0 + ? parts.join(" | ") + : util.inspect(error, { depth: null, colors: false }); + } + + return String(error); +} + +function writeLog(level: LogLevel, message: string, payload: Record) { + switch (level) { + case "error": + logger.error(message, payload); + return; + case "warn": + logger.warn(message, payload); + return; + case "debug": + logger.debug(message, payload); + return; + case "http": + logger.http(message, payload); + return; + default: + logger.info(message, payload); + } +} + +function logDomainEvent({ + level = "info", + event, + fctx, + durationMs, + error, + retryable, + meta, +}: { + level?: LogLevel; + event: string; + fctx: FlowCtxLike; + durationMs?: number; + error?: unknown; + retryable?: boolean; + meta?: Record; +}) { + const payload: Record = { + event, + flowId: fctx.flowId, + userId: fctx.userId, + sessionId: fctx.sessionId, + }; + + if (durationMs !== undefined) payload.duration_ms = durationMs; + if (retryable !== undefined) payload.retryable = retryable; + if (error !== undefined) { + payload.error_kind = classifyError(error); + payload.error_message = errorMessage(error); + if (error && typeof error === "object" && "code" in error) { + payload.error_code = String( + (error as { code?: unknown }).code ?? "UNKNOWN", + ); + } + } + if (meta) { + Object.assign(payload, sanitizeMeta(meta)); + } + + writeLog(level, event, payload); +} + +function getError(payload: Err, error?: any) { + logger.error(JSON.stringify({ payload, error }, null, 2)); + return { + code: payload.code, + message: payload.message, + description: payload.description, + detail: payload.detail, + error: error instanceof Error ? error.message : error, + actionable: payload.actionable, + } as Err; +} + +export { getError, logDomainEvent, logger, stream }; +export { formatErrorDetail }; diff --git a/packages/logger/package.json b/packages/logger/package.json new file mode 100644 index 0000000..b2d4e79 --- /dev/null +++ b/packages/logger/package.json @@ -0,0 +1,17 @@ +{ + "name": "@pkg/logger", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.9.3" + }, + "dependencies": { + "@opentelemetry/winston-transport": "^0.22.0", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "winston": "^3.17.0" + } +} diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json new file mode 100644 index 0000000..0f8d8dd --- /dev/null +++ b/packages/logger/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + }, +} diff --git a/packages/logic/core/array.utils.ts b/packages/logic/core/array.utils.ts new file mode 100644 index 0000000..237c4a5 --- /dev/null +++ b/packages/logic/core/array.utils.ts @@ -0,0 +1,7 @@ +export function chunk(arr: T[], size: number): T[][] { + const result = []; + for (let i = 0; i < arr.length; i += size) { + result.push(arr.slice(i, i + size)); + } + return result; +} diff --git a/packages/logic/core/data/countries.ts b/packages/logic/core/data/countries.ts new file mode 100644 index 0000000..be0a9e0 --- /dev/null +++ b/packages/logic/core/data/countries.ts @@ -0,0 +1,264 @@ +export const COUNTRIES = [ + { id: "1", name: "Afghanistan", code: "AF" }, + { id: "2", name: "Albania", code: "AL" }, + { id: "3", name: "Algeria", code: "DZ" }, + { id: "4", name: "American Samoa", code: "AS" }, + { id: "5", name: "Andorra", code: "AD" }, + { id: "6", name: "Angola", code: "AO" }, + { id: "7", name: "Anguilla", code: "AI" }, + { id: "8", name: "Antarctica", code: "AQ" }, + { id: "9", name: "Antigua and Barbuda", code: "AG" }, + { id: "10", name: "Argentina", code: "AR" }, + { id: "11", name: "Armenia", code: "AM" }, + { id: "12", name: "Aruba", code: "AW" }, + { id: "13", name: "Australia", code: "AU" }, + { id: "14", name: "Austria", code: "AT" }, + { id: "15", name: "Azerbaijan", code: "AZ" }, + { id: "16", name: "Bahamas", code: "BS" }, + { id: "17", name: "Bahrain", code: "BH" }, + { id: "18", name: "Bangladesh", code: "BD" }, + { id: "19", name: "Barbados", code: "BB" }, + { id: "20", name: "Belarus", code: "BY" }, + { id: "21", name: "Belgium", code: "BE" }, + { id: "22", name: "Belize", code: "BZ" }, + { id: "23", name: "Benin", code: "BJ" }, + { id: "24", name: "Bermuda", code: "BM" }, + { id: "25", name: "Bhutan", code: "BT" }, + { id: "26", name: "Bolivia", code: "BO" }, + { id: "27", name: "Bosnia and Herzegovina", code: "BA" }, + { id: "28", name: "Botswana", code: "BW" }, + { id: "29", name: "Bouvet Island", code: "BV" }, + { id: "30", name: "Brazil", code: "BR" }, + { id: "31", name: "British Indian Ocean Territory", code: "IO" }, + { id: "32", name: "British Virgin Islands", code: "VG" }, + { id: "33", name: "Brunei", code: "BN" }, + { id: "34", name: "Bulgaria", code: "BG" }, + { id: "35", name: "Burkina Faso", code: "BF" }, + { id: "36", name: "Burundi", code: "BI" }, + { id: "37", name: "Cambodia", code: "KH" }, + { id: "38", name: "Cameroon", code: "CM" }, + { id: "39", name: "Canada", code: "CA" }, + { id: "40", name: "Cape Verde", code: "CV" }, + { id: "41", name: "Caribbean Netherlands", code: "BQ" }, + { id: "42", name: "Cayman Islands", code: "KY" }, + { id: "43", name: "Central African Republic", code: "CF" }, + { id: "44", name: "Chad", code: "TD" }, + { id: "45", name: "Chile", code: "CL" }, + { id: "46", name: "China", code: "CN" }, + { id: "47", name: "Christmas Island", code: "CX" }, + { id: "48", name: "Cocos (Keeling) Islands", code: "CC" }, + { id: "49", name: "Colombia", code: "CO" }, + { id: "50", name: "Comoros", code: "KM" }, + { id: "51", name: "Cook Islands", code: "CK" }, + { id: "52", name: "Costa Rica", code: "CR" }, + { id: "53", name: "Croatia", code: "HR" }, + { id: "54", name: "Cuba", code: "CU" }, + { id: "55", name: "Curaçao", code: "CW" }, + { id: "56", name: "Cyprus", code: "CY" }, + { id: "57", name: "Czechia", code: "CZ" }, + { id: "58", name: "DR Congo", code: "CD" }, + { id: "59", name: "Denmark", code: "DK" }, + { id: "60", name: "Djibouti", code: "DJ" }, + { id: "61", name: "Dominica", code: "DM" }, + { id: "62", name: "Dominican Republic", code: "DO" }, + { id: "63", name: "Ecuador", code: "EC" }, + { id: "64", name: "Egypt", code: "EG" }, + { id: "65", name: "El Salvador", code: "SV" }, + { id: "66", name: "Equatorial Guinea", code: "GQ" }, + { id: "67", name: "Eritrea", code: "ER" }, + { id: "68", name: "Estonia", code: "EE" }, + { id: "69", name: "Eswatini", code: "SZ" }, + { id: "70", name: "Ethiopia", code: "ET" }, + { id: "71", name: "Falkland Islands", code: "FK" }, + { id: "72", name: "Faroe Islands", code: "FO" }, + { id: "73", name: "Fiji", code: "FJ" }, + { id: "74", name: "Finland", code: "FI" }, + { id: "75", name: "France", code: "FR" }, + { id: "76", name: "French Guiana", code: "GF" }, + { id: "77", name: "French Polynesia", code: "PF" }, + { id: "78", name: "French Southern and Antarctic Lands", code: "TF" }, + { id: "79", name: "Gabon", code: "GA" }, + { id: "80", name: "Gambia", code: "GM" }, + { id: "81", name: "Georgia", code: "GE" }, + { id: "82", name: "Germany", code: "DE" }, + { id: "83", name: "Ghana", code: "GH" }, + { id: "84", name: "Gibraltar", code: "GI" }, + { id: "85", name: "Greece", code: "GR" }, + { id: "86", name: "Greenland", code: "GL" }, + { id: "87", name: "Grenada", code: "GD" }, + { id: "88", name: "Guadeloupe", code: "GP" }, + { id: "89", name: "Guam", code: "GU" }, + { id: "90", name: "Guatemala", code: "GT" }, + { id: "91", name: "Guernsey", code: "GG" }, + { id: "92", name: "Guinea", code: "GN" }, + { id: "93", name: "Guinea-Bissau", code: "GW" }, + { id: "94", name: "Guyana", code: "GY" }, + { id: "95", name: "Haiti", code: "HT" }, + { id: "96", name: "Heard Island and McDonald Islands", code: "HM" }, + { id: "97", name: "Honduras", code: "HN" }, + { id: "98", name: "Hong Kong", code: "HK" }, + { id: "99", name: "Hungary", code: "HU" }, + { id: "100", name: "Iceland", code: "IS" }, + { id: "101", name: "India", code: "IN" }, + { id: "102", name: "Indonesia", code: "ID" }, + { id: "103", name: "Iran", code: "IR" }, + { id: "104", name: "Iraq", code: "IQ" }, + { id: "105", name: "Ireland", code: "IE" }, + { id: "106", name: "Isle of Man", code: "IM" }, + { id: "107", name: "Israel", code: "IL" }, + { id: "108", name: "Italy", code: "IT" }, + { id: "109", name: "Ivory Coast", code: "CI" }, + { id: "110", name: "Jamaica", code: "JM" }, + { id: "111", name: "Japan", code: "JP" }, + { id: "112", name: "Jersey", code: "JE" }, + { id: "113", name: "Jordan", code: "JO" }, + { id: "114", name: "Kazakhstan", code: "KZ" }, + { id: "115", name: "Kenya", code: "KE" }, + { id: "116", name: "Kiribati", code: "KI" }, + { id: "117", name: "Kosovo", code: "XK" }, + { id: "118", name: "Kuwait", code: "KW" }, + { id: "119", name: "Kyrgyzstan", code: "KG" }, + { id: "120", name: "Laos", code: "LA" }, + { id: "121", name: "Latvia", code: "LV" }, + { id: "122", name: "Lebanon", code: "LB" }, + { id: "123", name: "Lesotho", code: "LS" }, + { id: "124", name: "Liberia", code: "LR" }, + { id: "125", name: "Libya", code: "LY" }, + { id: "126", name: "Liechtenstein", code: "LI" }, + { id: "127", name: "Lithuania", code: "LT" }, + { id: "128", name: "Luxembourg", code: "LU" }, + { id: "129", name: "Macau", code: "MO" }, + { id: "130", name: "Madagascar", code: "MG" }, + { id: "131", name: "Malawi", code: "MW" }, + { id: "132", name: "Malaysia", code: "MY" }, + { id: "133", name: "Maldives", code: "MV" }, + { id: "134", name: "Mali", code: "ML" }, + { id: "135", name: "Malta", code: "MT" }, + { id: "136", name: "Marshall Islands", code: "MH" }, + { id: "137", name: "Martinique", code: "MQ" }, + { id: "138", name: "Mauritania", code: "MR" }, + { id: "139", name: "Mauritius", code: "MU" }, + { id: "140", name: "Mayotte", code: "YT" }, + { id: "141", name: "Mexico", code: "MX" }, + { id: "142", name: "Micronesia", code: "FM" }, + { id: "143", name: "Moldova", code: "MD" }, + { id: "144", name: "Monaco", code: "MC" }, + { id: "145", name: "Mongolia", code: "MN" }, + { id: "146", name: "Montenegro", code: "ME" }, + { id: "147", name: "Montserrat", code: "MS" }, + { id: "148", name: "Morocco", code: "MA" }, + { id: "149", name: "Mozambique", code: "MZ" }, + { id: "150", name: "Myanmar", code: "MM" }, + { id: "151", name: "Namibia", code: "NA" }, + { id: "152", name: "Nauru", code: "NR" }, + { id: "153", name: "Nepal", code: "NP" }, + { id: "154", name: "Netherlands", code: "NL" }, + { id: "155", name: "New Caledonia", code: "NC" }, + { id: "156", name: "New Zealand", code: "NZ" }, + { id: "157", name: "Nicaragua", code: "NI" }, + { id: "158", name: "Niger", code: "NE" }, + { id: "159", name: "Nigeria", code: "NG" }, + { id: "160", name: "Niue", code: "NU" }, + { id: "161", name: "Norfolk Island", code: "NF" }, + { id: "162", name: "North Korea", code: "KP" }, + { id: "163", name: "North Macedonia", code: "MK" }, + { id: "164", name: "Northern Mariana Islands", code: "MP" }, + { id: "165", name: "Norway", code: "NO" }, + { id: "166", name: "Oman", code: "OM" }, + { id: "167", name: "Pakistan", code: "PK" }, + { id: "168", name: "Palau", code: "PW" }, + { id: "169", name: "Palestine", code: "PS" }, + { id: "170", name: "Panama", code: "PA" }, + { id: "171", name: "Papua New Guinea", code: "PG" }, + { id: "172", name: "Paraguay", code: "PY" }, + { id: "173", name: "Peru", code: "PE" }, + { id: "174", name: "Philippines", code: "PH" }, + { id: "175", name: "Pitcairn Islands", code: "PN" }, + { id: "176", name: "Poland", code: "PL" }, + { id: "177", name: "Portugal", code: "PT" }, + { id: "178", name: "Puerto Rico", code: "PR" }, + { id: "179", name: "Qatar", code: "QA" }, + { id: "180", name: "Republic of the Congo", code: "CG" }, + { id: "181", name: "Romania", code: "RO" }, + { id: "182", name: "Russia", code: "RU" }, + { id: "183", name: "Rwanda", code: "RW" }, + { id: "184", name: "Réunion", code: "RE" }, + { id: "185", name: "Saint Barthélemy", code: "BL" }, + { + id: "186", + name: "Saint Helena, Ascension and Tristan da Cunha", + code: "SH", + }, + { id: "187", name: "Saint Kitts and Nevis", code: "KN" }, + { id: "188", name: "Saint Lucia", code: "LC" }, + { id: "189", name: "Saint Martin", code: "MF" }, + { id: "190", name: "Saint Pierre and Miquelon", code: "PM" }, + { id: "191", name: "Saint Vincent and the Grenadines", code: "VC" }, + { id: "192", name: "Samoa", code: "WS" }, + { id: "193", name: "San Marino", code: "SM" }, + { id: "194", name: "Saudi Arabia", code: "SA" }, + { id: "195", name: "Senegal", code: "SN" }, + { id: "196", name: "Serbia", code: "RS" }, + { id: "197", name: "Seychelles", code: "SC" }, + { id: "198", name: "Sierra Leone", code: "SL" }, + { id: "199", name: "Singapore", code: "SG" }, + { id: "200", name: "Sint Maarten", code: "SX" }, + { id: "201", name: "Slovakia", code: "SK" }, + { id: "202", name: "Slovenia", code: "SI" }, + { id: "203", name: "Solomon Islands", code: "SB" }, + { id: "204", name: "Somalia", code: "SO" }, + { id: "205", name: "South Africa", code: "ZA" }, + { id: "206", name: "South Georgia", code: "GS" }, + { id: "207", name: "South Korea", code: "KR" }, + { id: "208", name: "South Sudan", code: "SS" }, + { id: "209", name: "Spain", code: "ES" }, + { id: "210", name: "Sri Lanka", code: "LK" }, + { id: "211", name: "Sudan", code: "SD" }, + { id: "212", name: "Suriname", code: "SR" }, + { id: "213", name: "Svalbard and Jan Mayen", code: "SJ" }, + { id: "214", name: "Sweden", code: "SE" }, + { id: "215", name: "Switzerland", code: "CH" }, + { id: "216", name: "Syria", code: "SY" }, + { id: "217", name: "São Tomé and Príncipe", code: "ST" }, + { id: "218", name: "Taiwan", code: "TW" }, + { id: "219", name: "Tajikistan", code: "TJ" }, + { id: "220", name: "Tanzania", code: "TZ" }, + { id: "221", name: "Thailand", code: "TH" }, + { id: "222", name: "Timor-Leste", code: "TL" }, + { id: "223", name: "Togo", code: "TG" }, + { id: "224", name: "Tokelau", code: "TK" }, + { id: "225", name: "Tonga", code: "TO" }, + { id: "226", name: "Trinidad and Tobago", code: "TT" }, + { id: "227", name: "Tunisia", code: "TN" }, + { id: "228", name: "Turkey", code: "TR" }, + { id: "229", name: "Turkmenistan", code: "TM" }, + { id: "230", name: "Turks and Caicos Islands", code: "TC" }, + { id: "231", name: "Tuvalu", code: "TV" }, + { id: "232", name: "Uganda", code: "UG" }, + { id: "233", name: "Ukraine", code: "UA" }, + { id: "234", name: "United Arab Emirates", code: "AE" }, + { id: "235", name: "United Kingdom", code: "GB" }, + { id: "236", name: "United States", code: "US" }, + { id: "237", name: "United States Minor Outlying Islands", code: "UM" }, + { id: "238", name: "United States Virgin Islands", code: "VI" }, + { id: "239", name: "Uruguay", code: "UY" }, + { id: "240", name: "Uzbekistan", code: "UZ" }, + { id: "241", name: "Vanuatu", code: "VU" }, + { id: "242", name: "Vatican City", code: "VA" }, + { id: "243", name: "Venezuela", code: "VE" }, + { id: "244", name: "Vietnam", code: "VN" }, + { id: "245", name: "Wallis and Futuna", code: "WF" }, + { id: "246", name: "Western Sahara", code: "EH" }, + { id: "247", name: "Yemen", code: "YE" }, + { id: "248", name: "Zambia", code: "ZM" }, + { id: "249", name: "Zimbabwe", code: "ZW" }, + { id: "250", name: "Åland Islands", code: "AX" }, +]; + +export const COUNTRIES_SELECT = COUNTRIES.map((c) => { + return { + id: c.id, + label: `${c.code} (${c.name})`, + value: c.name.toLowerCase(), + }; +}); diff --git a/packages/logic/core/data/phonecc.ts b/packages/logic/core/data/phonecc.ts new file mode 100644 index 0000000..87d4c86 --- /dev/null +++ b/packages/logic/core/data/phonecc.ts @@ -0,0 +1,1227 @@ +export const PHONE_COUNTRY_CODES = [ + { + countryCode: "af", + country: "Afghanistan", + phoneCode: "+93", + }, + { + countryCode: "ax", + country: "Åland Islands", + phoneCode: "+358", + }, + { + countryCode: "al", + country: "Albania", + phoneCode: "+355", + }, + { + countryCode: "dz", + country: "Algeria", + phoneCode: "+213", + }, + { + countryCode: "as", + country: "American Samoa", + phoneCode: "+1", + }, + { + countryCode: "ad", + country: "Andorra", + phoneCode: "+376", + }, + { + countryCode: "ao", + country: "Angola", + phoneCode: "+244", + }, + { + countryCode: "ai", + country: "Anguilla", + phoneCode: "+1", + }, + { + countryCode: "aq", + country: "Antarctica", + phoneCode: "+672", + }, + { + countryCode: "ag", + country: "Antigua and Barbuda", + phoneCode: "+1", + }, + { + countryCode: "ar", + country: "Argentina", + phoneCode: "+54", + }, + { + countryCode: "am", + country: "Armenia", + phoneCode: "+374", + }, + { + countryCode: "aw", + country: "Aruba", + phoneCode: "+297", + }, + { + countryCode: "au", + country: "Australia", + phoneCode: "+61", + }, + { + countryCode: "at", + country: "Austria", + phoneCode: "+43", + }, + { + countryCode: "az", + country: "Azerbaijan", + phoneCode: "+994", + }, + { + countryCode: "bs", + country: "Bahamas", + phoneCode: "+1", + }, + { + countryCode: "bh", + country: "Bahrain", + phoneCode: "+973", + }, + { + countryCode: "bd", + country: "Bangladesh", + phoneCode: "+880", + }, + { + countryCode: "bb", + country: "Barbados", + phoneCode: "+1", + }, + { + countryCode: "by", + country: "Belarus", + phoneCode: "+375", + }, + { + countryCode: "be", + country: "Belgium", + phoneCode: "+32", + }, + { + countryCode: "bz", + country: "Belize", + phoneCode: "+501", + }, + { + countryCode: "bj", + country: "Benin", + phoneCode: "+229", + }, + { + countryCode: "bm", + country: "Bermuda", + phoneCode: "+1", + }, + { + countryCode: "bt", + country: "Bhutan", + phoneCode: "+975", + }, + { + countryCode: "bo", + country: "Bolivia", + phoneCode: "+591", + }, + { + countryCode: "ba", + country: "Bosnia and Herzegovina", + phoneCode: "+387", + }, + { + countryCode: "bw", + country: "Botswana", + phoneCode: "+267", + }, + { + countryCode: "br", + country: "Brazil", + phoneCode: "+55", + }, + { + countryCode: "io", + country: "British Indian Ocean Territory", + phoneCode: "+246", + }, + { + countryCode: "vg", + country: "British Virgin Islands", + phoneCode: "+1", + }, + { + countryCode: "bn", + country: "Brunei", + phoneCode: "+673", + }, + { + countryCode: "bg", + country: "Bulgaria", + phoneCode: "+359", + }, + { + countryCode: "bf", + country: "Burkina Faso", + phoneCode: "+226", + }, + { + countryCode: "bi", + country: "Burundi", + phoneCode: "+257", + }, + { + countryCode: "kh", + country: "Cambodia", + phoneCode: "+855", + }, + { + countryCode: "cm", + country: "Cameroon", + phoneCode: "+237", + }, + { + countryCode: "ca", + country: "Canada", + phoneCode: "+1", + }, + { + countryCode: "cv", + country: "Cape Verde", + phoneCode: "+238", + }, + { + countryCode: "ky", + country: "Cayman Islands", + phoneCode: "+1", + }, + { + countryCode: "cf", + country: "Central African Republic", + phoneCode: "+236", + }, + { + countryCode: "td", + country: "Chad", + phoneCode: "+235", + }, + { + countryCode: "cl", + country: "Chile", + phoneCode: "+56", + }, + { + countryCode: "cn", + country: "China", + phoneCode: "+86", + }, + { + countryCode: "cx", + country: "Christmas Island", + phoneCode: "+61", + }, + { + countryCode: "cc", + country: "Cocos [Keeling] Islands", + phoneCode: "+61", + }, + { + countryCode: "co", + country: "Colombia", + phoneCode: "+57", + }, + { + countryCode: "km", + country: "Comoros", + phoneCode: "+269", + }, + { + countryCode: "cg", + country: "Congo - Brazzaville", + phoneCode: "+242", + }, + { + countryCode: "cd", + country: "Congo - Kinshasa", + phoneCode: "+243", + }, + { + countryCode: "ck", + country: "Cook Islands", + phoneCode: "+682", + }, + { + countryCode: "cr", + country: "Costa Rica", + phoneCode: "+506", + }, + { + countryCode: "ci", + country: "Côte d’Ivoire", + phoneCode: "+225", + }, + { + countryCode: "hr", + country: "Croatia", + phoneCode: "+385", + }, + { + countryCode: "cy", + country: "Cyprus", + phoneCode: "+357", + }, + { + countryCode: "cz", + country: "Czech Republic", + phoneCode: "+420", + }, + { + countryCode: "dk", + country: "Denmark", + phoneCode: "+45", + }, + { + countryCode: "dj", + country: "Djibouti", + phoneCode: "+253", + }, + { + countryCode: "dm", + country: "Dominica", + phoneCode: "+1", + }, + { + countryCode: "do", + country: "Dominican Republic", + phoneCode: "+1", + }, + { + countryCode: "ec", + country: "Ecuador", + phoneCode: "+593", + }, + { + countryCode: "eg", + country: "Egypt", + phoneCode: "+20", + }, + { + countryCode: "sv", + country: "El Salvador", + phoneCode: "+503", + }, + { + countryCode: "gq", + country: "Equatorial Guinea", + phoneCode: "+240", + }, + { + countryCode: "er", + country: "Eritrea", + phoneCode: "+291", + }, + { + countryCode: "ee", + country: "Estonia", + phoneCode: "+372", + }, + { + countryCode: "et", + country: "Ethiopia", + phoneCode: "+251", + }, + { + countryCode: "fk", + country: "Falkland Islands", + phoneCode: "+500", + }, + { + countryCode: "fo", + country: "Faroe Islands", + phoneCode: "+298", + }, + { + countryCode: "fj", + country: "Fiji", + phoneCode: "+679", + }, + { + countryCode: "fi", + country: "Finland", + phoneCode: "+358", + }, + { + countryCode: "fr", + country: "France", + phoneCode: "+33", + }, + { + countryCode: "gf", + country: "French Guiana", + phoneCode: "+594", + }, + { + countryCode: "pf", + country: "French Polynesia", + phoneCode: "+689", + }, + { + countryCode: "tf", + country: "French Southern Territories", + phoneCode: "+262", + }, + { + countryCode: "ga", + country: "Gabon", + phoneCode: "+241", + }, + { + countryCode: "gm", + country: "Gambia", + phoneCode: "+220", + }, + { + countryCode: "ge", + country: "Georgia", + phoneCode: "+995", + }, + { + countryCode: "de", + country: "Germany", + phoneCode: "+49", + }, + { + countryCode: "gh", + country: "Ghana", + phoneCode: "+233", + }, + { + countryCode: "gi", + country: "Gibraltar", + phoneCode: "+350", + }, + { + countryCode: "gr", + country: "Greece", + phoneCode: "+30", + }, + { + countryCode: "gl", + country: "Greenland", + phoneCode: "+299", + }, + { + countryCode: "gd", + country: "Grenada", + phoneCode: "+1", + }, + { + countryCode: "gp", + country: "Guadeloupe", + phoneCode: "+590", + }, + { + countryCode: "gu", + country: "Guam", + phoneCode: "+1", + }, + { + countryCode: "gt", + country: "Guatemala", + phoneCode: "+502", + }, + { + countryCode: "gg", + country: "Guernsey", + phoneCode: "+44", + }, + { + countryCode: "gn", + country: "Guinea", + phoneCode: "+224", + }, + { + countryCode: "gw", + country: "Guinea-Bissau", + phoneCode: "+245", + }, + { + countryCode: "gy", + country: "Guyana", + phoneCode: "+592", + }, + { + countryCode: "ht", + country: "Haiti", + phoneCode: "+509", + }, + { + countryCode: "hn", + country: "Honduras", + phoneCode: "+504", + }, + { + countryCode: "hk", + country: "Hong Kong SAR China", + phoneCode: "+852", + }, + { + countryCode: "hu", + country: "Hungary", + phoneCode: "+36", + }, + { + countryCode: "is", + country: "Iceland", + phoneCode: "+354", + }, + { + countryCode: "in", + country: "India", + phoneCode: "+91", + }, + { + countryCode: "id", + country: "Indonesia", + phoneCode: "+62", + }, + { + countryCode: "ir", + country: "Iran", + phoneCode: "+98", + }, + { + countryCode: "iq", + country: "Iraq", + phoneCode: "+964", + }, + { + countryCode: "ie", + country: "Ireland", + phoneCode: "+353", + }, + { + countryCode: "im", + country: "Isle of Man", + phoneCode: "+44", + }, + { + countryCode: "il", + country: "Israel", + phoneCode: "+972", + }, + { + countryCode: "it", + country: "Italy", + phoneCode: "+39", + }, + { + countryCode: "jm", + country: "Jamaica", + phoneCode: "+1", + }, + { + countryCode: "jp", + country: "Japan", + phoneCode: "+81", + }, + { + countryCode: "je", + country: "Jersey", + phoneCode: "+44", + }, + { + countryCode: "jo", + country: "Jordan", + phoneCode: "+962", + }, + { + countryCode: "kz", + country: "Kazakhstan", + phoneCode: "+7", + }, + { + countryCode: "ke", + country: "Kenya", + phoneCode: "+254", + }, + { + countryCode: "ki", + country: "Kiribati", + phoneCode: "+686", + }, + { + countryCode: "xk", + country: "Kosovo", + phoneCode: "+383", + }, + { + countryCode: "kw", + country: "Kuwait", + phoneCode: "+965", + }, + { + countryCode: "kg", + country: "Kyrgyzstan", + phoneCode: "+996", + }, + { + countryCode: "la", + country: "Laos", + phoneCode: "+856", + }, + { + countryCode: "lv", + country: "Latvia", + phoneCode: "+371", + }, + { + countryCode: "lb", + country: "Lebanon", + phoneCode: "+961", + }, + { + countryCode: "ls", + country: "Lesotho", + phoneCode: "+266", + }, + { + countryCode: "lr", + country: "Liberia", + phoneCode: "+231", + }, + { + countryCode: "ly", + country: "Libya", + phoneCode: "+218", + }, + { + countryCode: "li", + country: "Liechtenstein", + phoneCode: "+423", + }, + { + countryCode: "lt", + country: "Lithuania", + phoneCode: "+370", + }, + { + countryCode: "lu", + country: "Luxembourg", + phoneCode: "+352", + }, + { + countryCode: "mo", + country: "Macau SAR China", + phoneCode: "+853", + }, + { + countryCode: "mk", + country: "Macedonia", + phoneCode: "+389", + }, + { + countryCode: "mg", + country: "Madagascar", + phoneCode: "+261", + }, + { + countryCode: "mw", + country: "Malawi", + phoneCode: "+265", + }, + { + countryCode: "my", + country: "Malaysia", + phoneCode: "+60", + }, + { + countryCode: "mv", + country: "Maldives", + phoneCode: "+960", + }, + { + countryCode: "ml", + country: "Mali", + phoneCode: "+223", + }, + { + countryCode: "mt", + country: "Malta", + phoneCode: "+356", + }, + { + countryCode: "mh", + country: "Marshall Islands", + phoneCode: "+692", + }, + { + countryCode: "mq", + country: "Martinique", + phoneCode: "+596", + }, + { + countryCode: "mr", + country: "Mauritania", + phoneCode: "+222", + }, + { + countryCode: "mu", + country: "Mauritius", + phoneCode: "+230", + }, + { + countryCode: "yt", + country: "Mayotte", + phoneCode: "+262", + }, + { + countryCode: "mx", + country: "Mexico", + phoneCode: "+52", + }, + { + countryCode: "fm", + country: "Micronesia", + phoneCode: "+691", + }, + { + countryCode: "md", + country: "Moldova", + phoneCode: "+373", + }, + { + countryCode: "mc", + country: "Monaco", + phoneCode: "+377", + }, + { + countryCode: "mn", + country: "Mongolia", + phoneCode: "+976", + }, + { + countryCode: "me", + country: "Montenegro", + phoneCode: "+382", + }, + { + countryCode: "ms", + country: "Montserrat", + phoneCode: "+1", + }, + { + countryCode: "ma", + country: "Morocco", + phoneCode: "+212", + }, + { + countryCode: "mz", + country: "Mozambique", + phoneCode: "+258", + }, + { + countryCode: "mm", + country: "Myanmar [Burma]", + phoneCode: "+95", + }, + { + countryCode: "na", + country: "Namibia", + phoneCode: "+264", + }, + { + countryCode: "nr", + country: "Nauru", + phoneCode: "+674", + }, + { + countryCode: "np", + country: "Nepal", + phoneCode: "+977", + }, + { + countryCode: "nl", + country: "Netherlands", + phoneCode: "+31", + }, + { + countryCode: "an", + country: "Netherlands Antilles", + phoneCode: "+599", + }, + { + countryCode: "nc", + country: "New Caledonia", + phoneCode: "+687", + }, + { + countryCode: "nz", + country: "New Zealand", + phoneCode: "+64", + }, + { + countryCode: "ni", + country: "Nicaragua", + phoneCode: "+505", + }, + { + countryCode: "ne", + country: "Niger", + phoneCode: "+227", + }, + { + countryCode: "ng", + country: "Nigeria", + phoneCode: "+234", + }, + { + countryCode: "nu", + country: "Niue", + phoneCode: "+683", + }, + { + countryCode: "nf", + country: "Norfolk Island", + phoneCode: "+672", + }, + { + countryCode: "kp", + country: "North Korea", + phoneCode: "+850", + }, + { + countryCode: "mp", + country: "Northern Mariana Islands", + phoneCode: "+1", + }, + { + countryCode: "no", + country: "Norway", + phoneCode: "+47", + }, + { + countryCode: "om", + country: "Oman", + phoneCode: "+968", + }, + { + countryCode: "pk", + country: "Pakistan", + phoneCode: "+92", + }, + { + countryCode: "pw", + country: "Palau", + phoneCode: "+680", + }, + { + countryCode: "ps", + country: "Palestinian Territories", + phoneCode: "+970", + }, + { + countryCode: "pa", + country: "Panama", + phoneCode: "+507", + }, + { + countryCode: "pg", + country: "Papua New Guinea", + phoneCode: "+675", + }, + { + countryCode: "py", + country: "Paraguay", + phoneCode: "+595", + }, + { + countryCode: "pe", + country: "Peru", + phoneCode: "+51", + }, + { + countryCode: "ph", + country: "Philippines", + phoneCode: "+63", + }, + { + countryCode: "pn", + country: "Pitcairn Islands", + phoneCode: "+64", + }, + { + countryCode: "pl", + country: "Poland", + phoneCode: "+48", + }, + { + countryCode: "pt", + country: "Portugal", + phoneCode: "+351", + }, + { + countryCode: "pr", + country: "Puerto Rico", + phoneCode: "+1", + }, + { + countryCode: "qa", + country: "Qatar", + phoneCode: "+974", + }, + { + countryCode: "re", + country: "Réunion", + phoneCode: "+262", + }, + { + countryCode: "ro", + country: "Romania", + phoneCode: "+40", + }, + { + countryCode: "ru", + country: "Russia", + phoneCode: "+7", + }, + { + countryCode: "rw", + country: "Rwanda", + phoneCode: "+250", + }, + { + countryCode: "bl", + country: "Saint Barthélemy", + phoneCode: "+590", + }, + { + countryCode: "sh", + country: "Saint Helena", + phoneCode: "+290", + }, + { + countryCode: "kn", + country: "Saint Kitts and Nevis", + phoneCode: "+1", + }, + { + countryCode: "lc", + country: "Saint Lucia", + phoneCode: "+1", + }, + { + countryCode: "mf", + country: "Saint Martin", + phoneCode: "+590", + }, + { + countryCode: "sx", + country: "Saint Martin", + phoneCode: "+1721", + }, + { + countryCode: "pm", + country: "Saint Pierre and Miquelon", + phoneCode: "+508", + }, + { + countryCode: "vc", + country: "Saint Vincent and the Grenadines", + phoneCode: "+1", + }, + { + countryCode: "ws", + country: "Samoa", + phoneCode: "+685", + }, + { + countryCode: "sm", + country: "San Marino", + phoneCode: "+378", + }, + { + countryCode: "st", + country: "São Tomé and Príncipe", + phoneCode: "+239", + }, + { + countryCode: "sa", + country: "Saudi Arabia", + phoneCode: "+966", + }, + { + countryCode: "sn", + country: "Senegal", + phoneCode: "+221", + }, + { + countryCode: "rs", + country: "Serbia", + phoneCode: "+381", + }, + { + countryCode: "sc", + country: "Seychelles", + phoneCode: "+248", + }, + { + countryCode: "sl", + country: "Sierra Leone", + phoneCode: "+232", + }, + { + countryCode: "sg", + country: "Singapore", + phoneCode: "+65", + }, + { + countryCode: "sk", + country: "Slovakia", + phoneCode: "+421", + }, + { + countryCode: "si", + country: "Slovenia", + phoneCode: "+386", + }, + { + countryCode: "sb", + country: "Solomon Islands", + phoneCode: "+677", + }, + { + countryCode: "so", + country: "Somalia", + phoneCode: "+252", + }, + { + countryCode: "za", + country: "South Africa", + phoneCode: "+27", + }, + { + countryCode: "gs", + country: "South Georgia and the South Sandwich Islands", + phoneCode: "+500", + }, + { + countryCode: "kr", + country: "South Korea", + phoneCode: "+82", + }, + { + countryCode: "ss", + country: "South Sudan", + phoneCode: "+211", + }, + { + countryCode: "es", + country: "Spain", + phoneCode: "+34", + }, + { + countryCode: "lk", + country: "Sri Lanka", + phoneCode: "+94", + }, + { + countryCode: "sd", + country: "Sudan", + phoneCode: "+249", + }, + { + countryCode: "sr", + country: "Suriname", + phoneCode: "+597", + }, + { + countryCode: "sj", + country: "Svalbard and Jan Mayen", + phoneCode: "+47", + }, + { + countryCode: "sz", + country: "Swaziland", + phoneCode: "+268", + }, + { + countryCode: "se", + country: "Sweden", + phoneCode: "+46", + }, + { + countryCode: "ch", + country: "Switzerland", + phoneCode: "+41", + }, + { + countryCode: "sy", + country: "Syria", + phoneCode: "+963", + }, + { + countryCode: "tw", + country: "Taiwan", + phoneCode: "+886", + }, + { + countryCode: "tj", + country: "Tajikistan", + phoneCode: "+992", + }, + { + countryCode: "tz", + country: "Tanzania", + phoneCode: "+255", + }, + { + countryCode: "th", + country: "Thailand", + phoneCode: "+66", + }, + { + countryCode: "tl", + country: "Timor-Leste", + phoneCode: "+670", + }, + { + countryCode: "tg", + country: "Togo", + phoneCode: "+228", + }, + { + countryCode: "tk", + country: "Tokelau", + phoneCode: "+690", + }, + { + countryCode: "to", + country: "Tonga", + phoneCode: "+676", + }, + { + countryCode: "tt", + country: "Trinidad and Tobago", + phoneCode: "+1", + }, + { + countryCode: "tn", + country: "Tunisia", + phoneCode: "+216", + }, + { + countryCode: "tr", + country: "Turkey", + phoneCode: "+90", + }, + { + countryCode: "tm", + country: "Turkmenistan", + phoneCode: "+993", + }, + { + countryCode: "tc", + country: "Turks and Caicos Islands", + phoneCode: "+1", + }, + { + countryCode: "tv", + country: "Tuvalu", + phoneCode: "+688", + }, + { + countryCode: "vi", + country: "U.S. Virgin Islands", + phoneCode: "+1", + }, + { + countryCode: "ug", + country: "Uganda", + phoneCode: "+256", + }, + { + countryCode: "ua", + country: "Ukraine", + phoneCode: "+380", + }, + { + countryCode: "ae", + country: "United Arab Emirates", + phoneCode: "+971", + }, + { + countryCode: "gb", + country: "United Kingdom", + phoneCode: "+44", + }, + { + countryCode: "us", + country: "United States", + phoneCode: "+1", + }, + { + countryCode: "uy", + country: "Uruguay", + phoneCode: "+598", + }, + { + countryCode: "uz", + country: "Uzbekistan", + phoneCode: "+998", + }, + { + countryCode: "vu", + country: "Vanuatu", + phoneCode: "+678", + }, + { + countryCode: "va", + country: "Vatican City", + phoneCode: "+379", + }, + { + countryCode: "ve", + country: "Venezuela", + phoneCode: "+58", + }, + { + countryCode: "vn", + country: "Vietnam", + phoneCode: "+84", + }, + { + countryCode: "wf", + country: "Wallis and Futuna", + phoneCode: "+681", + }, + { + countryCode: "eh", + country: "Western Sahara", + phoneCode: "+212", + }, + { + countryCode: "ye", + country: "Yemen", + phoneCode: "+967", + }, + { + countryCode: "zm", + country: "Zambia", + phoneCode: "+260", + }, + { + countryCode: "zw", + country: "Zimbabwe", + phoneCode: "+263", + }, +]; diff --git a/packages/logic/core/date.utils.ts b/packages/logic/core/date.utils.ts new file mode 100644 index 0000000..15bed65 --- /dev/null +++ b/packages/logic/core/date.utils.ts @@ -0,0 +1,83 @@ +import type { CalendarDate } from "@internationalized/date"; + +export function formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000); + if (seconds < 60) return `${seconds}s`; + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds}s`; +} + +export function formatDateTimeFromIsoString(isoString: string): string { + try { + const date = new Date(isoString); + return new Intl.DateTimeFormat("en-US", { + dateStyle: "medium", + timeStyle: "short", + }).format(date); + } catch (e) { + return "Invalid date"; + } +} + +export function getJustDateString(d: Date): string { + return d.toISOString().split("T")[0]; +} + +export function formatDateTime(dateTimeStr: string) { + const date = new Date(dateTimeStr); + return { + time: date.toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }), + date: date.toLocaleDateString("en-US", { + weekday: "short", + day: "2-digit", + month: "short", + }), + }; +} + +export function formatDate(dateStr: string) { + return new Date(dateStr).toLocaleDateString("en-US", { + weekday: "short", + day: "2-digit", + month: "short", + }); +} + +export function isTimestampMoreThan1MinAgo(ts: string): boolean { + const lastPingedDate = new Date(ts); + const now = new Date(); + const diff = now.getTime() - lastPingedDate.getTime(); + return diff > 60000; +} + +export function isTimestampOlderThan(ts: string, seconds: number): boolean { + const lastPingedDate = new Date(ts); + const now = new Date(); + const diff = now.getTime() - lastPingedDate.getTime(); + return diff > seconds * 1000; +} + +export function makeDateStringISO(ds: string): string { + if (ds.includes("T")) { + return `${ds.split("T")[0]}T00:00:00.000Z`; + } + return `${ds}T00:00:00.000Z`; +} + +export function parseCalDateToDateString(v: CalendarDate) { + let month: string | number = v.month; + if (month < 10) { + month = `0${month}`; + } + let day: string | number = v.day; + if (day < 10) { + day = `0${day}`; + } + return `${v.year}-${month}-${day}`; +} diff --git a/packages/logic/core/error.ts b/packages/logic/core/error.ts new file mode 100644 index 0000000..83d8c49 --- /dev/null +++ b/packages/logic/core/error.ts @@ -0,0 +1,8 @@ +export type Err = { + code: string; + message: string; + description: string; + detail: string; + actionable?: boolean; + error?: any; +}; diff --git a/packages/logic/core/flow.execution.context.ts b/packages/logic/core/flow.execution.context.ts new file mode 100644 index 0000000..6c84e29 --- /dev/null +++ b/packages/logic/core/flow.execution.context.ts @@ -0,0 +1,5 @@ +export type FlowExecCtx = { + flowId: string; + userId?: string; + sessionId?: string; +}; diff --git a/packages/logic/core/hash.utils.ts b/packages/logic/core/hash.utils.ts new file mode 100644 index 0000000..b0da8fa --- /dev/null +++ b/packages/logic/core/hash.utils.ts @@ -0,0 +1,31 @@ +import { argon2id, hash as argonHash, verify as argonVerify } from "argon2"; + +export async function hashString(target: string): Promise { + const salt = Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString( + "hex", + ); + const hash = await argonHash(target, { + type: argon2id, + salt: Buffer.from(salt, "hex"), + hashLength: 32, + timeCost: 3, + memoryCost: 65536, + parallelism: 1, + }); + return hash; +} + +export async function verifyHash({ + hash, + target, +}: { + hash: string; + target: string; +}): Promise { + try { + const isValid = await argonVerify(hash, `${target}`); + return isValid; + } catch (err) { + return false; + } +} diff --git a/packages/logic/core/http.telemetry.ts b/packages/logic/core/http.telemetry.ts new file mode 100644 index 0000000..a8ce133 --- /dev/null +++ b/packages/logic/core/http.telemetry.ts @@ -0,0 +1,88 @@ +import { context, metrics, SpanStatusCode, trace } from "@opentelemetry/api"; + +type TelemetryContext = { + req: { + method: string; + path: string; + }; + res: { + status: number; + }; +}; + +export function createHttpTelemetryMiddleware(serviceName: string) { + const tracer = trace.getTracer(`${serviceName}.http`); + const meter = metrics.getMeter(`${serviceName}.http`); + + const requestCount = meter.createCounter(`${serviceName}.http.server.requests`, { + description: `Total number of ${serviceName} HTTP requests`, + }); + + const requestDuration = meter.createHistogram( + `${serviceName}.http.server.duration`, + { + description: `${serviceName} HTTP request duration`, + unit: "ms", + }, + ); + + const activeRequests = meter.createUpDownCounter( + `${serviceName}.http.server.active_requests`, + { + description: `Number of in-flight ${serviceName} HTTP requests`, + }, + ); + + return async (c: TelemetryContext, next: () => Promise) => { + const startedAt = performance.now(); + const method = c.req.method; + const route = c.req.path; + + const span = tracer.startSpan(`http ${method} ${route}`, { + attributes: { + "http.request.method": method, + "url.path": route, + }, + }); + + activeRequests.add(1, { + "http.request.method": method, + "url.path": route, + }); + + try { + await context.with(trace.setSpan(context.active(), span), next); + + span.setAttribute("http.response.status_code", c.res.status); + span.setStatus({ + code: + c.res.status >= 500 + ? SpanStatusCode.ERROR + : SpanStatusCode.OK, + }); + } catch (error) { + span.recordException(error as Error); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: `Unhandled ${serviceName} request error`, + }); + throw error; + } finally { + const durationMs = performance.now() - startedAt; + const attrs = { + "http.request.method": method, + "http.response.status_code": c.res.status, + "url.path": route, + }; + + requestCount.add(1, attrs); + requestDuration.record(durationMs, attrs); + activeRequests.add(-1, { + "http.request.method": method, + "url.path": route, + }); + + span.end(); + } + }; +} diff --git a/packages/logic/core/observability.ts b/packages/logic/core/observability.ts new file mode 100644 index 0000000..ee79463 --- /dev/null +++ b/packages/logic/core/observability.ts @@ -0,0 +1,80 @@ +import { SpanStatusCode, trace, type Attributes } from "@opentelemetry/api"; +import type { FlowExecCtx } from "./flow.execution.context"; +import { ResultAsync } from "neverthrow"; + +const tracer = trace.getTracer("@pkg/logic"); + +type BaseSpanOptions = { + name: string; + fctx?: FlowExecCtx; + attributes?: Attributes; +}; + +function spanAttributes( + fctx?: FlowExecCtx, + attributes?: Attributes, +): Attributes | undefined { + const flowAttrs: Attributes = {}; + if (fctx?.flowId) flowAttrs["flow.id"] = fctx.flowId; + if (fctx?.userId) flowAttrs["flow.user_id"] = fctx.userId; + if (fctx?.sessionId) flowAttrs["flow.session_id"] = fctx.sessionId; + + if (!attributes && Object.keys(flowAttrs).length === 0) { + return undefined; + } + return { ...flowAttrs, ...(attributes ?? {}) }; +} + +export async function withFlowSpan({ + name, + fctx, + attributes, + fn, +}: BaseSpanOptions & { + fn: () => Promise; +}): Promise { + return tracer.startActiveSpan( + name, + { attributes: spanAttributes(fctx, attributes) }, + async (span) => { + try { + const result = await fn(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.recordException(error as Error); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: + error instanceof Error ? error.message : String(error), + }); + throw error; + } finally { + span.end(); + } + }, + ); +} + +export function traceResultAsync({ + name, + fctx, + attributes, + fn, +}: BaseSpanOptions & { + fn: () => ResultAsync; +}): ResultAsync { + return ResultAsync.fromPromise( + withFlowSpan({ + name, + fctx, + attributes, + fn: async () => + fn().match( + (value) => value, + (error) => Promise.reject(error), + ), + }), + (error) => error as E, + ); +} diff --git a/packages/logic/core/pagination.utils.ts b/packages/logic/core/pagination.utils.ts new file mode 100644 index 0000000..dd11b81 --- /dev/null +++ b/packages/logic/core/pagination.utils.ts @@ -0,0 +1,12 @@ +import * as v from "valibot"; + +export const paginationModel = v.object({ + cursor: v.optional(v.string()), + limit: v.pipe(v.number(), v.integer(), v.maxValue(100)), + asc: v.optional(v.boolean(), true), + totalItemCount: v.optional(v.pipe(v.number(), v.integer()), 0), + totalPages: v.pipe(v.number(), v.integer()), + page: v.pipe(v.number(), v.integer()), +}); + +export type PaginationModel = v.InferOutput; diff --git a/packages/logic/core/rate.limiter.ts b/packages/logic/core/rate.limiter.ts new file mode 100644 index 0000000..5f8b3bb --- /dev/null +++ b/packages/logic/core/rate.limiter.ts @@ -0,0 +1,40 @@ +import { logger } from "@pkg/logger"; + +export class RateLimiter { + private requestTimestamps: number[] = []; + private readonly callsPerMinute: number; + + constructor(callsPerMinute: number = 60) { + this.callsPerMinute = Math.min(callsPerMinute, 60); + } + + async checkRateLimit(): Promise { + const currentTime = Date.now(); + const oneMinuteAgo = currentTime - 60000; // 60 seconds in milliseconds + + // Remove timestamps older than 1 minute + this.requestTimestamps = this.requestTimestamps.filter( + (timestamp) => timestamp > oneMinuteAgo, + ); + + // If we're approaching the limit, wait until we have capacity + if (this.requestTimestamps.length >= this.callsPerMinute) { + const oldestRequest = this.requestTimestamps[0]; + const waitTime = oldestRequest + 60000 - currentTime; + + if (waitTime > 0) { + logger.warn( + `Rate limit approaching (${this.requestTimestamps.length} requests in last minute). Sleeping for ${waitTime}ms`, + ); + await new Promise((resolve) => setTimeout(resolve, waitTime)); + // After waiting, some timestamps may have expired + this.requestTimestamps = this.requestTimestamps.filter( + (timestamp) => timestamp > Date.now() - 60000, + ); + } + } + + // Add current request to timestamps + this.requestTimestamps.push(Date.now()); + } +} diff --git a/packages/logic/core/settings.ts b/packages/logic/core/settings.ts new file mode 100644 index 0000000..33b2d76 --- /dev/null +++ b/packages/logic/core/settings.ts @@ -0,0 +1 @@ +export { getSetting, settings } from "@pkg/settings"; diff --git a/packages/logic/core/string.utils/index.ts b/packages/logic/core/string.utils/index.ts new file mode 100644 index 0000000..7531a50 --- /dev/null +++ b/packages/logic/core/string.utils/index.ts @@ -0,0 +1,106 @@ +import * as v from "valibot"; + +export function capitalize(input: string, firstOfAllWords?: boolean): string { + // capitalize first letter of input + if (!firstOfAllWords) { + return input.charAt(0).toUpperCase() + input.slice(1); + } + let out = ""; + for (const word of input.split(" ")) { + out += word.charAt(0).toUpperCase() + word.slice(1) + " "; + } + return out.slice(0, -1); +} + +export function camelToSpacedPascal(input: string): string { + let result = ""; + let previousChar = ""; + for (const char of input) { + if (char === char.toUpperCase() && previousChar !== " ") { + result += " "; + } + result += char; + previousChar = char; + } + return result.charAt(0).toUpperCase() + result.slice(1); +} + +export function snakeToCamel(input: string): string { + if (!input) { + return input; + } + // also account for numbers and kebab-case + const splits = input.split(/[-_]/); + let result = splits[0]; + for (const split of splits.slice(1)) { + result += capitalize(split, true); + } + return result ?? ""; +} + +export function snakeToSpacedPascal(input: string): string { + return camelToSpacedPascal(snakeToCamel(input)); +} + +export function spacedPascalToSnake(input: string): string { + return input.split(" ").join("_").toLowerCase(); +} + +export function convertDashedLowerToTitleCase(input: string): string { + return input + .split("-") + .map( + (word) => + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(), + ) + .join(" "); // Join the words with a space +} + +export function encodeCursor(cursor: T): string { + try { + // Convert the object to a JSON string + const jsonString = JSON.stringify(cursor); + // Convert to UTF-8 bytes, then base64 + return btoa( + encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (_, p1) => + String.fromCharCode(parseInt(p1, 16)), + ), + ); + } catch (error) { + console.error("Error encoding cursor:", error); + throw new Error("Failed to encode cursor"); + } +} + +export function decodeCursor( + cursor: string, + parser: v.BaseSchema, +) { + try { + // Decode base64 back to UTF-8 string + const decoded = decodeURIComponent( + Array.prototype.map + .call(atob(cursor), (c) => { + return ( + "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2) + ); + }) + .join(""), + ); + // Parse back to object + const parsedData = JSON.parse(decoded); + const result = v.safeParse(parser, parsedData); + return result.success + ? { success: true, data: result.output as T } + : { + success: false, + error: new Error( + result.issues.map((i) => i.message).join(", "), + ), + data: undefined, + }; + } catch (error) { + console.error("Error decoding cursor:", error); + return { error: new Error("Failed to decode cursor"), data: undefined }; + } +} diff --git a/packages/logic/core/string.utils/sequence.matcher.ts b/packages/logic/core/string.utils/sequence.matcher.ts new file mode 100644 index 0000000..1196510 --- /dev/null +++ b/packages/logic/core/string.utils/sequence.matcher.ts @@ -0,0 +1,555 @@ +/** + * Similar to Python's difflib.SequenceMatcher + * + * A flexible class for comparing pairs of sequences of any type. + * Uses the Ratcliff-Obershelp algorithm with "gestalt pattern matching" + * to find the longest contiguous matching subsequences. + */ + +export interface Match { + /** Starting position in sequence a */ + a: number; + /** Starting position in sequence b */ + b: number; + /** Length of the matching block */ + size: number; +} + +export type OpCode = "replace" | "delete" | "insert" | "equal"; + +export interface OpCodeTuple { + /** Operation type */ + tag: OpCode; + /** Start index in sequence a */ + i1: number; + /** End index in sequence a */ + i2: number; + /** Start index in sequence b */ + j1: number; + /** End index in sequence b */ + j2: number; +} + +export type JunkFunction = (element: T) => boolean; + +export class SequenceMatcher { + private isjunk: JunkFunction | null; + private a: T[]; + private b: T[]; + private autojunk: boolean; + + // Cached data structures for sequence b + private bjunk: Set; + private bpopular: Set; + private b2j: Map; + + // Cached results + private fullbcount: Map | null = null; + private matchingBlocks: Match[] | null = null; + private opcodes: OpCodeTuple[] | null = null; + + constructor( + isjunk: JunkFunction | null = null, + a: T[] = [], + b: T[] = [], + autojunk: boolean = true, + ) { + this.isjunk = isjunk; + this.a = []; + this.b = []; + this.autojunk = autojunk; + this.bjunk = new Set(); + this.bpopular = new Set(); + this.b2j = new Map(); + + this.setSeqs(a, b); + } + + /** + * Set both sequences to be compared + */ + setSeqs(a: T[], b: T[]): void { + this.setSeq1(a); + this.setSeq2(b); + } + + /** + * Set the first sequence to be compared + */ + setSeq1(a: T[]): void { + if (a === this.a) return; + this.a = [...a]; + this.matchingBlocks = null; + this.opcodes = null; + } + + /** + * Set the second sequence to be compared + */ + setSeq2(b: T[]): void { + if (b === this.b) return; + this.b = [...b]; + this.matchingBlocks = null; + this.opcodes = null; + this.fullbcount = null; + this.chainB(); + } + + /** + * Analyze sequence b and build lookup structures + */ + private chainB(): void { + const b = this.b; + this.bjunk = new Set(); + this.bpopular = new Set(); + this.b2j = new Map(); + + // Count occurrences of each element + const elementCounts = new Map(); + for (const element of b) { + elementCounts.set(element, (elementCounts.get(element) || 0) + 1); + } + + // Determine junk and popular elements + const n = b.length; + const popularThreshold = Math.floor(n / 100) + 1; // > 1% of sequence + + for (const [element, count] of elementCounts) { + if (this.isjunk && this.isjunk(element)) { + this.bjunk.add(element); + } else if (this.autojunk && n >= 200 && count > popularThreshold) { + this.bpopular.add(element); + } + } + + // Build position mapping for non-junk, non-popular elements + for (let i = 0; i < b.length; i++) { + const element = b[i]; + if (!this.bjunk.has(element) && !this.bpopular.has(element)) { + if (!this.b2j.has(element)) { + this.b2j.set(element, []); + } + this.b2j.get(element)!.push(i); + } + } + } + + /** + * Find the longest matching block in a[alo:ahi] and b[blo:bhi] + */ + findLongestMatch( + alo: number = 0, + ahi: number | null = null, + blo: number = 0, + bhi: number | null = null, + ): Match { + if (ahi === null) ahi = this.a.length; + if (bhi === null) bhi = this.b.length; + + let besti = alo; + let bestj = blo; + let bestsize = 0; + + // Find all positions where a[i] appears in b + const j2len = new Map(); + + for (let i = alo; i < ahi; i++) { + const element = this.a[i]; + const positions = this.b2j.get(element) || []; + const newj2len = new Map(); + + for (const j of positions) { + if (j < blo) continue; + if (j >= bhi) break; + + const prevLen = j2len.get(j - 1) || 0; + const k = prevLen + 1; + newj2len.set(j, k); + + if (k > bestsize) { + besti = i - k + 1; + bestj = j - k + 1; + bestsize = k; + } + } + + j2len.clear(); + for (const [key, value] of newj2len) { + j2len.set(key, value); + } + } + + // Extend match with junk elements + while ( + besti > alo && + bestj > blo && + !this.isBJunk(this.b[bestj - 1]) && + this.elementsEqual(this.a[besti - 1], this.b[bestj - 1]) + ) { + besti--; + bestj--; + bestsize++; + } + + while ( + besti + bestsize < ahi && + bestj + bestsize < bhi && + !this.isBJunk(this.b[bestj + bestsize]) && + this.elementsEqual(this.a[besti + bestsize], this.b[bestj + bestsize]) + ) { + bestsize++; + } + + // Extend match with junk elements at the beginning + while (besti > alo && bestj > blo && this.isBJunk(this.b[bestj - 1])) { + besti--; + bestj--; + bestsize++; + } + + // Extend match with junk elements at the end + while ( + besti + bestsize < ahi && + bestj + bestsize < bhi && + this.isBJunk(this.b[bestj + bestsize]) + ) { + bestsize++; + } + + return { a: besti, b: bestj, size: bestsize }; + } + + /** + * Return list of non-overlapping matching blocks + */ + getMatchingBlocks(): Match[] { + if (this.matchingBlocks !== null) { + return this.matchingBlocks; + } + + const matches: Match[] = []; + this.getMatchingBlocksRecursive( + 0, + this.a.length, + 0, + this.b.length, + matches, + ); + + // Add sentinel + matches.push({ a: this.a.length, b: this.b.length, size: 0 }); + + this.matchingBlocks = matches; + return matches; + } + + /** + * Recursively find matching blocks + */ + private getMatchingBlocksRecursive( + alo: number, + ahi: number, + blo: number, + bhi: number, + matches: Match[], + ): void { + const match = this.findLongestMatch(alo, ahi, blo, bhi); + + if (match.size > 0) { + // Recurse on the pieces before and after the match + if (alo < match.a && blo < match.b) { + this.getMatchingBlocksRecursive( + alo, + match.a, + blo, + match.b, + matches, + ); + } + + matches.push(match); + + if (match.a + match.size < ahi && match.b + match.size < bhi) { + this.getMatchingBlocksRecursive( + match.a + match.size, + ahi, + match.b + match.size, + bhi, + matches, + ); + } + } + } + + /** + * Return list of 5-tuples describing how to turn a into b + */ + getOpcodes(): OpCodeTuple[] { + if (this.opcodes !== null) { + return this.opcodes; + } + + let i = 0; + let j = 0; + const opcodes: OpCodeTuple[] = []; + + for (const match of this.getMatchingBlocks()) { + let tag: OpCode = "equal"; + + if (i < match.a && j < match.b) { + tag = "replace"; + } else if (i < match.a) { + tag = "delete"; + } else if (j < match.b) { + tag = "insert"; + } + + if (tag !== "equal") { + opcodes.push({ + tag, + i1: i, + i2: match.a, + j1: j, + j2: match.b, + }); + } + + i = match.a + match.size; + j = match.b + match.size; + + // Don't add the sentinel match + if (match.size > 0) { + opcodes.push({ + tag: "equal", + i1: match.a, + i2: i, + j1: match.b, + j2: j, + }); + } + } + + this.opcodes = opcodes; + return opcodes; + } + + /** + * Return a measure of sequences' similarity (0.0-1.0) + */ + ratio(): number { + const matches = this.getMatchingBlocks() + .slice(0, -1) // Exclude sentinel + .reduce((sum, match) => sum + match.size, 0); + + const total = this.a.length + this.b.length; + return total === 0 ? 1.0 : (2.0 * matches) / total; + } + + /** + * Return an upper bound on ratio() relatively quickly + */ + quickRatio(): number { + if (this.fullbcount === null) { + this.fullbcount = new Map(); + for (const element of this.b) { + this.fullbcount.set( + element, + (this.fullbcount.get(element) || 0) + 1, + ); + } + } + + let matches = 0; + const tempCounts = new Map(this.fullbcount); + + for (const element of this.a) { + const count = tempCounts.get(element); + if (count && count > 0) { + matches++; + tempCounts.set(element, count - 1); + } + } + + const total = this.a.length + this.b.length; + return total === 0 ? 1.0 : (2.0 * matches) / total; + } + + /** + * Return an upper bound on ratio() very quickly + */ + realQuickRatio(): number { + const total = this.a.length + this.b.length; + return total === 0 + ? 1.0 + : (2.0 * Math.min(this.a.length, this.b.length)) / total; + } + + /** + * Check if element is junk in sequence b + */ + private isBJunk(element: T): boolean { + return this.bjunk.has(element); + } + + /** + * Check if two elements are equal + */ + private elementsEqual(a: T, b: T): boolean { + return a === b; + } +} + +/** + * Utility function to get close matches similar to Python's get_close_matches + */ +export function getCloseMatches( + word: T[], + possibilities: T[][], + n: number = 3, + cutoff: number = 0.6, +): T[][] { + if (n <= 0) { + throw new Error("n must be greater than 0"); + } + + const matches: Array<{ sequence: T[]; ratio: number }> = []; + + for (const possibility of possibilities) { + const matcher = new SequenceMatcher(null, word, possibility); + const ratio = matcher.ratio(); + + if (ratio >= cutoff) { + matches.push({ sequence: possibility, ratio }); + } + } + + // Sort by ratio (descending) and take top n + matches.sort((a, b) => b.ratio - a.ratio); + return matches.slice(0, n).map((match) => match.sequence); +} + +/** + * String-specific version of SequenceMatcher for character-by-character comparison. + * This class treats strings as sequences of characters while providing a string-friendly API. + */ +export class StringSequenceMatcher { + private matcher: SequenceMatcher; + + constructor( + isjunk: JunkFunction | null = null, + a: string = "", + b: string = "", + autojunk: boolean = true, + ) { + this.matcher = new SequenceMatcher( + isjunk, + Array.from(a), + Array.from(b), + autojunk, + ); + } + + /** + * Set both sequences to be compared + */ + setSeqs(a: string, b: string): void { + this.matcher.setSeqs(Array.from(a), Array.from(b)); + } + + /** + * Set the first sequence to be compared + */ + setSeq1(a: string): void { + this.matcher.setSeq1(Array.from(a)); + } + + /** + * Set the second sequence to be compared + */ + setSeq2(b: string): void { + this.matcher.setSeq2(Array.from(b)); + } + + /** + * Find the longest matching block in a[alo:ahi] and b[blo:bhi] + */ + findLongestMatch( + alo: number = 0, + ahi: number | null = null, + blo: number = 0, + bhi: number | null = null, + ): Match { + return this.matcher.findLongestMatch(alo, ahi, blo, bhi); + } + + /** + * Return list of non-overlapping matching blocks + */ + getMatchingBlocks(): Match[] { + return this.matcher.getMatchingBlocks(); + } + + /** + * Return list of 5-tuples describing how to turn a into b + */ + getOpcodes(): OpCodeTuple[] { + return this.matcher.getOpcodes(); + } + + /** + * Return a measure of sequences' similarity (0.0-1.0) + */ + ratio(): number { + return this.matcher.ratio(); + } + + /** + * Return an upper bound on ratio() relatively quickly + */ + quickRatio(): number { + return this.matcher.quickRatio(); + } + + /** + * Return an upper bound on ratio() very quickly + */ + realQuickRatio(): number { + return this.matcher.realQuickRatio(); + } +} + +/** + * Utility function for string similarity + */ +export function getStringSimilarity(a: string, b: string): number { + const matcher = new StringSequenceMatcher(null, a, b); + return matcher.ratio(); +} + +/** + * Get close string matches + */ +export function getCloseStringMatches( + word: string, + possibilities: string[], + n: number = 3, + cutoff: number = 0.6, +): string[] { + if (n <= 0) { + throw new Error("n must be greater than 0"); + } + + const matches: Array<{ string: string; ratio: number }> = []; + + for (const possibility of possibilities) { + const ratio = getStringSimilarity(word, possibility); + + if (ratio >= cutoff) { + matches.push({ string: possibility, ratio }); + } + } + + // Sort by ratio (descending) and take top n + matches.sort((a, b) => b.ratio - a.ratio); + return matches.slice(0, n).map((match) => match.string); +} diff --git a/packages/logic/domains/2fa/controller.ts b/packages/logic/domains/2fa/controller.ts new file mode 100644 index 0000000..cdf1e2b --- /dev/null +++ b/packages/logic/domains/2fa/controller.ts @@ -0,0 +1,396 @@ +import { errAsync, okAsync, ResultAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { UserRepository } from "@domains/user/repository"; +import { getRedisInstance, Redis } from "@pkg/keystore"; +import { TwofaRepository } from "./repository"; +import { logDomainEvent } from "@pkg/logger"; +import { auth } from "../auth/config.base"; +import type { TwoFaSession } from "./data"; +import { User } from "@domains/user/data"; +import { settings } from "@core/settings"; +import { type Err } from "@pkg/result"; +import { twofaErrors } from "./errors"; +import { db } from "@pkg/db"; + +export class TwofaController { + constructor( + private twofaRepo: TwofaRepository, + private userRepo: UserRepository, + private store: Redis, + private secret: string, + ) {} + + checkTotp(secret: string, code: string) { + return this.twofaRepo.checkTotp(secret, code); + } + + is2faEnabled(fctx: FlowExecCtx, userId: string) { + return this.twofaRepo + .getUsers2FAInfo(fctx, userId, true) + .map((data) => !!data) + .orElse(() => okAsync(false)); + } + + isUserBanned(fctx: FlowExecCtx, userId: string) { + return this.userRepo.isUserBanned(fctx, userId).orElse((error) => { + logDomainEvent({ + level: "error", + event: "security.twofa.user_ban_check.failed", + fctx, + error, + meta: { userId }, + }); + return okAsync(false); + }); + } + + setup2FA(fctx: FlowExecCtx, user: User) { + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => + enabled + ? errAsync(twofaErrors.alreadyEnabled(fctx)) + : this.twofaRepo.setup(fctx, user.id, this.secret), + ) + .map((secret) => { + const appName = settings.appName; + const totpUri = `otpauth://totp/${appName}:${user.email}?secret=${secret}&issuer=${appName}`; + return { totpURI: totpUri, secret }; + }); + } + + verifyAndEnable2FA( + fctx: FlowExecCtx, + user: User, + code: string, + headers: Headers, + ) { + const startedAt = Date.now(); + logDomainEvent({ + event: "security.twofa.verify_and_enable.started", + fctx, + meta: { userId: user.id }, + }); + + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (enabled) { + logDomainEvent({ + level: "warn", + event: "security.twofa.verify_and_enable.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "ALREADY_ENABLED", + message: "2FA already enabled", + }, + meta: { userId: user.id }, + }); + return errAsync(twofaErrors.alreadyEnabled(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => + this.twofaRepo.verifyAndEnable2FA(fctx, user.id, code), + ) + .andThen((verified) => { + if (verified) { + return ResultAsync.combine([ + ResultAsync.fromPromise( + auth.api.revokeOtherSessions({ headers }), + () => twofaErrors.revokeSessionsFailed(fctx), + ), + this.userRepo.updateLastVerified2FaAtToNow( + fctx, + user.id, + ), + ]).map(() => { + logDomainEvent({ + event: "security.twofa.verify_and_enable.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId: user.id }, + }); + return true; + }); + } + logDomainEvent({ + level: "warn", + event: "security.twofa.verify_and_enable.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "INVALID_CODE", + message: "2FA code verification failed", + }, + meta: { userId: user.id }, + }); + return okAsync(verified); + }); + } + + disable(fctx: FlowExecCtx, user: User, code: string) { + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (!enabled) { + return errAsync(twofaErrors.notEnabled(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => this.twofaRepo.get2FASecret(fctx, user.id)) + .andThen((secret) => { + if (!secret) { + return errAsync(twofaErrors.invalidSetup(fctx)); + } + if (!this.checkTotp(secret, code)) { + return errAsync(twofaErrors.invalidCode(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => this.twofaRepo.disable(fctx, user.id)); + } + + generateBackupCodes(fctx: FlowExecCtx, user: User) { + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (!enabled) { + return errAsync(twofaErrors.notEnabled(fctx)); + } + return okAsync(undefined); + }) + .andThen(() => this.twofaRepo.generateBackupCodes(fctx, user.id)); + } + + requiresInitialVerification( + fctx: FlowExecCtx, + user: User, + sessionId: string, + ) { + return this.is2faEnabled(fctx, user.id).andThen((enabled) => { + if (!enabled) { + return okAsync(false); + } + + return ResultAsync.fromPromise( + this.store.get(`initial_2fa_completed:${sessionId}`), + () => null, + ) + .map((completed) => !completed && completed !== "0") + .orElse(() => okAsync(true)); + }); + } + + requiresSensitiveActionVerification(fctx: FlowExecCtx, user: User) { + return this.is2faEnabled(fctx, user.id).andThen((enabled) => { + if (!enabled) { + return okAsync(false); + } + + if (!user.last2FAVerifiedAt) { + return okAsync(true); + } + + const requiredHours = settings.twofaRequiredHours || 24; + const verificationAge = + Date.now() - user.last2FAVerifiedAt.getTime(); + const maxAge = requiredHours * 60 * 60 * 1000; + + return okAsync(verificationAge > maxAge); + }); + } + + markInitialVerificationComplete(sessionId: string) { + return ResultAsync.fromPromise( + this.store.setex( + `initial_2fa_completed:${sessionId}`, + 60 * 60 * 24 * 7, + "true", + ), + () => null, + ) + .map(() => undefined) + .orElse((error) => { + logDomainEvent({ + level: "error", + event: "security.twofa.mark_initial_verification.failed", + fctx: { flowId: crypto.randomUUID() }, + error, + }); + return okAsync(undefined); + }); + } + + startVerification( + fctx: FlowExecCtx, + params: { + userId: string; + sessionId: string; + ipAddress?: string; + userAgent?: string; + }, + ) { + return this.twofaRepo.createSession(fctx, params).map((session) => ({ + verificationToken: session.verificationToken, + })); + } + + private validateSession(fctx: FlowExecCtx, session: TwoFaSession) { + if (session.status !== "pending") { + return errAsync(twofaErrors.sessionNotActive(fctx)); + } + + if (session.expiresAt < new Date()) { + return this.twofaRepo + .updateSession(fctx, session.id, { status: "expired" }) + .andThen(() => errAsync(twofaErrors.sessionExpired(fctx))); + } + + return okAsync(session); + } + + private handleMaxAttempts( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + ) { + const banExpiresAt = new Date(); + banExpiresAt.setHours(banExpiresAt.getHours() + 1); + + return this.twofaRepo + .updateSession(fctx, session.id, { status: "failed" }) + .andThen(() => + this.userRepo.banUser( + fctx, + userId, + "Too many failed 2FA verification attempts", + banExpiresAt, + ), + ) + .andThen(() => errAsync(twofaErrors.tooManyAttempts(fctx))); + } + + private checkAttemptsLimit( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + ) { + if (session.attempts >= session.maxAttempts) { + return this.handleMaxAttempts(fctx, session, userId); + } + return okAsync(session); + } + + private checkCodeReplay( + fctx: FlowExecCtx, + session: TwoFaSession, + code: string, + ): ResultAsync { + if (session.codeUsed === code) { + return this.twofaRepo + .incrementAttempts(fctx, session.id) + .andThen(() => errAsync(twofaErrors.codeReplay(fctx))); + } + return okAsync(session); + } + + private verifyTotpCode( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + code: string, + ) { + return this.twofaRepo.get2FASecret(fctx, userId).andThen((secret) => { + if (!secret) { + return errAsync(twofaErrors.invalidSetup(fctx)); + } + + if (!this.checkTotp(secret, code)) { + return this.twofaRepo + .incrementAttempts(fctx, session.id) + .andThen(() => errAsync(twofaErrors.invalidCode(fctx))); + } + + return okAsync(session); + }); + } + + private completeVerification( + fctx: FlowExecCtx, + session: TwoFaSession, + userId: string, + code: string, + ) { + return this.twofaRepo + .updateSession(fctx, session.id, { + status: "verified", + verifiedAt: new Date(), + codeUsed: code, + }) + .andThen(() => + ResultAsync.combine([ + this.userRepo.updateLastVerified2FaAtToNow(fctx, userId), + this.markInitialVerificationComplete(session.sessionId), + ]), + ) + .map(() => undefined); + } + + verifyCode( + fctx: FlowExecCtx, + params: { verificationSessToken: string; code: string }, + user?: User, + ) { + if (!user) { + return errAsync(twofaErrors.userNotFound(fctx)); + } + + return this.is2faEnabled(fctx, user.id) + .andThen((enabled) => { + if (!enabled) { + return errAsync( + twofaErrors.notEnabledForVerification(fctx), + ); + } + return okAsync(undefined); + }) + .andThen(() => + this.twofaRepo.getSessionByToken( + fctx, + params.verificationSessToken, + ), + ) + .andThen((session) => { + if (!session) { + return errAsync(twofaErrors.sessionNotFound(fctx)); + } + return okAsync(session); + }) + .andThen((session) => this.validateSession(fctx, session)) + .andThen((session) => + this.checkAttemptsLimit(fctx, session, user.id), + ) + .andThen((session) => + this.checkCodeReplay(fctx, session, params.code), + ) + .andThen((session) => + this.verifyTotpCode(fctx, session, user.id, params.code), + ) + .andThen((session) => + this.completeVerification(fctx, session, user.id, params.code), + ) + .map(() => ({ success: true })); + } + + cleanupExpiredSessions(fctx: FlowExecCtx) { + return this.twofaRepo.cleanupExpiredSessions(fctx); + } +} + +export function getTwofaController() { + const _redis = getRedisInstance(); + return new TwofaController( + new TwofaRepository(db, _redis), + new UserRepository(db), + _redis, + settings.twoFaSecret, + ); +} diff --git a/packages/logic/domains/2fa/data.ts b/packages/logic/domains/2fa/data.ts new file mode 100644 index 0000000..7e19e0a --- /dev/null +++ b/packages/logic/domains/2fa/data.ts @@ -0,0 +1,48 @@ +import * as v from "valibot"; + +export const startVerificationSchema = v.object({ + userId: v.string(), + sessionId: v.string(), +}); + +export const verifyCodeSchema = v.object({ + verificationToken: v.string(), + code: v.string(), +}); + +export const enable2FACodeSchema = v.object({ + code: v.string(), +}); + +export const disable2FASchema = v.object({ + code: v.string(), +}); + +export const twoFactorSchema = v.object({ + id: v.string(), + secret: v.string(), + backupCodes: v.array(v.string()), + userId: v.string(), + createdAt: v.date(), + updatedAt: v.date(), +}); +export type TwoFactor = v.InferOutput; + +export type TwoFaSessionStatus = "pending" | "verified" | "failed" | "expired"; + +export const twoFaSessionSchema = v.object({ + id: v.string(), + userId: v.string(), + sessionId: v.string(), + verificationToken: v.string(), + codeUsed: v.optional(v.string()), + status: v.picklist(["pending", "verified", "failed", "expired"]), + attempts: v.number(), + maxAttempts: v.number(), + verifiedAt: v.optional(v.date()), + expiresAt: v.date(), + createdAt: v.date(), + ipAddress: v.string(), + userAgent: v.string(), +}); +export type TwoFaSession = v.InferOutput; diff --git a/packages/logic/domains/2fa/errors.ts b/packages/logic/domains/2fa/errors.ts new file mode 100644 index 0000000..5777acf --- /dev/null +++ b/packages/logic/domains/2fa/errors.ts @@ -0,0 +1,180 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const twofaErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + alreadyEnabled: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "2FA already enabled", + description: "Disable it first if you want to re-enable it", + detail: "2FA already enabled", + }), + + notEnabled: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "2FA not enabled for this user", + description: "Enable 2FA to perform this action", + detail: "2FA not enabled for this user", + }), + + userNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "User not found", + description: "Session is invalid or expired", + detail: "User ID not found in database", + }), + + sessionNotActive: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Verification session is no longer active", + description: "Please request a new verification code", + detail: "Session status is not 'pending'", + }), + + sessionExpired: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Verification session has expired", + description: "Please request a new verification code", + detail: "Session expired timestamp passed", + }), + + sessionNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "Invalid or expired verification session", + description: "Your verification session has expired or is invalid", + detail: "Session not found by verification token", + }), + + tooManyAttempts: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.BANNED, + message: "Too many failed attempts", + description: + "Your account has been banned, contact us to resolve this issue", + detail: "Max attempts reached for 2FA verification", + }), + + codeReplay: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "This code has already been used", + description: "Please request a new verification code", + detail: "Code replay attempt detected", + }), + + invalidSetup: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Invalid 2FA setup found", + description: "Please contact us to resolve this issue", + detail: "Invalid 2FA data found", + }), + + invalidCode: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Invalid verification code", + description: "Please try again with the correct code", + detail: "Code is invalid", + }), + + notEnabledForVerification: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "2FA not enabled for this user", + description: + "Two-factor authentication is not enabled on your account", + detail: "User has 2FA disabled but verification attempted", + }), + + revokeSessionsFailed: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Failed to revoke sessions", + description: "Please try again later", + detail: "Failed to revoke other sessions", + }), + + // Repository errors + notFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "2FA not found", + description: "Likely not enabled, otherwise please contact us :)", + detail: "2FA not found", + }), + + setupNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.VALIDATION_ERROR, + message: "Cannot perform action", + description: "If 2FA is not enabled, please refresh and try again", + detail: "2FA setup not found", + }), + + maxAttemptsReached: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Too many failed attempts", + description: "Please refresh and try again", + detail: "Max attempts reached for session", + }), + + backupCodesNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "2FA info not found", + description: "Please setup 2FA or contact us if this is unexpected", + detail: "2FA info not found for user", + }), + + backupCodesAlreadyGenerated: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.AUTH_ERROR, + message: "Backup codes already generated", + description: + "Can only generate if not already present, or all are used up", + detail: "Backup codes already generated", + }), + + sessionNotFoundById: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "2FA session not found", + description: "The verification session may have expired", + detail: "Session ID not found in database", + }), +}; diff --git a/packages/logic/domains/2fa/repository.ts b/packages/logic/domains/2fa/repository.ts new file mode 100644 index 0000000..784b948 --- /dev/null +++ b/packages/logic/domains/2fa/repository.ts @@ -0,0 +1,695 @@ +import { errAsync, okAsync, ResultAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { hashString, verifyHash } from "@/core/hash.utils"; +import { twoFactor, twofaSessions } from "@pkg/db/schema"; +import { TwoFactor, type TwoFaSession } from "./data"; +import { crypto } from "@otplib/plugin-crypto-noble"; +import { base32 } from "@otplib/plugin-base32-scure"; +import { and, Database, eq, gt, lt } from "@pkg/db"; +import { generate, verify } from "@otplib/totp"; +import { settings } from "@core/settings"; +import type { Err } from "@pkg/result"; +import { twofaErrors } from "./errors"; +import { Redis } from "@pkg/keystore"; +import { logDomainEvent, logger } from "@pkg/logger"; +import { nanoid } from "nanoid"; + +type TwoFaSetup = { + secret: string; + lastUsedCode: string; + tries: number; +}; + +export class TwofaRepository { + private PENDING_KEY_PREFIX = "pending_enabling_2fa:"; + private EXPIRY_TIME = 60 * 20; // 20 mins + private DEFAULT_BACKUP_CODES_AMT = 8; + private MAX_SETUP_ATTEMPTS = 3; + + constructor( + private db: Database, + private store: Redis, + ) {} + + checkTotp(secret: string, code: string) { + const checked = verify({ secret, token: code, crypto, base32 }); + logger.debug("TOTP check result", { checked }); + return checked; + } + + async checkBackupCode(hash: string, code: string) { + return verifyHash({ hash, target: code }); + } + + private getKey(userId: string) { + if (userId.includes(this.PENDING_KEY_PREFIX)) { + return userId; + } + return `${this.PENDING_KEY_PREFIX}${userId}`; + } + + getUsers2FAInfo( + fctx: FlowExecCtx, + userId: string, + returnUndefined?: boolean, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "security.twofa.get_info.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db.query.twoFactor.findFirst({ + where: eq(twoFactor.userId, userId), + }), + (error) => { + logDomainEvent({ + level: "error", + event: "security.twofa.get_info.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return twofaErrors.dbError(fctx, "Failed to query 2FA info"); + }, + ).andThen((found) => { + if (!found) { + logDomainEvent({ + level: "warn", + event: "security.twofa.get_info.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { code: "NOT_FOUND", message: "2FA info not found" }, + meta: { userId }, + }); + if (returnUndefined) { + return okAsync(undefined); + } + return errAsync(twofaErrors.notFound(fctx)); + } + logDomainEvent({ + event: "security.twofa.get_info.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId }, + }); + return okAsync(found as TwoFactor); + }); + } + + isSetupPending( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.debug("Checking if 2FA setup is pending", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.store.get(this.getKey(userId)), + () => + twofaErrors.dbError( + fctx, + "Failed to check setup pending status", + ), + ).map((found) => { + const isPending = !!found; + logger.debug("Setup pending status checked", { + ...fctx, + userId, + isPending, + }); + return isPending; + }); + } + + setup( + fctx: FlowExecCtx, + userId: string, + secret: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "security.twofa.setup.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromSafePromise( + (async () => { + const token = await generate({ + secret, + crypto, + base32, + }); + const payload = { + secret: token, + lastUsedCode: "", + tries: 0, + } as TwoFaSetup; + await this.store.setex( + this.getKey(userId), + this.EXPIRY_TIME, + JSON.stringify(payload), + ); + logDomainEvent({ + event: "security.twofa.setup.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, expiresInSec: this.EXPIRY_TIME }, + }); + return secret; + })(), + ).mapErr((error) => { + logDomainEvent({ + level: "error", + event: "security.twofa.setup.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return twofaErrors.dbError(fctx, "Setting to data store failed"); + }); + } + + verifyAndEnable2FA( + fctx: FlowExecCtx, + userId: string, + code: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "security.twofa.verify_enable.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.store.get(this.getKey(userId)), + () => twofaErrors.dbError(fctx, "Failed to get setup session"), + ) + .andThen((payload) => { + if (!payload) { + logDomainEvent({ + level: "warn", + event: "security.twofa.verify_enable.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "SETUP_NOT_FOUND", + message: "2FA setup session not found", + }, + meta: { userId }, + }); + return errAsync(twofaErrors.setupNotFound(fctx)); + } + return okAsync(JSON.parse(payload) as TwoFaSetup); + }) + .andThen((payloadObj) => { + const key = this.getKey(userId); + + if (payloadObj.tries >= this.MAX_SETUP_ATTEMPTS) { + logDomainEvent({ + level: "warn", + event: "security.twofa.verify_enable.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "MAX_ATTEMPTS_REACHED", + message: "Max setup attempts reached", + }, + meta: { userId, attempts: payloadObj.tries }, + }); + return ResultAsync.fromPromise(this.store.del(key), () => + twofaErrors.dbError( + fctx, + "Failed to delete setup session", + ), + ).andThen(() => + errAsync(twofaErrors.maxAttemptsReached(fctx)), + ); + } + + if ( + !this.checkTotp(payloadObj.secret, code) || + code === payloadObj.lastUsedCode + ) { + logDomainEvent({ + level: "warn", + event: "security.twofa.verify_enable.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "INVALID_CODE", + message: "Invalid or replayed setup code", + }, + meta: { + userId, + attempts: payloadObj.tries + 1, + codeReused: code === payloadObj.lastUsedCode, + }, + }); + return ResultAsync.fromPromise( + this.store.setex( + key, + this.EXPIRY_TIME, + JSON.stringify({ + secret: payloadObj.secret, + lastUsedCode: code, + tries: payloadObj.tries + 1, + }), + ), + () => + twofaErrors.dbError( + fctx, + "Failed to update setup session", + ), + ).map(() => false); + } + + logger.info("2FA code verified successfully, enabling 2FA", { + ...fctx, + userId, + }); + + return ResultAsync.fromPromise(this.store.del(key), () => + twofaErrors.dbError(fctx, "Failed to delete setup session"), + ) + .andThen(() => + ResultAsync.fromPromise( + this.db + .insert(twoFactor) + .values({ + id: nanoid(), + secret: payloadObj.secret, + userId: userId, + createdAt: new Date(), + updatedAt: new Date(), + }) + .execute(), + () => + twofaErrors.dbError( + fctx, + "Failed to insert 2FA record", + ), + ), + ) + .map(() => { + logDomainEvent({ + event: "security.twofa.verify_enable.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId }, + }); + return true; + }); + }); + } + + disable(fctx: FlowExecCtx, userId: string): ResultAsync { + logger.info("Disabling 2FA", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db + .delete(twoFactor) + .where(eq(twoFactor.userId, userId)) + .execute(), + () => twofaErrors.dbError(fctx, "Failed to delete 2FA record"), + ).map((result) => { + logger.info("2FA disabled successfully", { ...fctx, userId }); + return true; + }); + } + + generateBackupCodes( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.info("Generating backup codes", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db.query.twoFactor.findFirst({ + where: eq(twoFactor.userId, userId), + }), + () => twofaErrors.dbError(fctx, "Failed to query 2FA info"), + ) + .andThen((found) => { + if (!found) { + logger.error("2FA not enabled for user", { + ...fctx, + userId, + }); + return errAsync(twofaErrors.backupCodesNotFound(fctx)); + } + if (found.backupCodes && found.backupCodes.length) { + logger.warn("Backup codes already generated", { + ...fctx, + userId, + }); + return errAsync( + twofaErrors.backupCodesAlreadyGenerated(fctx), + ); + } + return okAsync(found); + }) + .andThen(() => { + const codes = Array.from( + { length: this.DEFAULT_BACKUP_CODES_AMT }, + () => nanoid(12), + ); + + logger.debug("Backup codes generated, hashing", { + ...fctx, + userId, + count: codes.length, + }); + + return ResultAsync.fromPromise( + (async () => { + const hashed = []; + for (const code of codes) { + const hash = await hashString(code); + hashed.push(hash); + } + return { codes, hashed }; + })(), + () => + twofaErrors.dbError( + fctx, + "Failed to hash backup codes", + ), + ).andThen(({ codes, hashed }) => + ResultAsync.fromPromise( + this.db + .update(twoFactor) + .set({ backupCodes: hashed }) + .where(eq(twoFactor.userId, userId)) + .returning(), + () => + twofaErrors.dbError( + fctx, + "Failed to update backup codes", + ), + ).map(() => { + logger.info("Backup codes generated successfully", { + ...fctx, + userId, + }); + return codes; + }), + ); + }); + } + + get2FASecret( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + logger.debug("Getting 2FA secret", { ...fctx, userId }); + + return ResultAsync.fromPromise( + this.db + .select() + .from(twoFactor) + .where(eq(twoFactor.userId, userId)) + .limit(1), + () => twofaErrors.dbError(fctx, "Failed to query 2FA secret"), + ).map((result) => { + if (!result.length) { + logger.debug("No 2FA secret found", { ...fctx, userId }); + return null; + } + logger.debug("2FA secret retrieved", { ...fctx, userId }); + return result[0].secret; + }); + } + + createSession( + fctx: FlowExecCtx, + params: { + userId: string; + sessionId: string; + ipAddress?: string; + userAgent?: string; + }, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "security.twofa.create_session.started", + fctx, + meta: { userId: params.userId, sessionId: params.sessionId }, + }); + + return ResultAsync.fromSafePromise( + (async () => { + const expiryMinutes = settings.twofaSessionExpiryMinutes || 10; + const now = new Date(); + const expiresAt = new Date( + now.getTime() + expiryMinutes * 60 * 1000, + ); + + return { expiresAt, now, params }; + })(), + ).andThen(({ expiresAt, now, params }) => + ResultAsync.fromPromise( + this.db + .insert(twofaSessions) + .values({ + id: nanoid(), + userId: params.userId, + sessionId: params.sessionId, + verificationToken: nanoid(32), + status: "pending", + attempts: 0, + maxAttempts: 5, + expiresAt, + createdAt: now, + ipAddress: params.ipAddress, + userAgent: params.userAgent, + }) + .returning(), + (error) => { + logDomainEvent({ + level: "error", + event: "security.twofa.create_session.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId: params.userId }, + }); + return twofaErrors.dbError( + fctx, + "Failed to create 2FA session", + ); + }, + ).map(([session]) => { + logDomainEvent({ + event: "security.twofa.create_session.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { + twofaSessionId: session.id, + userId: params.userId, + }, + }); + return session as TwoFaSession; + }), + ); + } + + getSessionByToken( + fctx: FlowExecCtx, + token: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + level: "debug", + event: "security.twofa.get_session.started", + fctx, + }); + + return ResultAsync.fromPromise( + this.db + .select() + .from(twofaSessions) + .where( + and( + eq(twofaSessions.verificationToken, token), + gt(twofaSessions.expiresAt, new Date()), + ), + ) + .limit(1), + (error) => { + logDomainEvent({ + level: "error", + event: "security.twofa.get_session.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return twofaErrors.dbError(fctx, "Failed to query 2FA session"); + }, + ).map((result) => { + if (!result.length) { + logDomainEvent({ + level: "warn", + event: "security.twofa.get_session.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { + code: "SESSION_NOT_FOUND", + message: "2FA session not found or expired", + }, + }); + return null; + } + logDomainEvent({ + level: "debug", + event: "security.twofa.get_session.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { twofaSessionId: result[0].id }, + }); + return result[0] as TwoFaSession; + }); + } + + updateSession( + fctx: FlowExecCtx, + id: string, + updates: Partial< + Pick< + TwoFaSession, + "status" | "attempts" | "verifiedAt" | "codeUsed" + > + >, + ): ResultAsync { + logger.debug("Updating 2FA session", { + ...fctx, + sessionId: id, + updates, + }); + + return ResultAsync.fromPromise( + this.db + .update(twofaSessions) + .set(updates) + .where(eq(twofaSessions.id, id)) + .returning(), + () => twofaErrors.dbError(fctx, "Failed to update 2FA session"), + ).andThen(([session]) => { + if (!session) { + logger.error("2FA session not found for update", { + ...fctx, + sessionId: id, + }); + return errAsync(twofaErrors.sessionNotFoundById(fctx)); + } + logger.debug("2FA session updated successfully", { + ...fctx, + sessionId: id, + }); + return okAsync(session as TwoFaSession); + }); + } + + incrementAttempts( + fctx: FlowExecCtx, + id: string, + ): ResultAsync { + logger.debug("Incrementing session attempts", { + ...fctx, + sessionId: id, + }); + + return ResultAsync.fromPromise( + this.db.query.twofaSessions.findFirst({ + where: eq(twofaSessions.id, id), + columns: { id: true, attempts: true }, + }), + () => + twofaErrors.dbError( + fctx, + "Failed to query session for increment", + ), + ) + .andThen((s) => { + if (!s) { + logger.error("Session not found for increment", { + ...fctx, + sessionId: id, + }); + return errAsync(twofaErrors.sessionNotFoundById(fctx)); + } + return okAsync(s); + }) + .andThen((s) => + ResultAsync.fromPromise( + this.db + .update(twofaSessions) + .set({ attempts: s.attempts + 1 }) + .where(eq(twofaSessions.id, id)) + .returning(), + () => + twofaErrors.dbError( + fctx, + "Failed to increment attempts", + ), + ).andThen(([session]) => { + if (!session) { + logger.error("Session not found after increment", { + ...fctx, + sessionId: id, + }); + return errAsync(twofaErrors.sessionNotFoundById(fctx)); + } + + logger.warn("Failed verification attempt", { + ...fctx, + sessionId: session.id, + attempts: session.attempts, + }); + + return okAsync(session as TwoFaSession); + }), + ); + } + + cleanupExpiredSessions(fctx: FlowExecCtx): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "security.twofa.cleanup_expired.started", + fctx, + }); + + return ResultAsync.fromPromise( + this.db + .delete(twofaSessions) + .where(lt(twofaSessions.expiresAt, new Date())), + (error) => { + logDomainEvent({ + level: "error", + event: "security.twofa.cleanup_expired.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return twofaErrors.dbError( + fctx, + "Failed to cleanup expired sessions", + ); + }, + ).map((result) => { + const count = result.length || 0; + logDomainEvent({ + event: "security.twofa.cleanup_expired.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { count }, + }); + return count; + }); + } +} diff --git a/packages/logic/domains/2fa/sensitive-actions.ts b/packages/logic/domains/2fa/sensitive-actions.ts new file mode 100644 index 0000000..54d4315 --- /dev/null +++ b/packages/logic/domains/2fa/sensitive-actions.ts @@ -0,0 +1,43 @@ +import { FlowExecCtx } from "@core/flow.execution.context"; +import { getTwofaController } from "./controller"; +import type { User } from "@/domains/user/data"; + +const twofaController = getTwofaController(); + +/** + * Check if user needs 2FA verification for sensitive actions + * Call this before executing sensitive operations like: + * - Changing password + * - Viewing billing info + * - Deleting account + * - etc. + */ +export async function requiresSensitiveAction2FA( + fctx: FlowExecCtx, + user: User, +): Promise { + const result = await twofaController.requiresSensitiveActionVerification( + fctx, + user, + ); + return result.match( + (data) => data, + () => true, // On error, require verification for security + ); +} + +export async function checkInitial2FaRequired( + fctx: FlowExecCtx, + user: User, + sessionId: string, +): Promise { + const result = await twofaController.requiresInitialVerification( + fctx, + user, + sessionId, + ); + return result.match( + (data) => data, + () => true, + ); +} diff --git a/packages/logic/domains/auth/config.base.ts b/packages/logic/domains/auth/config.base.ts new file mode 100644 index 0000000..91ea862 --- /dev/null +++ b/packages/logic/domains/auth/config.base.ts @@ -0,0 +1,99 @@ +import { + admin, + customSession, + multiSession, + username, +} from "better-auth/plugins"; +import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { UserRoleMap } from "@domains/user/data"; +import { getRedisInstance } from "@pkg/keystore"; +import { settings } from "@core/settings"; +import { betterAuth } from "better-auth"; +import { logger } from "@pkg/logger"; +import { db, schema } from "@pkg/db"; + +const COOKIE_CACHE_MAX_AGE = 60 * 5; +const USERNAME_REGEX = /^[a-zA-Z0-9_]+$/; + +export const auth = betterAuth({ + trustedOrigins: ["http://localhost:5173", settings.betterAuthUrl], + advanced: { useSecureCookies: settings.nodeEnv === "production" }, + appName: settings.appName, + emailAndPassword: { + enabled: true, + disableSignUp: true, + requireEmailVerification: false, + }, + plugins: [ + customSession(async ({ user, session }) => { + session.id = session.token; + return { user, session }; + }), + username({ + minUsernameLength: 5, + maxUsernameLength: 20, + usernameValidator: async (username) => { + return USERNAME_REGEX.test(username); + }, + }), + admin({ + defaultRole: UserRoleMap.admin, + defaultBanReason: + "Stop fanum taxing the server bub, losing aura points fr", + defaultBanExpiresIn: 60 * 60 * 24, + }), + multiSession({ maximumSessions: 5 }), + ], + logger: { + log: (level, message, metadata) => { + logger.log(level, message, metadata); + }, + level: "debug", + }, + database: drizzleAdapter(db, { provider: "pg", schema: { ...schema } }), + secondaryStorage: { + get: async (key) => { + const redis = getRedisInstance(); + return await redis.get(key); + }, + set: async (key, value, ttl) => { + const redis = getRedisInstance(); + if (ttl) { + await redis.setex(key, ttl, value); + } else { + await redis.set(key, value); + } + }, + delete: async (key) => { + const redis = getRedisInstance(); + const out = await redis.del(key); + if (!out && out !== 0) { + return null; + } + return out.toString() as any; + }, + }, + session: { + modelName: "session", + expiresIn: 60 * 60 * 24 * 7, + updateAge: 60 * 60 * 24, + cookieCache: { + enabled: true, + maxAge: COOKIE_CACHE_MAX_AGE, + }, + }, + user: { + modelName: "user", + additionalFields: { + onboardingDone: { + type: "boolean", + defaultValue: false, + required: false, + }, + last2FAVerifiedAt: { type: "date", required: false }, + parentId: { required: false, type: "string" }, + }, + }, +}); + +// - - - diff --git a/packages/logic/domains/auth/controller.ts b/packages/logic/domains/auth/controller.ts new file mode 100644 index 0000000..3b5be9d --- /dev/null +++ b/packages/logic/domains/auth/controller.ts @@ -0,0 +1,60 @@ +import { AuthContext, MiddlewareContext, MiddlewareOptions } from "better-auth"; +import { AccountRepository } from "../user/account.repository"; +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ResultAsync } from "neverthrow"; +import { authErrors } from "./errors"; +import { logger } from "@pkg/logger"; +import { nanoid } from "nanoid"; +import { db } from "@pkg/db"; + +export class AuthController { + constructor(private accountRepo: AccountRepository) {} + + swapAccountPasswordForTwoFactor( + fctx: FlowExecCtx, + ctx: MiddlewareContext< + MiddlewareOptions, + AuthContext & { returned?: unknown; responseHeaders?: Headers } + >, + ) { + logger.info("Swapping account password for 2FA", { + ...fctx, + }); + + if (!ctx.path.includes("two-factor")) { + return ResultAsync.fromSafePromise(Promise.resolve(ctx)); + } + + if (!ctx.body.password || ctx.body.password.length === 0) { + return ResultAsync.fromSafePromise(Promise.resolve(ctx)); + } + + logger.info("Rotating password for 2FA setup for user", { + ...fctx, + userId: ctx.body.userId, + }); + + return this.accountRepo + .rotatePassword(fctx, ctx.body.userId, nanoid()) + .mapErr((err) => { + logger.error("Failed to rotate password for 2FA", { + ...fctx, + error: err, + }); + return authErrors.passwordRotationFailed(fctx, err.detail); + }) + .map((newPassword) => { + logger.info("Password rotated successfully for 2FA setup", { + ...fctx, + }); + return { + ...ctx, + body: { ...ctx.body, password: newPassword }, + }; + }); + } +} + +export function getAuthController(): AuthController { + return new AuthController(new AccountRepository(db)); +} diff --git a/packages/logic/domains/auth/errors.ts b/packages/logic/domains/auth/errors.ts new file mode 100644 index 0000000..39104fa --- /dev/null +++ b/packages/logic/domains/auth/errors.ts @@ -0,0 +1,32 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { getError } from "@pkg/logger"; +import { ERROR_CODES, type Err } from "@pkg/result"; + +export const authErrors = { + passwordRotationFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.INTERNAL_SERVER_ERROR, + message: "Failed to begin 2FA setup", + description: "An error occurred while rotating the password for 2FA", + detail, + }), + + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + accountNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "Account not found", + description: "Please try again later", + detail: "Account not found for user", + }), +}; diff --git a/packages/logic/domains/notifications/controller.ts b/packages/logic/domains/notifications/controller.ts new file mode 100644 index 0000000..82c5bb9 --- /dev/null +++ b/packages/logic/domains/notifications/controller.ts @@ -0,0 +1,96 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { okAsync } from "neverthrow"; +import { + NotificationFilters, + PaginationOptions, +} from "./data"; +import { NotificationRepository } from "./repository"; +import { db } from "@pkg/db"; + +export class NotificationController { + constructor(private notifsRepo: NotificationRepository) {} + + getNotifications( + fctx: FlowExecCtx, + filters: NotificationFilters, + pagination: PaginationOptions, + ) { + return this.notifsRepo.getNotifications(fctx, filters, pagination); + } + + markAsRead( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.markAsRead(fctx, notificationIds, userId); + } + + markAsUnread( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.markAsUnread(fctx, notificationIds, userId); + } + + archive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.archive(fctx, notificationIds, userId); + } + + unarchive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.unarchive(fctx, notificationIds, userId); + } + + deleteNotifications( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ) { + return this.notifsRepo.deleteNotifications(fctx, notificationIds, userId); + } + + getUnreadCount( + fctx: FlowExecCtx, + userId: string, + ) { + return this.notifsRepo.getUnreadCount(fctx, userId); + } + + markAllAsRead( + fctx: FlowExecCtx, + userId: string, + ) { + // Get all unread notification IDs for this user + const filters: NotificationFilters = { + userId, + isRead: false, + isArchived: false, + }; + + // Get a large number to handle bulk operations + const pagination: PaginationOptions = { page: 1, pageSize: 1000 }; + + return this.notifsRepo + .getNotifications(fctx, filters, pagination) + .map((paginated) => paginated.data.map((n) => n.id)) + .andThen((notificationIds) => { + if (notificationIds.length === 0) { + return okAsync(true); + } + return this.notifsRepo.markAsRead(fctx, notificationIds, userId); + }); + } +} + +export function getNotificationController(): NotificationController { + return new NotificationController(new NotificationRepository(db)); +} diff --git a/packages/logic/domains/notifications/data.ts b/packages/logic/domains/notifications/data.ts new file mode 100644 index 0000000..0f584f7 --- /dev/null +++ b/packages/logic/domains/notifications/data.ts @@ -0,0 +1,115 @@ +import * as v from "valibot"; + +// Notification schema +export const notificationSchema = v.object({ + id: v.pipe(v.number(), v.integer()), + title: v.string(), + body: v.string(), + priority: v.string(), + type: v.string(), + category: v.string(), + isRead: v.boolean(), + isArchived: v.boolean(), + actionUrl: v.string(), + actionType: v.string(), + actionData: v.string(), + icon: v.string(), + userId: v.string(), + sentAt: v.date(), + readAt: v.nullable(v.date()), + expiresAt: v.nullable(v.date()), + createdAt: v.date(), + updatedAt: v.date(), +}); + +export type Notification = v.InferOutput; +export type Notifications = Notification[]; + +// Notification filters schema +export const notificationFiltersSchema = v.object({ + userId: v.string(), + isRead: v.optional(v.boolean()), + isArchived: v.optional(v.boolean()), + type: v.optional(v.string()), + category: v.optional(v.string()), + priority: v.optional(v.string()), + search: v.optional(v.string()), +}); +export type NotificationFilters = v.InferOutput< + typeof notificationFiltersSchema +>; + +export type NotificationsQueryInput = { + isRead?: boolean; + isArchived?: boolean; + type?: string; + category?: string; + priority?: string; + search?: string; + page?: number; + pageSize?: number; + sortBy?: string; + sortOrder?: string; +}; + +// Pagination options schema +export const paginationOptionsSchema = v.object({ + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + sortBy: v.optional(v.string()), + sortOrder: v.optional(v.string()), +}); +export type PaginationOptions = v.InferOutput; + +// Paginated notifications schema +export const paginatedNotificationsSchema = v.object({ + data: v.array(notificationSchema), + total: v.pipe(v.number(), v.integer()), + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + totalPages: v.pipe(v.number(), v.integer()), +}); +export type PaginatedNotifications = v.InferOutput< + typeof paginatedNotificationsSchema +>; + +// Get notifications schema +export const getNotificationsSchema = v.object({ + filters: notificationFiltersSchema, + pagination: paginationOptionsSchema, +}); +export type GetNotifications = v.InferOutput; + +// Bulk notification IDs schema +export const bulkNotificationIdsSchema = v.object({ + notificationIds: v.array(v.pipe(v.number(), v.integer())), +}); +export type BulkNotificationIds = v.InferOutput< + typeof bulkNotificationIdsSchema +>; + +// View Model specific types +export const clientNotificationFiltersSchema = v.object({ + userId: v.string(), + isRead: v.optional(v.boolean()), + isArchived: v.optional(v.boolean()), + type: v.optional(v.string()), + category: v.optional(v.string()), + priority: v.optional(v.string()), + search: v.optional(v.string()), +}); +export type ClientNotificationFilters = v.InferOutput< + typeof clientNotificationFiltersSchema +>; + +export const clientPaginationStateSchema = v.object({ + page: v.pipe(v.number(), v.integer()), + pageSize: v.pipe(v.number(), v.integer()), + total: v.pipe(v.number(), v.integer()), + totalPages: v.pipe(v.number(), v.integer()), + sortBy: v.picklist(["createdAt", "sentAt", "readAt", "priority"]), + sortOrder: v.picklist(["asc", "desc"]), +}); +export type ClientPaginationState = v.InferOutput< + typeof clientPaginationStateSchema +>; diff --git a/packages/logic/domains/notifications/errors.ts b/packages/logic/domains/notifications/errors.ts new file mode 100644 index 0000000..d924146 --- /dev/null +++ b/packages/logic/domains/notifications/errors.ts @@ -0,0 +1,78 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const notificationErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + getNotificationsFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to fetch notifications", + description: "Please try again later", + detail, + }), + + markAsReadFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to mark notifications as read", + description: "Please try again later", + detail, + }), + + markAsUnreadFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to mark notifications as unread", + description: "Please try again later", + detail, + }), + + archiveFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to archive notifications", + description: "Please try again later", + detail, + }), + + unarchiveFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to unarchive notifications", + description: "Please try again later", + detail, + }), + + deleteNotificationsFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to delete notifications", + description: "Please try again later", + detail, + }), + + getUnreadCountFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to get unread count", + description: "Please try again later", + detail, + }), +}; + diff --git a/packages/logic/domains/notifications/repository.ts b/packages/logic/domains/notifications/repository.ts new file mode 100644 index 0000000..8578da3 --- /dev/null +++ b/packages/logic/domains/notifications/repository.ts @@ -0,0 +1,453 @@ +import { and, asc, count, Database, desc, eq, like, or, sql } from "@pkg/db"; +import { notifications } from "@pkg/db/schema"; +import { ResultAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import type { + Notification, + NotificationFilters, + PaginatedNotifications, + PaginationOptions, +} from "./data"; +import { type Err } from "@pkg/result"; +import { notificationErrors } from "./errors"; +import { logDomainEvent } from "@pkg/logger"; + +export class NotificationRepository { + constructor(private db: Database) {} + + getNotifications( + fctx: FlowExecCtx, + filters: NotificationFilters, + pagination: PaginationOptions, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "notifications.list.started", + fctx, + meta: { + hasSearch: Boolean(filters.search), + isRead: filters.isRead, + isArchived: filters.isArchived, + page: pagination.page, + pageSize: pagination.pageSize, + sortBy: pagination.sortBy, + sortOrder: pagination.sortOrder, + }, + }); + + const { userId, isRead, isArchived, type, category, priority, search } = + filters; + const { + page, + pageSize, + sortBy = "createdAt", + sortOrder = "desc", + } = pagination; + + // Build WHERE conditions + const conditions = [eq(notifications.userId, userId)]; + + if (isRead !== undefined) { + conditions.push(eq(notifications.isRead, isRead)); + } + + if (isArchived !== undefined) { + conditions.push(eq(notifications.isArchived, isArchived)); + } + + if (type) { + conditions.push(eq(notifications.type, type)); + } + + if (category) { + conditions.push(eq(notifications.category, category)); + } + + if (priority) { + conditions.push(eq(notifications.priority, priority)); + } + + if (search) { + conditions.push( + or( + like(notifications.title, `%${search}%`), + like(notifications.body, `%${search}%`), + )!, + ); + } + + const whereClause = and(...conditions); + + return ResultAsync.fromPromise( + this.db.select({ count: count() }).from(notifications).where(whereClause), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.list.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.getNotificationsFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((totalResult) => { + const total = totalResult[0]?.count || 0; + const offset = (page - 1) * pageSize; + + // Map sortBy to proper column + const getOrderColumn = (sortBy: string) => { + switch (sortBy) { + case "createdAt": + return notifications.createdAt; + case "sentAt": + return notifications.sentAt; + case "readAt": + return notifications.readAt; + case "priority": + return notifications.priority; + default: + return notifications.createdAt; + } + }; + + const orderColumn = getOrderColumn(sortBy); + const orderFunc = sortOrder === "asc" ? asc : desc; + + return ResultAsync.fromPromise( + this.db + .select() + .from(notifications) + .where(whereClause) + .orderBy(orderFunc(orderColumn)) + .limit(pageSize) + .offset(offset), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.list.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.getNotificationsFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((data) => { + const totalPages = Math.ceil(total / pageSize); + logDomainEvent({ + event: "notifications.list.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { + count: data.length, + page, + totalPages, + }, + }); + + return { + data: data as Notification[], + total, + page, + pageSize, + totalPages, + }; + }); + }); + } + + markAsRead( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "notifications.mark_read.started", + fctx, + meta: { userId, notificationCount: notificationIds.length }, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isRead: true, + readAt: new Date(), + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.mark_read.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.markAsReadFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "notifications.mark_read.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { notificationCount: notificationIds.length }, + }); + return true; + }); + } + + markAsUnread( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "notifications.mark_unread.started", + fctx, + meta: { userId, notificationCount: notificationIds.length }, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isRead: false, + readAt: null, + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.mark_unread.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.markAsUnreadFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "notifications.mark_unread.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { notificationCount: notificationIds.length }, + }); + return true; + }); + } + + archive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "notifications.archive.started", + fctx, + meta: { userId, notificationCount: notificationIds.length }, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isArchived: true, + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.archive.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.archiveFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "notifications.archive.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { notificationCount: notificationIds.length }, + }); + return true; + }); + } + + unarchive( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "notifications.unarchive.started", + fctx, + meta: { userId, notificationCount: notificationIds.length }, + }); + + return ResultAsync.fromPromise( + this.db + .update(notifications) + .set({ + isArchived: false, + updatedAt: new Date(), + }) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.unarchive.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.unarchiveFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "notifications.unarchive.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { notificationCount: notificationIds.length }, + }); + return true; + }); + } + + deleteNotifications( + fctx: FlowExecCtx, + notificationIds: number[], + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "notifications.delete.started", + fctx, + meta: { userId, notificationCount: notificationIds.length }, + }); + + return ResultAsync.fromPromise( + this.db + .delete(notifications) + .where( + and( + eq(notifications.userId, userId), + sql`${notifications.id} = ANY(${notificationIds})`, + ), + ), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.delete.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.deleteNotificationsFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "notifications.delete.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { notificationCount: notificationIds.length }, + }); + return true; + }); + } + + getUnreadCount( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + const startedAt = Date.now(); + logDomainEvent({ + event: "notifications.unread_count.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db + .select({ count: count() }) + .from(notifications) + .where( + and( + eq(notifications.userId, userId), + eq(notifications.isRead, false), + eq(notifications.isArchived, false), + ), + ), + (error) => { + logDomainEvent({ + level: "error", + event: "notifications.unread_count.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return notificationErrors.getUnreadCountFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((result) => { + const count = result[0]?.count || 0; + logDomainEvent({ + event: "notifications.unread_count.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { count }, + }); + return count; + }); + } +} diff --git a/packages/logic/domains/tasks/controller.ts b/packages/logic/domains/tasks/controller.ts new file mode 100644 index 0000000..f51382a --- /dev/null +++ b/packages/logic/domains/tasks/controller.ts @@ -0,0 +1,72 @@ +import { db } from "@pkg/db"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { CreateTask, TaskStatus, TaskType, UpdateTask } from "./data"; +import { TasksRepository } from "./repository"; + +export class TasksController { + constructor(private tasksRepo: TasksRepository) {} + + createTask(fctx: FlowExecCtx, taskData: CreateTask) { + return this.tasksRepo.createTask(fctx, taskData); + } + + getTaskById(fctx: FlowExecCtx, taskId: string) { + return this.tasksRepo.getTaskById(fctx, taskId); + } + + updateTask(fctx: FlowExecCtx, taskId: string, updates: UpdateTask) { + return this.tasksRepo.updateTask(fctx, taskId, updates); + } + + deleteTask(fctx: FlowExecCtx, taskId: string) { + return this.tasksRepo.deleteTask(fctx, taskId); + } + + getTasksByStatuses(fctx: FlowExecCtx, statuses: TaskStatus[]) { + return this.tasksRepo.getTasksByStatuses(fctx, statuses); + } + + getTasksByTypeAndStatuses( + fctx: FlowExecCtx, + type: TaskType, + statuses: TaskStatus[], + ) { + return this.tasksRepo.getTasksByTypeAndStatuses(fctx, type, statuses); + } + + markTaskAsCompleted( + fctx: FlowExecCtx, + taskId: string, + result?: Record, + ) { + return this.tasksRepo.markTaskAsCompleted(fctx, taskId, result); + } + + markTaskAsFailed(fctx: FlowExecCtx, taskId: string, error: any) { + return this.tasksRepo.markTaskAsFailed(fctx, taskId, error); + } + + updateTaskProgress(fctx: FlowExecCtx, taskId: string, progress: number) { + return this.tasksRepo.updateTask(fctx, taskId, { + progress: Math.max(0, Math.min(100, progress)), + }); + } + + cancelTask(fctx: FlowExecCtx, taskId: string) { + return this.tasksRepo.updateTask(fctx, taskId, { + status: TaskStatus.CANCELLED, + completedAt: new Date(), + }); + } + + startTask(fctx: FlowExecCtx, taskId: string) { + return this.tasksRepo.updateTask(fctx, taskId, { + status: TaskStatus.RUNNING, + startedAt: new Date(), + }); + } +} + +export function getTasksController(): TasksController { + return new TasksController(new TasksRepository(db)); +} diff --git a/packages/logic/domains/tasks/data.ts b/packages/logic/domains/tasks/data.ts new file mode 100644 index 0000000..a66278a --- /dev/null +++ b/packages/logic/domains/tasks/data.ts @@ -0,0 +1,71 @@ +import * as v from "valibot"; + +export enum TaskStatus { + PENDING = "pending", + RUNNING = "running", + COMPLETED = "completed", + FAILED = "failed", + CANCELLED = "cancelled", +} + +export const taskStatusSchema = v.picklist([ + "pending", + "running", + "completed", + "failed", + "cancelled", +]); +export type TaskStatusType = v.InferOutput; + +export enum TaskType { + APK_BUILD = "apk_build", +} + +export const taskTypeSchema = v.picklist(["apk_build"]); +export type TaskTypeValue = v.InferOutput; + +export const taskErrorSchema = v.object({ + code: v.string(), + message: v.string(), + detail: v.optional(v.string()), + timestamp: v.date(), +}); +export type TaskError = v.InferOutput; + +export const taskSchema = v.object({ + id: v.string(), + type: taskTypeSchema, + status: taskStatusSchema, + progress: v.pipe(v.number(), v.integer()), + payload: v.optional(v.nullable(v.record(v.string(), v.any()))), + result: v.optional(v.nullable(v.record(v.string(), v.any()))), + error: v.optional(v.nullable(taskErrorSchema)), + userId: v.string(), + resourceId: v.string(), + startedAt: v.optional(v.nullable(v.date())), + completedAt: v.optional(v.nullable(v.date())), + createdAt: v.date(), + updatedAt: v.date(), +}); +export type Task = v.InferOutput; + +export const createTaskSchema = v.object({ + id: v.string(), + type: taskTypeSchema, + status: v.optional(taskStatusSchema), + progress: v.optional(v.pipe(v.number(), v.integer())), + payload: v.optional(v.nullable(v.record(v.string(), v.any()))), + userId: v.string(), + resourceId: v.string(), +}); +export type CreateTask = v.InferOutput; + +export const updateTaskSchema = v.object({ + status: v.optional(taskStatusSchema), + progress: v.optional(v.pipe(v.number(), v.integer())), + result: v.optional(v.nullable(v.record(v.string(), v.any()))), + error: v.optional(v.nullable(taskErrorSchema)), + startedAt: v.optional(v.nullable(v.date())), + completedAt: v.optional(v.nullable(v.date())), +}); +export type UpdateTask = v.InferOutput; diff --git a/packages/logic/domains/tasks/errors.ts b/packages/logic/domains/tasks/errors.ts new file mode 100644 index 0000000..058954f --- /dev/null +++ b/packages/logic/domains/tasks/errors.ts @@ -0,0 +1,87 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const taskErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + taskNotFound: (fctx: FlowExecCtx, taskId: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "Task not found", + description: "The requested task does not exist", + detail: `No task found with ID: ${taskId}`, + }), + + createTaskFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while creating task", + description: "Try again later", + detail, + }), + + getTaskFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while fetching task", + description: "Try again later", + detail, + }), + + updateTaskFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while updating task", + description: "Try again later", + detail, + }), + + deleteTaskFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while deleting task", + description: "Try again later", + detail, + }), + + getTasksFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while fetching tasks", + description: "Try again later", + detail, + }), + + getTasksByStatusFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while fetching tasks by status", + description: "Try again later", + detail, + }), + + checkTaskExistenceFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while checking task existence", + description: "Try again later", + detail, + }), +}; + diff --git a/packages/logic/domains/tasks/repository.ts b/packages/logic/domains/tasks/repository.ts new file mode 100644 index 0000000..344c545 --- /dev/null +++ b/packages/logic/domains/tasks/repository.ts @@ -0,0 +1,163 @@ +import { CreateTask, Task, TaskStatus, TaskType, UpdateTask } from "./data"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { Database, and, asc, eq, inArray } from "@pkg/db"; +import { task } from "@pkg/db/schema"; +import { type Err } from "@pkg/result"; +import { taskErrors } from "./errors"; +import { logger } from "@pkg/logger"; + +export class TasksRepository { + constructor(private db: Database) {} + + createTask(fctx: FlowExecCtx, taskData: CreateTask): ResultAsync { + logger.info("Creating new task", { ...fctx, taskId: taskData.id }); + + return ResultAsync.fromPromise( + this.db + .insert(task) + .values({ + id: taskData.id, + type: taskData.type, + status: taskData.status || TaskStatus.PENDING, + progress: taskData.progress || 0, + payload: taskData.payload ?? null, + userId: taskData.userId, + resourceId: taskData.resourceId, + createdAt: new Date(), + updatedAt: new Date(), + }) + .returning() + .execute(), + (error) => + taskErrors.createTaskFailed( + fctx, + error instanceof Error ? error.message : String(error), + ), + ).map((result) => result[0] as Task); + } + + getTaskById(fctx: FlowExecCtx, taskId: string): ResultAsync { + return ResultAsync.fromPromise( + this.db.query.task.findFirst({ + where: eq(task.id, taskId), + }), + (error) => + taskErrors.getTaskFailed( + fctx, + error instanceof Error ? error.message : String(error), + ), + ).andThen((result) => { + if (!result) { + return errAsync(taskErrors.taskNotFound(fctx, taskId)); + } + + return okAsync(result as Task); + }); + } + + updateTask( + fctx: FlowExecCtx, + taskId: string, + updates: UpdateTask, + ): ResultAsync { + return this.getTaskById(fctx, taskId).andThen(() => + ResultAsync.fromPromise( + this.db + .update(task) + .set({ ...updates, updatedAt: new Date() }) + .where(eq(task.id, taskId)) + .returning() + .execute(), + (error) => + taskErrors.updateTaskFailed( + fctx, + error instanceof Error ? error.message : String(error), + ), + ).andThen((updateResult) => { + if (!updateResult[0]) { + return errAsync(taskErrors.taskNotFound(fctx, taskId)); + } + return okAsync(updateResult[0] as Task); + }), + ); + } + + deleteTask(fctx: FlowExecCtx, taskId: string): ResultAsync { + return ResultAsync.fromPromise( + this.db.delete(task).where(eq(task.id, taskId)).execute(), + (error) => + taskErrors.deleteTaskFailed( + fctx, + error instanceof Error ? error.message : String(error), + ), + ).map(() => true); + } + + getTasksByStatuses( + fctx: FlowExecCtx, + statuses: TaskStatus[], + ): ResultAsync { + return ResultAsync.fromPromise( + this.db + .select() + .from(task) + .where(inArray(task.status, statuses)) + .orderBy(asc(task.createdAt)), + (error) => + taskErrors.getTasksByStatusFailed( + fctx, + error instanceof Error ? error.message : String(error), + ), + ).map((result) => result as Task[]); + } + + getTasksByTypeAndStatuses( + fctx: FlowExecCtx, + type: TaskType, + statuses: TaskStatus[], + ): ResultAsync { + return ResultAsync.fromPromise( + this.db + .select() + .from(task) + .where(and(eq(task.type, type), inArray(task.status, statuses))) + .orderBy(asc(task.createdAt)), + (error) => + taskErrors.getTasksByStatusFailed( + fctx, + error instanceof Error ? error.message : String(error), + ), + ).map((result) => result as Task[]); + } + + markTaskAsCompleted( + fctx: FlowExecCtx, + taskId: string, + result?: Record, + ): ResultAsync { + return this.updateTask(fctx, taskId, { + status: TaskStatus.COMPLETED, + progress: 100, + result: result ?? null, + completedAt: new Date(), + }); + } + + markTaskAsFailed( + fctx: FlowExecCtx, + taskId: string, + error: any, + ): ResultAsync { + return this.updateTask(fctx, taskId, { + status: TaskStatus.FAILED, + error: { + code: error.code || "UNKNOWN_ERROR", + message: error.message || "Task failed", + detail: error.detail, + timestamp: new Date(), + }, + completedAt: new Date(), + }); + } +} diff --git a/packages/logic/domains/user/account.repository.ts b/packages/logic/domains/user/account.repository.ts new file mode 100644 index 0000000..91fa4d0 --- /dev/null +++ b/packages/logic/domains/user/account.repository.ts @@ -0,0 +1,250 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { traceResultAsync } from "@core/observability"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError, logDomainEvent } from "@pkg/logger"; +import { auth } from "../auth/config.base"; +import { account } from "@pkg/db/schema"; +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import { Database, eq } from "@pkg/db"; +import { nanoid } from "nanoid"; + +export class AccountRepository { + constructor(private db: Database) {} + + private dbError(fctx: FlowExecCtx, detail: string): Err { + return getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }); + } + + private accountNotFound(fctx: FlowExecCtx): Err { + return getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "Account not found", + description: "Please try again later", + detail: "Account not found for user", + }); + } + + ensureAccountExists( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.ensureAccountExists", + fctx, + attributes: { "app.user.id": userId }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "account.ensure_exists.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db.query.account.findFirst({ + where: eq(account.userId, userId), + }), + (error) => { + logDomainEvent({ + level: "error", + event: "account.ensure_exists.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return this.dbError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((existingAccount) => { + if (existingAccount) { + logDomainEvent({ + event: "account.ensure_exists.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, existed: true }, + }); + return okAsync(true); + } + + return ResultAsync.fromPromise( + auth.$context.then((ctx) => ctx.password.hash(nanoid())), + (error) => { + logDomainEvent({ + level: "error", + event: "account.ensure_exists.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, stage: "hash_password" }, + }); + return this.dbError( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ).andThen((password) => { + const aid = nanoid(); + + return ResultAsync.fromPromise( + this.db + .insert(account) + .values({ + id: aid, + accountId: userId, + providerId: "credential", + userId, + password, + createdAt: new Date(), + updatedAt: new Date(), + }) + .execute(), + (error) => { + logDomainEvent({ + level: "error", + event: "account.ensure_exists.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, stage: "create_account" }, + }); + return this.dbError( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "account.ensure_exists.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, existed: false }, + }); + return false; + }); + }); + }); + }, + }); + } + + rotatePassword( + fctx: FlowExecCtx, + userId: string, + password: string, + ): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.rotatePassword", + fctx, + attributes: { "app.user.id": userId }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "account.rotate_password.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db.query.account.findFirst({ + where: eq(account.userId, userId), + }), + (error) => { + logDomainEvent({ + level: "error", + event: "account.rotate_password.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, stage: "check_exists" }, + }); + return this.dbError( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ).andThen((existingAccount) => { + if (!existingAccount) { + logDomainEvent({ + level: "warn", + event: "account.rotate_password.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { code: "NOT_FOUND", message: "Account not found" }, + meta: { userId }, + }); + return errAsync(this.accountNotFound(fctx)); + } + + return ResultAsync.fromPromise( + auth.$context.then((ctx) => ctx.password.hash(password)), + (error) => { + logDomainEvent({ + level: "error", + event: "account.rotate_password.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, stage: "hash_password" }, + }); + return this.dbError( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ).andThen((hashed) => { + return ResultAsync.fromPromise( + this.db + .update(account) + .set({ password: hashed }) + .where(eq(account.userId, userId)) + .returning() + .execute(), + (error) => { + logDomainEvent({ + level: "error", + event: "account.rotate_password.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, stage: "update_password" }, + }); + return this.dbError( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "account.rotate_password.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId }, + }); + return password; + }); + }); + }); + }, + }); + } +} diff --git a/packages/logic/domains/user/controller.ts b/packages/logic/domains/user/controller.ts new file mode 100644 index 0000000..7e283b7 --- /dev/null +++ b/packages/logic/domains/user/controller.ts @@ -0,0 +1,96 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { traceResultAsync } from "@core/observability"; +import { AccountRepository } from "./account.repository"; +import { UserRepository } from "./repository"; +import { db } from "@pkg/db"; + +export class UserController { + constructor( + private userRepository: UserRepository, + private accountRepo: AccountRepository, + ) {} + + getUserInfo(fctx: FlowExecCtx, userId: string) { + return traceResultAsync({ + name: "logic.user.controller.getUserInfo", + fctx, + attributes: { "app.user.id": userId }, + fn: () => this.userRepository.getUserInfo(fctx, userId), + }); + } + + ensureAccountExists(fctx: FlowExecCtx, userId: string) { + return traceResultAsync({ + name: "logic.user.controller.ensureAccountExists", + fctx, + attributes: { "app.user.id": userId }, + fn: () => this.accountRepo.ensureAccountExists(fctx, userId), + }); + } + + isUsernameAvailable(fctx: FlowExecCtx, username: string) { + return traceResultAsync({ + name: "logic.user.controller.isUsernameAvailable", + fctx, + attributes: { "app.user.username": username }, + fn: () => this.userRepository.isUsernameAvailable(fctx, username), + }); + } + + updateLastVerified2FaAtToNow(fctx: FlowExecCtx, userId: string) { + return traceResultAsync({ + name: "logic.user.controller.updateLastVerified2FaAtToNow", + fctx, + attributes: { "app.user.id": userId }, + fn: () => this.userRepository.updateLastVerified2FaAtToNow(fctx, userId), + }); + } + + banUser( + fctx: FlowExecCtx, + userId: string, + reason: string, + banExpiresAt: Date, + ) { + return traceResultAsync({ + name: "logic.user.controller.banUser", + fctx, + attributes: { "app.user.id": userId }, + fn: () => this.userRepository.banUser(fctx, userId, reason, banExpiresAt), + }); + } + + isUserBanned(fctx: FlowExecCtx, userId: string) { + return traceResultAsync({ + name: "logic.user.controller.isUserBanned", + fctx, + attributes: { "app.user.id": userId }, + fn: () => this.userRepository.isUserBanned(fctx, userId), + }); + } + + getBanInfo(fctx: FlowExecCtx, userId: string) { + return traceResultAsync({ + name: "logic.user.controller.getBanInfo", + fctx, + attributes: { "app.user.id": userId }, + fn: () => this.userRepository.getBanInfo(fctx, userId), + }); + } + + rotatePassword(fctx: FlowExecCtx, userId: string, password: string) { + return traceResultAsync({ + name: "logic.user.controller.rotatePassword", + fctx, + attributes: { "app.user.id": userId }, + fn: () => this.accountRepo.rotatePassword(fctx, userId, password), + }); + } +} + +export function getUserController(): UserController { + return new UserController( + new UserRepository(db), + new AccountRepository(db), + ); +} diff --git a/packages/logic/domains/user/data.ts b/packages/logic/domains/user/data.ts new file mode 100644 index 0000000..70e660c --- /dev/null +++ b/packages/logic/domains/user/data.ts @@ -0,0 +1,159 @@ +import { Session } from "better-auth"; +import * as v from "valibot"; + +export type { Session } from "better-auth"; + +export type ModifiedSession = Session & { isCurrent?: boolean }; + +// User role enum +export enum UserRoleMap { + user = "user", + admin = "admin", +} + +// User role schema +export const userRoleSchema = v.picklist(["user", "admin"]); +export type UserRole = v.InferOutput; + +// User schema +export const userSchema = v.object({ + id: v.string(), + name: v.string(), + email: v.string(), + emailVerified: v.boolean(), + image: v.optional(v.string()), + createdAt: v.date(), + updatedAt: v.date(), + username: v.optional(v.string()), + displayUsername: v.optional(v.string()), + role: v.optional(v.string()), + banned: v.optional(v.boolean()), + banReason: v.optional(v.string()), + banExpires: v.optional(v.date()), + onboardingDone: v.optional(v.boolean()), + last2FAVerifiedAt: v.optional(v.date()), + parentId: v.optional(v.string()), +}); +export type User = v.InferOutput; + +// Account schema +export const accountSchema = v.object({ + id: v.string(), + accountId: v.string(), + providerId: v.string(), + userId: v.string(), + accessToken: v.string(), + refreshToken: v.string(), + idToken: v.string(), + accessTokenExpiresAt: v.date(), + refreshTokenExpiresAt: v.date(), + scope: v.string(), + password: v.string(), + createdAt: v.date(), + updatedAt: v.date(), +}); +export type Account = v.InferOutput; + +// Ensure account exists schema +export const ensureAccountExistsSchema = v.object({ + userId: v.string(), +}); +export type EnsureAccountExists = v.InferOutput< + typeof ensureAccountExistsSchema +>; + +// Ban info schema +export const banInfoSchema = v.object({ + banned: v.boolean(), + reason: v.optional(v.string()), + expires: v.optional(v.date()), +}); +export type BanInfo = v.InferOutput; + +// Ban user schema +export const banUserSchema = v.object({ + userId: v.string(), + reason: v.string(), + banExpiresAt: v.date(), +}); +export type BanUser = v.InferOutput; + +// Check username availability schema +export const checkUsernameSchema = v.object({ + username: v.string(), +}); +export type CheckUsername = v.InferOutput; + +// Rotate password schema +export const rotatePasswordSchema = v.object({ + userId: v.string(), + password: v.string(), +}); +export type RotatePassword = v.InferOutput; + +// View Model specific types + +// Search and filter types +export const searchFieldSchema = v.picklist(["email", "name", "username"]); +export type SearchField = v.InferOutput; + +export const searchOperatorSchema = v.picklist([ + "contains", + "starts_with", + "ends_with", +]); +export type SearchOperator = v.InferOutput; + +export const filterOperatorSchema = v.picklist([ + "eq", + "ne", + "lt", + "lte", + "gt", + "gte", +]); +export type FilterOperator = v.InferOutput; + +export const sortDirectionSchema = v.picklist(["asc", "desc"]); +export type SortDirection = v.InferOutput; + +// Users query state +export const usersQueryStateSchema = v.object({ + // searching + searchValue: v.optional(v.string()), + searchField: v.optional(searchFieldSchema), + searchOperator: v.optional(searchOperatorSchema), + + // pagination + limit: v.pipe(v.number(), v.integer()), + offset: v.pipe(v.number(), v.integer()), + + // sorting + sortBy: v.optional(v.string()), + sortDirection: v.optional(sortDirectionSchema), + + // filtering + filterField: v.optional(v.string()), + filterValue: v.optional(v.union([v.string(), v.number(), v.boolean()])), + filterOperator: v.optional(filterOperatorSchema), +}); +export type UsersQueryState = v.InferOutput; + +// UI View Model types + +export const banExpiryModeSchema = v.picklist([ + "never", + "1d", + "7d", + "30d", + "custom", +]); +export type BanExpiryMode = v.InferOutput; + +export const createUserFormSchema = v.object({ + email: v.string(), + password: v.string(), + name: v.string(), + role: v.union([userRoleSchema, v.array(userRoleSchema)]), +}); +export type CreateUserForm = v.InferOutput; diff --git a/packages/logic/domains/user/errors.ts b/packages/logic/domains/user/errors.ts new file mode 100644 index 0000000..ad178b8 --- /dev/null +++ b/packages/logic/domains/user/errors.ts @@ -0,0 +1,77 @@ +import { FlowExecCtx } from "@/core/flow.execution.context"; +import { ERROR_CODES, type Err } from "@pkg/result"; +import { getError } from "@pkg/logger"; + +export const userErrors = { + dbError: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Database operation failed", + description: "Please try again later", + detail, + }), + + userNotFound: (fctx: FlowExecCtx): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.NOT_FOUND, + message: "User not found", + description: "Try with a different user id", + detail: "User not found in database", + }), + + usernameCheckFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while checking username availability", + description: "Try again later", + detail, + }), + + banOperationFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to perform ban operation", + description: "Please try again later", + detail, + }), + + unbanFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to unban user", + description: "Please try again later", + detail, + }), + + updateFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "Failed to update user", + description: "Please try again later", + detail, + }), + + getUserInfoFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while getting user info", + description: "Try again later", + detail, + }), + + getBanInfoFailed: (fctx: FlowExecCtx, detail: string): Err => + getError({ + flowId: fctx.flowId, + code: ERROR_CODES.DATABASE_ERROR, + message: "An error occurred while getting ban info", + description: "Try again later", + detail, + }), +}; diff --git a/packages/logic/domains/user/repository.ts b/packages/logic/domains/user/repository.ts new file mode 100644 index 0000000..69eadeb --- /dev/null +++ b/packages/logic/domains/user/repository.ts @@ -0,0 +1,420 @@ +import { ResultAsync, errAsync, okAsync } from "neverthrow"; +import { FlowExecCtx } from "@core/flow.execution.context"; +import { traceResultAsync } from "@core/observability"; +import { type Err } from "@pkg/result"; +import { Database, eq } from "@pkg/db"; +import { BanInfo, User } from "./data"; +import { user } from "@pkg/db/schema"; +import { userErrors } from "./errors"; +import { logDomainEvent } from "@pkg/logger"; + +export class UserRepository { + constructor(private db: Database) {} + + getUserInfo(fctx: FlowExecCtx, userId: string): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.getUserInfo", + fctx, + attributes: { "app.user.id": userId }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "user.get_info.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.id, userId), + }), + (error) => { + logDomainEvent({ + level: "error", + event: "user.get_info.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return userErrors.getUserInfoFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((userData) => { + if (!userData) { + logDomainEvent({ + level: "warn", + event: "user.get_info.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { code: "NOT_FOUND", message: "User not found" }, + meta: { userId }, + }); + return errAsync(userErrors.userNotFound(fctx)); + } + + logDomainEvent({ + event: "user.get_info.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId }, + }); + return okAsync(userData as User); + }); + }, + }); + } + + isUsernameAvailable( + fctx: FlowExecCtx, + username: string, + ): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.isUsernameAvailable", + fctx, + attributes: { "app.user.username": username }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "user.username_check.started", + fctx, + }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.username, username), + }), + (error) => { + logDomainEvent({ + level: "error", + event: "user.username_check.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + }); + return userErrors.usernameCheckFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map((existingUser) => { + const isAvailable = !existingUser?.id; + logDomainEvent({ + event: "user.username_check.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { isAvailable }, + }); + return isAvailable; + }); + }, + }); + } + + updateLastVerified2FaAtToNow( + fctx: FlowExecCtx, + userId: string, + ): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.updateLastVerified2FaAtToNow", + fctx, + attributes: { "app.user.id": userId }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "user.update_last_2fa.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db + .update(user) + .set({ last2FAVerifiedAt: new Date() }) + .where(eq(user.id, userId)) + .execute(), + (error) => { + logDomainEvent({ + level: "error", + event: "user.update_last_2fa.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return userErrors.updateFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "user.update_last_2fa.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId }, + }); + return true; + }); + }, + }); + } + + banUser( + fctx: FlowExecCtx, + userId: string, + reason: string, + banExpiresAt: Date, + ): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.banUser", + fctx, + attributes: { "app.user.id": userId }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "user.ban.started", + fctx, + meta: { + userId, + reasonLength: reason.length, + banExpiresAt: banExpiresAt.toISOString(), + }, + }); + + return ResultAsync.fromPromise( + this.db + .update(user) + .set({ + banned: true, + banReason: reason, + banExpires: banExpiresAt, + }) + .where(eq(user.id, userId)) + .execute(), + (error) => { + logDomainEvent({ + level: "error", + event: "user.ban.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return userErrors.banOperationFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).map(() => { + logDomainEvent({ + event: "user.ban.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId }, + }); + return true; + }); + }, + }); + } + + isUserBanned(fctx: FlowExecCtx, userId: string): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.isUserBanned", + fctx, + attributes: { "app.user.id": userId }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "user.is_banned.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.id, userId), + columns: { + banned: true, + banExpires: true, + }, + }), + (error) => { + logDomainEvent({ + level: "error", + event: "user.is_banned.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return userErrors.dbError( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((userData) => { + if (!userData) { + logDomainEvent({ + level: "warn", + event: "user.is_banned.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { code: "NOT_FOUND", message: "User not found" }, + meta: { userId }, + }); + return errAsync(userErrors.userNotFound(fctx)); + } + + if (!userData.banned) { + logDomainEvent({ + event: "user.is_banned.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, isBanned: false }, + }); + return okAsync(false); + } + + if (!userData.banExpires) { + logDomainEvent({ + event: "user.is_banned.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, isBanned: true, isPermanent: true }, + }); + return okAsync(true); + } + + const now = new Date(); + if (userData.banExpires <= now) { + return ResultAsync.fromPromise( + this.db + .update(user) + .set({ + banned: false, + banReason: null, + banExpires: null, + }) + .where(eq(user.id, userId)) + .execute(), + (error) => { + logDomainEvent({ + level: "error", + event: "user.unban_after_expiry.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return userErrors.unbanFailed( + fctx, + error instanceof Error + ? error.message + : String(error), + ); + }, + ) + .map(() => { + logDomainEvent({ + event: "user.unban_after_expiry.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId }, + }); + return false; + }) + .orElse((error) => { + logDomainEvent({ + level: "warn", + event: "user.is_banned.succeeded", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId, degraded: true, isBanned: true }, + }); + return okAsync(true); + }); + } + + logDomainEvent({ + event: "user.is_banned.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { + userId, + isBanned: true, + banExpires: userData.banExpires.toISOString(), + }, + }); + return okAsync(true); + }); + }, + }); + } + + getBanInfo(fctx: FlowExecCtx, userId: string): ResultAsync { + return traceResultAsync({ + name: "logic.user.repository.getBanInfo", + fctx, + attributes: { "app.user.id": userId }, + fn: () => { + const startedAt = Date.now(); + logDomainEvent({ + event: "user.ban_info.started", + fctx, + meta: { userId }, + }); + + return ResultAsync.fromPromise( + this.db.query.user.findFirst({ + where: eq(user.id, userId), + columns: { banned: true, banReason: true, banExpires: true }, + }), + (error) => { + logDomainEvent({ + level: "error", + event: "user.ban_info.failed", + fctx, + durationMs: Date.now() - startedAt, + error, + meta: { userId }, + }); + return userErrors.getBanInfoFailed( + fctx, + error instanceof Error ? error.message : String(error), + ); + }, + ).andThen((userData) => { + if (!userData) { + logDomainEvent({ + level: "warn", + event: "user.ban_info.failed", + fctx, + durationMs: Date.now() - startedAt, + error: { code: "NOT_FOUND", message: "User not found" }, + meta: { userId }, + }); + return errAsync(userErrors.userNotFound(fctx)); + } + + logDomainEvent({ + event: "user.ban_info.succeeded", + fctx, + durationMs: Date.now() - startedAt, + meta: { userId, banned: userData.banned || false }, + }); + + return okAsync({ + banned: userData.banned || false, + reason: userData.banReason || undefined, + expires: userData.banExpires || undefined, + }); + }); + }, + }); + } +} diff --git a/packages/logic/package.json b/packages/logic/package.json new file mode 100644 index 0000000..a804d38 --- /dev/null +++ b/packages/logic/package.json @@ -0,0 +1,40 @@ +{ + "name": "@pkg/logic", + "type": "module", + "scripts": { + "auth:schemagen": "pnpm dlx @better-auth/cli generate --config ./domains/auth/config.base.ts --output ../../packages/db/schema/better.auth.schema.ts" + }, + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@otplib/plugin-base32-scure": "^13.3.0", + "@otplib/plugin-crypto-noble": "^13.3.0", + "@otplib/totp": "^13.3.0", + "@pkg/db": "workspace:*", + "@pkg/keystore": "workspace:*", + "@pkg/logger": "workspace:*", + "@pkg/result": "workspace:*", + "@pkg/settings": "workspace:*", + "@types/pdfkit": "^0.14.0", + "argon2": "^0.43.0", + "better-auth": "^1.4.7", + "date-fns-tz": "^3.2.0", + "dotenv": "^16.5.0", + "hono": "^4.11.1", + "imapflow": "^1.0.188", + "mailparser": "^3.7.3", + "nanoid": "^5.1.5", + "neverthrow": "^8.2.0", + "otplib": "^13.3.0", + "uuid": "^11.1.0", + "valibot": "^1.2.0" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/mailparser": "^3.4.6", + "@types/tmp": "^0.2.6", + "@types/uuid": "^10.0.0" + }, + "peerDependencies": { + "typescript": "^5.9.3" + } +} diff --git a/packages/logic/tsconfig.json b/packages/logic/tsconfig.json new file mode 100644 index 0000000..3c8de5f --- /dev/null +++ b/packages/logic/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + "baseUrl": ".", + "paths": { + "@/*": ["./*"], + "@domains/*": ["./domains/*"], + "@core/*": ["./core/*"] + }, + "moduleResolution": "bundler", + "module": "esnext", + "target": "esnext" + } +} diff --git a/packages/result/index.ts b/packages/result/index.ts new file mode 100644 index 0000000..4de398e --- /dev/null +++ b/packages/result/index.ts @@ -0,0 +1,81 @@ +export const ERROR_CODES = { + API_ERROR: "API_ERROR", + EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR", + RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR", + DATABASE_ERROR: "DATABASE_ERROR", + NETWORK_ERROR: "NETWORK_ERROR", + BANNED: "BANNED", + AUTH_ERROR: "AUTH_ERROR", + PERMISSION_ERROR: "PERMISSION_ERROR", + VALIDATION_ERROR: "VALIDATION_ERROR", + UNKNOWN_ERROR: "UNKNOWN_ERROR", + NOT_FOUND_ERROR: "NOT_FOUND_ERROR", + NOT_FOUND: "NOT_FOUND", + INPUT_ERROR: "INPUT_ERROR", + INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR", + EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR", + FILE_SYSTEM_ERROR: "FILE_SYSTEM_ERROR", + STORAGE_ERROR: "STORAGE_ERROR", + NOT_ALLOWED: "NOT_ALLOWED", + NOT_IMPLEMENTED: "NOT_IMPLEMENTED", + PROCESSING_ERROR: "PROCESSING_ERROR", + PARSING_ERROR: "PARSING_ERROR", +} as const; + +export const errorStatusMap = { + [ERROR_CODES.VALIDATION_ERROR]: 400, + [ERROR_CODES.AUTH_ERROR]: 403, + [ERROR_CODES.BANNED]: 403, + [ERROR_CODES.NOT_FOUND]: 404, + [ERROR_CODES.NOT_ALLOWED]: 405, + [ERROR_CODES.RATE_LIMIT_ERROR]: 429, + [ERROR_CODES.DATABASE_ERROR]: 500, + [ERROR_CODES.NETWORK_ERROR]: 500, + [ERROR_CODES.EXTERNAL_API_ERROR]: 500, + [ERROR_CODES.API_ERROR]: 500, + [ERROR_CODES.INTERNAL_SERVER_ERROR]: 500, + [ERROR_CODES.EXTERNAL_SERVICE_ERROR]: 500, + [ERROR_CODES.FILE_SYSTEM_ERROR]: 500, + [ERROR_CODES.STORAGE_ERROR]: 500, + [ERROR_CODES.PROCESSING_ERROR]: 500, + [ERROR_CODES.PARSING_ERROR]: 500, + [ERROR_CODES.NOT_IMPLEMENTED]: 501, +} as Record; + +export type Err = { + flowId?: string; + code: string; + message: string; + description: string; + detail: string; + actionable?: boolean; + error?: any; +}; + +type Success = { data: T; error?: undefined | null }; +type Failure = { data?: undefined | null; error: E }; + +// Legacy now, making use of Effect throughout the project +export type Result = Success | Failure; + +export async function tryCatch( + promise: Promise, + err?: E, +): Promise> { + try { + const data = await promise; + return { data }; + } catch (e) { + return { + // @ts-ignore + error: !!err + ? err + : { + code: "UNKNOWN_ERROR", + message: "An unknown error occurred", + description: "An unknown error occurred", + detail: "An unknown error occurred", + }, + }; + } +} diff --git a/packages/result/package.json b/packages/result/package.json new file mode 100644 index 0000000..449a414 --- /dev/null +++ b/packages/result/package.json @@ -0,0 +1,11 @@ +{ + "name": "@pkg/result", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.9.3" + } +} diff --git a/packages/settings/index.ts b/packages/settings/index.ts new file mode 100644 index 0000000..62515b7 --- /dev/null +++ b/packages/settings/index.ts @@ -0,0 +1,181 @@ +import * as v from "valibot"; + +import "dotenv/config"; + +/** + * Settings schema using Valibot for validation + */ +export const settingsSchema = v.object({ + appName: v.string(), + nodeEnv: v.string(), + logLevel: v.string(), + + isDevelopment: v.optional(v.boolean()), + + redisUrl: v.string(), + databaseUrl: v.string(), + + internalApiKey: v.string(), + debugKey: v.string(), + + processorApiUrl: v.string(), + appBuilderApiUrl: v.string(), + appBuilderAssetsPublicUrl: v.string(), + clientDownloadedApkName: v.string(), + mobileAppApiUrl: v.string(), + + betterAuthUrl: v.string(), + betterAuthSecret: v.string(), + + twoFaSecret: v.string(), + twofaSessionExpiryMinutes: v.optional(v.number()), + twofaRequiredHours: v.optional(v.number()), + + defaultAdminEmail: v.string(), + defaultAdminPassword: v.string(), + + otelServiceName: v.string(), + otelExporterOtlpHttpEndpoint: v.string(), + + // R2/Object Storage settings + r2BucketName: v.string(), + r2Region: v.string(), + r2Endpoint: v.string(), + r2AccessKey: v.string(), + r2SecretKey: v.string(), + r2PublicUrl: v.optional(v.string()), + + // File upload settings + maxFileSize: v.number(), + allowedMimeTypes: v.array(v.string()), + allowedExtensions: v.array(v.string()), +}); + +export type Settings = v.InferOutput; + +/** + * Helper to get environment variable with default value + */ +function getEnv(key: string, defaultValue: string = ""): string { + return process.env[key] ?? defaultValue; +} + +/** + * Helper to get environment variable as number with default value + */ +function getEnvNumber(key: string, defaultValue: number): number { + const value = process.env[key]; + if (!value) return defaultValue; + const parsed = Number(value); + return Number.isNaN(parsed) ? defaultValue : parsed; +} + +/** + * Parse comma-separated string into array + */ +function parseCommaSeparated(value: string): string[] { + return value + .split(",") + .map((item) => item.trim()) + .filter((item) => item.length > 0); +} + +/** + * Load and validate settings from environment variables + */ +function loadSettings(): Settings { + const nodeEnv = getEnv("NODE_ENV", "development"); + + const rawSettings = { + appName: getEnv("APP_NAME", "App"), + nodeEnv, + logLevel: getEnv("LOG_LEVEL", "info"), + + isDevelopment: nodeEnv === "development", + + redisUrl: getEnv("REDIS_URL", "redis://localhost:6379"), + databaseUrl: getEnv("DATABASE_URL"), + + internalApiKey: getEnv("INTERNAL_API_KEY"), + debugKey: getEnv("DEBUG_KEY"), + + processorApiUrl: getEnv("PROCESSOR_API_URL", "http://localhost:3000"), + appBuilderApiUrl: getEnv( + "APP_BUILDER_API_URL", + "http://localhost:3001", + ), + appBuilderAssetsPublicUrl: getEnv( + "APP_BUILDER_ASSETS_PUBLIC_URL", + "http://localhost:3001", + ), + clientDownloadedApkName: getEnv( + "CLIENT_DOWNLOADED_APK_NAME", + "illusory-client.apk", + ), + mobileAppApiUrl: getEnv("MOBILE_APP_API_URL"), + + betterAuthUrl: getEnv("BETTER_AUTH_URL"), + betterAuthSecret: getEnv("BETTER_AUTH_SECRET"), + + twoFaSecret: getEnv("TWOFA_SECRET"), + twofaSessionExpiryMinutes: getEnvNumber( + "TWOFA_SESSION_EXPIRY_MINUTES", + 10, + ), + twofaRequiredHours: getEnvNumber("TWOFA_REQUIRED_HOURS", 24), + + defaultAdminEmail: getEnv("DEFAULT_ADMIN_EMAIL"), + defaultAdminPassword: getEnv("DEFAULT_ADMIN_PASSWORD"), + + otelServiceName: getEnv("OTEL_SERVICE_NAME"), + otelExporterOtlpHttpEndpoint: getEnv( + "OTEL_EXPORTER_OTLP_HTTP_ENDPOINT", + ), + + // R2/Object Storage settings + r2BucketName: getEnv("R2_BUCKET_NAME"), + r2Region: getEnv("R2_REGION", "auto"), + r2Endpoint: getEnv("R2_ENDPOINT"), + r2AccessKey: getEnv("R2_ACCESS_KEY"), + r2SecretKey: getEnv("R2_SECRET_KEY"), + r2PublicUrl: getEnv("R2_PUBLIC_URL") || undefined, + + // File upload settings + maxFileSize: getEnvNumber("MAX_FILE_SIZE", 10485760), // 10MB default + allowedMimeTypes: parseCommaSeparated( + getEnv( + "ALLOWED_MIME_TYPES", + "image/jpeg,image/png,image/webp,image/gif,application/pdf,text/plain", + ), + ), + allowedExtensions: parseCommaSeparated( + getEnv("ALLOWED_EXTENSIONS", "jpg,jpeg,png,webp,gif,pdf,txt"), + ), + }; + + try { + return v.parse(settingsSchema, rawSettings); + } catch (error) { + console.error("❌ Settings validation failed:"); + if (error instanceof v.ValiError) { + for (const issue of error.issues) { + console.error( + ` - ${issue.path?.map((p: any) => p.key).join(".")}: ${issue.message}`, + ); + } + } else { + console.error(error); + } + throw new Error( + "Failed to load settings. Check environment variables.", + ); + } +} + +export const settings = loadSettings(); + +export const getSetting = (key: K): Settings[K] => { + return settings[key]; +}; + +console.log(`✅ Settings loaded | ${settings.appName} (${settings.nodeEnv})`); diff --git a/packages/settings/package.json b/packages/settings/package.json new file mode 100644 index 0000000..062792c --- /dev/null +++ b/packages/settings/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pkg/settings", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.9.3" + }, + "dependencies": { + "dotenv": "^17.2.3", + "valibot": "^1.2.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..e20738c --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,7616 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + prettier: + specifier: ^3.8.1 + version: 3.8.1 + prettier-plugin-sort-imports: + specifier: ^1.8.11 + version: 1.8.11(typescript@5.9.3) + prettier-plugin-svelte: + specifier: ^3.5.0 + version: 3.5.0(prettier@3.8.1)(svelte@5.53.6) + prettier-plugin-tailwindcss: + specifier: ^0.7.2 + version: 0.7.2(prettier-plugin-sort-imports@1.8.11(typescript@5.9.3))(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.6))(prettier@3.8.1) + turbo: + specifier: ^2.8.16 + version: 2.8.16 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + apps/front: + dependencies: + '@hono/node-server': + specifier: ^1.19.9 + version: 1.19.9(hono@4.12.8) + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/auto-instrumentations-node': + specifier: ^0.70.1 + version: 0.70.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)) + '@opentelemetry/exporter-logs-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': + specifier: ^2.1.0 + version: 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@pkg/db': + specifier: workspace:* + version: link:../../packages/db + '@pkg/logger': + specifier: workspace:* + version: link:../../packages/logger + '@pkg/logic': + specifier: workspace:* + version: link:../../packages/logic + '@pkg/result': + specifier: workspace:* + version: link:../../packages/result + '@pkg/settings': + specifier: workspace:* + version: link:../../packages/settings + hono: + specifier: ^4.12.8 + version: 4.12.8 + import-in-the-middle: + specifier: ^3.0.0 + version: 3.0.0 + valibot: + specifier: ^1.2.0 + version: 1.2.0(typescript@5.9.3) + devDependencies: + '@types/node': + specifier: ^25.3.2 + version: 25.5.0 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + apps/main: + dependencies: + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/auto-instrumentations-node': + specifier: ^0.70.1 + version: 0.70.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)) + '@opentelemetry/exporter-logs-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@pkg/db': + specifier: workspace:* + version: link:../../packages/db + '@pkg/keystore': + specifier: workspace:* + version: link:../../packages/keystore + '@pkg/logger': + specifier: workspace:* + version: link:../../packages/logger + '@pkg/logic': + specifier: workspace:* + version: link:../../packages/logic + '@pkg/result': + specifier: workspace:* + version: link:../../packages/result + '@pkg/settings': + specifier: workspace:* + version: link:../../packages/settings + argon2: + specifier: ^0.43.0 + version: 0.43.1 + better-auth: + specifier: ^1.4.20 + version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + import-in-the-middle: + specifier: ^3.0.0 + version: 3.0.0 + nanoid: + specifier: ^5.1.6 + version: 5.1.6 + neverthrow: + specifier: ^8.2.0 + version: 8.2.0 + qrcode: + specifier: ^1.5.4 + version: 1.5.4 + sharp: + specifier: ^0.34.5 + version: 0.34.5 + valibot: + specifier: ^1.2.0 + version: 1.2.0(typescript@5.9.3) + devDependencies: + '@iconify/json': + specifier: ^2.2.434 + version: 2.2.444 + '@internationalized/date': + specifier: ^3.10.0 + version: 3.11.0 + '@lucide/svelte': + specifier: ^0.561.0 + version: 0.561.0(svelte@5.53.6) + '@sveltejs/adapter-node': + specifier: ^5.5.4 + version: 5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + '@sveltejs/kit': + specifier: ^2.53.4 + version: 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@tailwindcss/forms': + specifier: ^0.5.10 + version: 0.5.11(tailwindcss@4.2.1) + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@4.2.1) + '@tailwindcss/vite': + specifier: ^4.1.18 + version: 4.2.1(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@tanstack/table-core': + specifier: ^8.21.3 + version: 8.21.3 + '@types/qrcode': + specifier: ^1.5.6 + version: 1.5.6 + bits-ui: + specifier: ^2.14.4 + version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + embla-carousel-svelte: + specifier: ^8.6.0 + version: 8.6.0(svelte@5.53.6) + formsnap: + specifier: ^2.0.1 + version: 2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)) + layerchart: + specifier: 2.0.0-next.43 + version: 2.0.0-next.43(svelte@5.53.6) + mode-watcher: + specifier: ^1.1.0 + version: 1.1.0(svelte@5.53.6) + paneforge: + specifier: ^1.0.2 + version: 1.0.2(svelte@5.53.6) + prettier: + specifier: ^3.7.4 + version: 3.8.1 + prettier-plugin-svelte: + specifier: ^3.4.0 + version: 3.5.0(prettier@3.8.1)(svelte@5.53.6) + prettier-plugin-tailwindcss: + specifier: ^0.7.2 + version: 0.7.2(prettier-plugin-sort-imports@1.8.11(typescript@5.9.3))(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.6))(prettier@3.8.1) + svelte: + specifier: ^5.53.6 + version: 5.53.6 + svelte-check: + specifier: ^4.4.4 + version: 4.4.4(picomatch@4.0.3)(svelte@5.53.6)(typescript@5.9.3) + svelte-sonner: + specifier: ^1.0.7 + version: 1.0.7(svelte@5.53.6) + sveltekit-superforms: + specifier: ^2.30.0 + version: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3) + tailwind-merge: + specifier: ^3.4.0 + version: 3.5.0 + tailwind-variants: + specifier: ^3.2.2 + version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.1) + tailwindcss: + specifier: ^4.1.18 + version: 4.2.1 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + unplugin-icons: + specifier: ^23.0.1 + version: 23.0.1(svelte@5.53.6) + vaul-svelte: + specifier: ^1.0.0-next.7 + version: 1.0.0-next.7(svelte@5.53.6) + vite: + specifier: ^7.2.6 + version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vitest: + specifier: ^4.0.15 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + apps/orchestrator: + dependencies: + '@hono/node-server': + specifier: ^1.19.9 + version: 1.19.9(hono@4.12.8) + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/auto-instrumentations-node': + specifier: ^0.70.1 + version: 0.70.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)) + '@opentelemetry/exporter-logs-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': + specifier: ^2.1.0 + version: 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: ^0.212.0 + version: 0.212.0(@opentelemetry/api@1.9.0) + '@pkg/db': + specifier: workspace:* + version: link:../../packages/db + '@pkg/logger': + specifier: workspace:* + version: link:../../packages/logger + '@pkg/logic': + specifier: workspace:* + version: link:../../packages/logic + '@pkg/result': + specifier: workspace:* + version: link:../../packages/result + '@pkg/settings': + specifier: workspace:* + version: link:../../packages/settings + hono: + specifier: ^4.12.8 + version: 4.12.8 + import-in-the-middle: + specifier: ^3.0.0 + version: 3.0.0 + valibot: + specifier: ^1.2.0 + version: 1.2.0(typescript@5.9.3) + devDependencies: + '@types/node': + specifier: ^25.3.2 + version: 25.5.0 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + packages/db: + dependencies: + '@pkg/settings': + specifier: workspace:* + version: link:../settings + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + drizzle-orm: + specifier: ^0.45.1 + version: 0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8) + postgres: + specifier: ^3.4.8 + version: 3.4.8 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + devDependencies: + '@types/bun': + specifier: latest + version: 1.3.9 + '@types/pg': + specifier: ^8.11.10 + version: 8.16.0 + drizzle-kit: + specifier: ^0.31.9 + version: 0.31.9 + + packages/keystore: + dependencies: + ioredis: + specifier: ^5.6.1 + version: 5.10.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + devDependencies: + '@types/bun': + specifier: latest + version: 1.3.9 + + packages/logger: + dependencies: + '@opentelemetry/winston-transport': + specifier: ^0.22.0 + version: 0.22.0 + '@pkg/result': + specifier: workspace:* + version: link:../result + '@pkg/settings': + specifier: workspace:* + version: link:../settings + typescript: + specifier: ^5.9.3 + version: 5.9.3 + winston: + specifier: ^3.17.0 + version: 3.19.0 + devDependencies: + '@types/bun': + specifier: latest + version: 1.3.9 + + packages/logic: + dependencies: + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@otplib/plugin-base32-scure': + specifier: ^13.3.0 + version: 13.3.0 + '@otplib/plugin-crypto-noble': + specifier: ^13.3.0 + version: 13.3.0 + '@otplib/totp': + specifier: ^13.3.0 + version: 13.3.0 + '@pkg/db': + specifier: workspace:* + version: link:../db + '@pkg/keystore': + specifier: workspace:* + version: link:../keystore + '@pkg/logger': + specifier: workspace:* + version: link:../logger + '@pkg/result': + specifier: workspace:* + version: link:../result + '@pkg/settings': + specifier: workspace:* + version: link:../settings + '@types/pdfkit': + specifier: ^0.14.0 + version: 0.14.0 + argon2: + specifier: ^0.43.0 + version: 0.43.1 + better-auth: + specifier: ^1.4.7 + version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + date-fns-tz: + specifier: ^3.2.0 + version: 3.2.0(date-fns@4.1.0) + dotenv: + specifier: ^16.5.0 + version: 16.6.1 + hono: + specifier: ^4.11.1 + version: 4.12.8 + imapflow: + specifier: ^1.0.188 + version: 1.2.10 + mailparser: + specifier: ^3.7.3 + version: 3.9.3 + nanoid: + specifier: ^5.1.5 + version: 5.1.6 + neverthrow: + specifier: ^8.2.0 + version: 8.2.0 + otplib: + specifier: ^13.3.0 + version: 13.3.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + uuid: + specifier: ^11.1.0 + version: 11.1.0 + valibot: + specifier: ^1.2.0 + version: 1.2.0(typescript@5.9.3) + devDependencies: + '@types/bun': + specifier: latest + version: 1.3.9 + '@types/mailparser': + specifier: ^3.4.6 + version: 3.4.6 + '@types/tmp': + specifier: ^0.2.6 + version: 0.2.6 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + + packages/result: + dependencies: + typescript: + specifier: ^5.9.3 + version: 5.9.3 + devDependencies: + '@types/bun': + specifier: latest + version: 1.3.9 + + packages/settings: + dependencies: + dotenv: + specifier: ^17.2.3 + version: 17.3.1 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + valibot: + specifier: ^1.2.0 + version: 1.2.0(typescript@5.9.3) + devDependencies: + '@types/bun': + specifier: latest + version: 1.3.9 + +packages: + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@ark/schema@0.56.0': + resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} + + '@ark/util@0.56.0': + resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@better-auth/core@1.4.20': + resolution: {integrity: sha512-Qf29DOL4LricVJWsPOwg2Ymm+qfYQ14EkyTlnMOp8qKSPzbfMSgRvr6oiwZqmUFxystJ3ft8TzDwTvOSAuNbfA==} + peerDependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + better-call: 1.1.8 + jose: ^6.1.0 + kysely: ^0.28.5 + nanostores: ^1.0.1 + + '@better-auth/telemetry@1.4.20': + resolution: {integrity: sha512-ItRo5WswZl6gU8MPRrcn94d7mXk7vbN2zi6gSX8y2QcILRv7aXr6WhxTNKNeh5pnBUfIPdFYZcOnGf1uwgNKxg==} + peerDependencies: + '@better-auth/core': 1.4.20 + + '@better-auth/utils@0.3.0': + resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} + + '@better-fetch/fetch@1.1.21': + resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@dabh/diagnostics@2.0.8': + resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + + '@dagrejs/dagre@1.1.8': + resolution: {integrity: sha512-5SEDlndt4W/LaVzPYJW+bSmSEZc9EzTf8rJ20WCKvjS5EAZAN0b+x0Yww7VMT4R3Wootkg+X9bUfUxazYw6Blw==} + + '@dagrejs/graphlib@2.2.4': + resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==} + engines: {node: '>17.0.0'} + + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@exodus/schemasafe@1.3.0': + resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + + '@floating-ui/core@1.7.4': + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + + '@floating-ui/dom@1.7.5': + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@grpc/grpc-js@1.14.3': + resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + engines: {node: '>=6'} + hasBin: true + + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + + '@hono/node-server@1.19.9': + resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@iconify/json@2.2.444': + resolution: {integrity: sha512-z0UwFaVtaN/h/iWZ1kzEjqFU3sp0rRy93tzOtpepZU89DY39WsNeYZv2mxtft/2La6Bz2b4z1C/HkU5Cqv3gbw==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@internationalized/date@3.11.0': + resolution: {integrity: sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==} + + '@ioredis/commands@1.5.1': + resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@layerstack/svelte-actions@1.0.1-next.14': + resolution: {integrity: sha512-MPBmVaB+GfNHvBkg5nJkPG18smoXKvsvJRpsdWnrUBfca+TieZLoaEzNxDH+9LG11dIXP9gghsXt1mUqbbyAsA==} + + '@layerstack/svelte-state@0.1.0-next.19': + resolution: {integrity: sha512-yCYoQAIbeP8y1xmOB/r0+UundgP4JFnpNURgMki+26TotzoqrZ5oLpHvhPSVm60ks+buR3ebDBTeUFdHzxwzQQ==} + + '@layerstack/tailwind@2.0.0-next.17': + resolution: {integrity: sha512-ZSn6ouqpnzB6DKzSKLVwrUBOQsrzpDA/By2/ba9ApxgTGnaD1nyqNwrvmZ+kswdAwB4YnrGEAE4VZkKrB2+DaQ==} + + '@layerstack/utils@2.0.0-next.14': + resolution: {integrity: sha512-1I2CS0Cwgs53W35qVg1eBdYhB/CiPvL3s0XE61b8jWkTHxgjBF65yYNgXjW74kv7WI7GsJcWMNBufPd0rnu9kA==} + + '@lucide/svelte@0.561.0': + resolution: {integrity: sha512-vofKV2UFVrKE6I4ewKJ3dfCXSV6iP6nWVmiM83MLjsU91EeJcEg7LoWUABLp/aOTxj1HQNbJD1f3g3L0JQgH9A==} + peerDependencies: + svelte: ^5 + + '@noble/ciphers@2.1.1': + resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@2.0.1': + resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + engines: {node: '>= 20.19.0'} + + '@opentelemetry/api-logs@0.212.0': + resolution: {integrity: sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/auto-instrumentations-node@0.70.1': + resolution: {integrity: sha512-r8BKs0rHtBAzZViPIuzSD2eh65fOPau0NqVsca2sACuZ6LFGu6a+QMhqq7skXz+/OqKwFr/7/b6VsaNMS+zZpQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.4.1 + '@opentelemetry/core': ^2.0.0 + + '@opentelemetry/configuration@0.212.0': + resolution: {integrity: sha512-D8sAY6RbqMa1W8lCeiaSL2eMCW2MF87QI3y+I6DQE1j+5GrDMwiKPLdzpa/2/+Zl9v1//74LmooCTCJBvWR8Iw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.5.1': + resolution: {integrity: sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.5.1': + resolution: {integrity: sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.212.0': + resolution: {integrity: sha512-/0bk6fQG+eSFZ4L6NlckGTgUous/ib5+OVdg0x4OdwYeHzV3lTEo3it1HgnPY6UKpmX7ki+hJvxjsOql8rCeZA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.212.0': + resolution: {integrity: sha512-JidJasLwG/7M9RTxV/64xotDKmFAUSBc9SNlxI32QYuUMK5rVKhHNWMPDzC7E0pCAL3cu+FyiKvsTwLi2KqPYw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.212.0': + resolution: {integrity: sha512-RpKB5UVfxc7c6Ta1UaCrxXDTQ0OD7BCGT66a97Q5zR1x3+9fw4dSaiqMXT/6FAWj2HyFbem6Rcu1UzPZikGTWQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.212.0': + resolution: {integrity: sha512-/6Gqf9wpBq22XsomR1i0iPGnbQtCq2Vwnrq5oiDPjYSqveBdK1jtQbhGfmpK2mLLxk4cPDtD1ZEYdIou5K8EaA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.212.0': + resolution: {integrity: sha512-8hgBw3aTTRpSTkU4b9MLf/2YVLnfWp+hfnLq/1Fa2cky+vx6HqTodo+Zv1GTIrAKMOOwgysOjufy0gTxngqeBg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.212.0': + resolution: {integrity: sha512-C7I4WN+ghn3g7SnxXm2RK3/sRD0k/BYcXaK6lGU3yPjiM7a1M25MLuM6zY3PeVPPzzTZPfuS7+wgn/tHk768Xw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.212.0': + resolution: {integrity: sha512-hJFLhCJba5MW5QHexZMHZdMhBfNqNItxOsN0AZojwD1W2kU9xM+BEICowFGJFo/vNV+I2BJvTtmuKafeDSAo7Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.212.0': + resolution: {integrity: sha512-9xTuYWp8ClBhljDGAoa0NSsJcsxJsC9zCFKMSZJp1Osb9pjXCMRdA6fwXtlubyqe7w8FH16EWtQNKx/FWi+Ghw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.212.0': + resolution: {integrity: sha512-v/0wMozNoiEPRolzC4YoPo4rAT0q8r7aqdnRw3Nu7IDN0CGFzNQazkfAlBJ6N5y0FYJkban7Aw5WnN73//6YlA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.212.0': + resolution: {integrity: sha512-d1ivqPT0V+i0IVOOdzGaLqonjtlk5jYrW7ItutWzXL/Mk+PiYb59dymy/i2reot9dDnBFWfrsvxyqdutGF5Vig==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.5.1': + resolution: {integrity: sha512-Me6JVO7WqXGXsgr4+7o+B7qwKJQbt0c8WamFnxpkR43avgG9k/niTntwCaXiXUTjonWy0+61ZuX6CGzj9nn8CQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-amqplib@0.59.0': + resolution: {integrity: sha512-xscSgOJA+GHphESDZxBHNk/zjNaEgoeufMwmiqYdL+qM27Xw3BbR9vN6Ucbq9dW6Y+oYUPgTTj17qf+Za4+uzg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-aws-lambda@0.64.0': + resolution: {integrity: sha512-vYhM/a8fG34/Dl/Q9gfv5Ih3OFPgqeyn79S8FN+Xs/QZw6h6L8a1lDa3CyigyicOXLCmVIM7Fc9vFD4BGqgGLA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-aws-sdk@0.67.0': + resolution: {integrity: sha512-btpwJnZ2RBXDh/pTpfVpInpBu9Pedi+lbLKbt3naB344SggbbYnIdT7u8EzmGIApWi9EV91vw7hm896I7nESQA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-bunyan@0.57.0': + resolution: {integrity: sha512-W4zLz1Y9ptCsdL+QMXR7xQaBHkJivLBmVlLCjUe23rX4V8E65fGAtlIJSKTKAfz4aEgtWgQAGMdkeqACwG0Caw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-cassandra-driver@0.57.0': + resolution: {integrity: sha512-xLwrK+XnN32IB5i6t/a2j+SVdjlq/BIgjpVRkke4HAsKjoSMy1GeSI+ZOiJffRLFb4MojcvH4RG2+nEg1uC6Zg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-connect@0.55.0': + resolution: {integrity: sha512-UfGw7ubKKZBoTRjxi5KlfeECEaXZinS20RdRNlZE5tVF+O17hJOnrcGwAoQAHp6eYmxI2jW9IQ4t6450gnNF9g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-cucumber@0.28.0': + resolution: {integrity: sha512-kim+bRxu4LZqKEyF2SgO01tgG88W+/iYltyP1XjT31FIXzlBjzQpwtSLLM8byayO85mcZIBha54WSNFDLM/7qQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-dataloader@0.29.0': + resolution: {integrity: sha512-220WjRb1G1UiAKbVblSMxwxxFdpyB4wj1XYIO9BJs5r62Azj2dL5fyZiXK3/WO6wB3uLul9R946iKI1bpPxktQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-dns@0.55.0': + resolution: {integrity: sha512-cfWLaFi22V+sQrKY7t6QroYzT3kO9m3PpkN1OXYmuCyfwxQaXOVlF8NSAHtua/RQYw0aQl+2fe6JOWyJdEZiwA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-express@0.60.0': + resolution: {integrity: sha512-KghHCDqKq0D7iuPIVCuPSXut5WVAI6uwKcPrhwTUJL5VE2LC18id2vKoiAm1V8XvVlgIGAiECtEvbrFwkTCj3g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fastify@0.56.0': + resolution: {integrity: sha512-zotOPoZsWtMF47BjottK23XaaBSmVuwG5D/R3FlGfAAwMNFoDR3IY1OGO9v9KfOU/1/xDVkxsQ22NFfu9lE8aA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fs@0.31.0': + resolution: {integrity: sha512-C7tdXGDnkMgLVlE79VSekB+Y+P345zKUigvFMs5M7U0GIYA8ERx3FS0aAcY/ICIq9YwRmH2uuMb++Br5M2vNUg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-generic-pool@0.55.0': + resolution: {integrity: sha512-7hWiyLbEX/dIS4LZy/h8VaAQPs8oBeEqsrysDWbos0b9PF414L6Rsbi2um/omtxIs+GTvsbuqDscWigeaxyWdA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.60.0': + resolution: {integrity: sha512-XPATrmxAd2tFCsYbJ3eVIXt+gyvMKjc36QQuQxjtssMnAbw006Le9b5lKs7WXik7ItOpM1exATi1aDdOcCjRRg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-grpc@0.212.0': + resolution: {integrity: sha512-r1t7LNKWVhSQMUrBdDJtooFmmLZ93kGuFixqeXPoUP8W+chJCxhey9l0c0+L3xriNdyB7TzvkKHhPXUDevgVEA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.58.0': + resolution: {integrity: sha512-reuRApR2KHm2VsfyDgsrLhNE+IOy4uIU6n3oMjUleReHacEEZmf4vXxdt4/qcmJ6GoUXnRN2AOu3s5N3pMrgYA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.212.0': + resolution: {integrity: sha512-t2nt16Uyv9irgR+tqnX96YeToOStc3X5js7Ljn3EKlI2b4Fe76VhMkTXtsTQ0aId6AsYgefrCRnXSCo/Fn/vww==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-ioredis@0.60.0': + resolution: {integrity: sha512-R+nnbPD9l2ruzu248qM3YDWzpdmWVaFFFv08lQqsc0EP4pT/B1GGUg06/tHOSo3L5njB2eejwyzpkvJkjaQEMA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.21.0': + resolution: {integrity: sha512-lkLrILnKGO7SHw1xPJnuGx2S4XwbKmQiJyzUGuEImRoU/6Gj0Nka0lkbeRd4ANN20dxr/mLdXIsUsk6DzTrX6A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-knex@0.56.0': + resolution: {integrity: sha512-pKqtY5lbAQ70MC5K/BJeAA1t2gAUlRBZBAJ5ergRUNs5jw8zbdOXEZOLztiuNvQqD2z4a9N0Tkde9JMFm2pKMQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.60.0': + resolution: {integrity: sha512-UOmu2y2LHgPzKsm9xd0sCQJimr11YP4MKFc190Do1ufd8qds7Zd5BI3f6TudqYhH9dUIhojsQyUaS6K4nv5FsQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.56.0': + resolution: {integrity: sha512-vXtOValhKRgWA9tLAiTU3P37Q31OveRuM2N5iLSVHl4GzkMBQ5p50A9kSKvt5gReL6BzFDXPCM9ItJiAhSS2KQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-memcached@0.55.0': + resolution: {integrity: sha512-kdhW/j5X+vNCAvHVc50PZfvE7diUScg1ZkBaNFRygY3Z6IUjgPLR0luWQMDPSFun6AVo1HaMDPxbUqJrot6qrA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.65.0': + resolution: {integrity: sha512-hOAJRs5vrY7fZolSYUXmf29Y+HFDHWrek0DeLq82uwMPjPSda7h6oumQnqEX5olzw357q/QG39/uJdkclJ/JUg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.58.0': + resolution: {integrity: sha512-3L0Fqo1y2oreISFPWaqdt/bg3NhLgrkn5U/E/9RNG1QaM81drTMBCHseMY1q8SlejjE43ZWOy+0KbmRBlUPJ+g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.58.0': + resolution: {integrity: sha512-EubjV1XZb7XHrENqF7TW2lnah+KN0LddMneKNAB8PjGVKL5lJkVV/vhJ6EIcUNn9nCWmAwZ3GRcFVEDKCnyXfQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.58.0': + resolution: {integrity: sha512-wZDrBCL3WfJclV6KywWVV3/B2ZiUYmDQdgyu3pq4jK/5qSfoDmezHzT/Nayln5MVVWMAGXIMLrCj8BKa6jaKQQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-nestjs-core@0.58.0': + resolution: {integrity: sha512-0lE9oW8j6nmvBHJoOxIQgKzMQQYNfX1nhiWZdXD0sNAMFsWBtvECWS7NAPSroKrEP53I04TcHCyyhcK4I9voXg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-net@0.56.0': + resolution: {integrity: sha512-h69x7U6f86mP3gGWWTaMkQZk0K3tBvpVMIU7E0q2kkVw6eZ5TqFm9rkaEy38moQmixiDFQ9j/2/cwxG9P7ZEeA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-openai@0.10.0': + resolution: {integrity: sha512-0lV2zxge2mMaruVCw/bmypWVu+aJ76rc0HBvAVFCPUI3zzJdgBZJZafGIHZ1IB2F6VvrDFL+JstEnle6V8brvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-oracledb@0.37.0': + resolution: {integrity: sha512-OzMghtAEAEkXlkUrZI4QcXSZq0MILeU6WC0/N5+1MSkuIkruIeaRw99/RtyS2of8vlPDa8XbbXl32Q1RM3wSyg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.64.0': + resolution: {integrity: sha512-NbfB/rlfsRI3zpTjnbvJv3qwuoGLsN8FxR/XoI+ZTn1Rs62x1IenO+TSSvk4NO+7FlXpd2MiOe8LT/oNbydHGA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pino@0.58.0': + resolution: {integrity: sha512-rgy+tA7cDjuSq6dXAO40OiYP25azIDHMBtxG3RzSmCBVEYdjggl6btyuLVasX6VkOOhP2gf6PBuLMNxVwaIqAw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis@0.60.0': + resolution: {integrity: sha512-Ea/GffmmzIVHc9geaMjT94IR7poVZzIv4Kk/Lw0tbxGD3cBYcMUsLFVajKxpZsE1NRCECFpidAWeifCIKD0inw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-restify@0.57.0': + resolution: {integrity: sha512-kO6MsZFU+RdXOKhsKw8SOSBYGYCdFSlza+mpBQRl1DQmveZcnidchv4V5JQPtNgHxCGH+1n3hDpLdxdGUbJPNA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-router@0.56.0': + resolution: {integrity: sha512-PHECDGQElLazI/QbHU16C5m9fDC7DGJk+jLIwO5ca6bcp7bXhUPPUTT78l7da2pDsrz4mhv5ytYNZmBbW/Q3rA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-runtime-node@0.25.0': + resolution: {integrity: sha512-XaCmwBSui5KeTn8M6OzaEn1rEsNWtUkjuc1ylg0tqQTLHibNQ0n7f8v4zdF6x/nBV1OnsiYlN8RLHauGemv/TA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-socket.io@0.59.0': + resolution: {integrity: sha512-71DnM/FEqH0PjvU2uZvzWJeaGyVIy3rJKk8rZrxg/aS2QT3qLGb+UPL/B+1vOw4pzDPn4papLTSMpLVF9G8uvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.31.0': + resolution: {integrity: sha512-HoF2EtcyP3JR4R3jLPHohZ9lFcj1QLJyGmFfLKDTvUUjPiFuK4XZ6L1OV9HhaqvN0xY+tWKfNdCPS3r33rd0Xw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-undici@0.22.0': + resolution: {integrity: sha512-yb6vEWUPOrD5i7yR1XceEEqiVHbMgr5YnUPnom5eQVCjvrTkEVswyrf9i+vvJR+28wrNqILIIphWgOOx6BjnTQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + + '@opentelemetry/instrumentation-winston@0.56.0': + resolution: {integrity: sha512-ITIA0Qe61CQ6FQU/bN23pNBvJ+5U0ofoASMOOYrODtXyV9wI267AigNTTwDmv2Myt8dPEFvvVFJZKhiZLIpehA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.212.0': + resolution: {integrity: sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.212.0': + resolution: {integrity: sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.212.0': + resolution: {integrity: sha512-YidOSlzpsun9uw0iyIWrQp6HxpMtBlECE3tiHGAsnpEqJWbAUWcMnIffvIuvTtTQ1OyRtwwaE79dWSQ8+eiB7g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.212.0': + resolution: {integrity: sha512-bj7zYFOg6Db7NUwsRZQ/WoVXpAf41WY2gsd3kShSfdpZQDRKHWJiRZIg7A8HvWsf97wb05rMFzPbmSHyjEl9tw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.5.1': + resolution: {integrity: sha512-AU6sZgunZrZv/LTeHP+9IQsSSH5p3PtOfDPe8VTdwYH69nZCfvvvXehhzu+9fMW2mgJMh5RVpiH8M9xuYOu5Dg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@2.5.1': + resolution: {integrity: sha512-8+SB94/aSIOVGDUPRFSBRHVUm2A8ye1vC6/qcf/D+TF4qat7PC6rbJhRxiUGDXZtMtKEPM/glgv5cBGSJQymSg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/redis-common@0.38.2': + resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} + engines: {node: ^18.19.0 || >=20.6.0} + + '@opentelemetry/resource-detector-alibaba-cloud@0.33.2': + resolution: {integrity: sha512-EaS54zwYmOg9Ttc79juaktpCBYqyh2IquXl534sLls+c1/pc8LZfWPMqytFt+iBvSPQ6ajraUnvi6cun4AhSjQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-aws@2.12.0': + resolution: {integrity: sha512-VelueKblsnQEiBVqEYcvM9VEb+B8zN6nftltdO9HAD7qi/OlicP4z/UGJ9EeW2m++WabdMoj0G3QVL8YV0P9tw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-azure@0.20.0': + resolution: {integrity: sha512-iRy+O2cB6DOlQ/OONaK+L8Cp8nLS89dZVRp6KgnFAfzykXuq9Ws/ygJKcU3CCmjkgY5j2Vk3uVTre/E35bWhYg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-container@0.8.3': + resolution: {integrity: sha512-5J0JP2cy655rBKM9Doz26ffO3rG+Xqm7OXeNXkckzmc3JmL6Bj3dPBKugPYsfemhEIqtf7INH9UmPQqTMuWoHg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-gcp@0.47.0': + resolution: {integrity: sha512-57T/kRVdU0ch1P4KPEkmU2b5mWNlUs8hHgqrBYVF+fNZMc1jMdL1mANZhEzoLtWKIeoCEy+57Itt7RkXAYNJiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resources@2.5.1': + resolution: {integrity: sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.212.0': + resolution: {integrity: sha512-qglb5cqTf0mOC1sDdZ7nfrPjgmAqs2OxkzOPIf2+Rqx8yKBK0pS7wRtB1xH30rqahBIut9QJDbDePyvtyqvH/Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.5.1': + resolution: {integrity: sha512-RKMn3QKi8nE71ULUo0g/MBvq1N4icEBo7cQSKnL3URZT16/YH3nSVgWegOjwx7FRBTrjOIkMJkCUn/ZFIEfn4A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.212.0': + resolution: {integrity: sha512-tJzVDk4Lo44MdgJLlP+gdYdMnjxSNsjC/IiTxj5CFSnsjzpHXwifgl3BpUX67Ty3KcdubNVfedeBc/TlqHXwwg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.5.1': + resolution: {integrity: sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.5.1': + resolution: {integrity: sha512-9lopQ6ZoElETOEN0csgmtEV5/9C7BMfA7VtF4Jape3i954b6sTY2k3Xw3CxUTKreDck/vpAuJM+EDo4zheUw+A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + + '@opentelemetry/winston-transport@0.22.0': + resolution: {integrity: sha512-VuiSjTxfAk5GNuTtBPbvPEDPFV8U9qWgn4DyGHuBNfuJrBcz21mmRyQxpOR+ueG6oA7Nrx7MmTFJ4JiWDvF2Vw==} + engines: {node: ^18.19.0 || >=20.6.0} + + '@otplib/core@13.3.0': + resolution: {integrity: sha512-pnQDOuCmFVeF/XnboJq9TOJgLoo2idNPJKMymOF8vGqJJ+ReKRYM9bUGjNPRWC0tHjMwu1TXbnzyBp494JgRag==} + + '@otplib/hotp@13.3.0': + resolution: {integrity: sha512-XJMZGz2bg4QJwK7ulvl1GUI2VMn/flaIk/E/BTKAejHsX2kUtPF1bRhlZ2+elq8uU5Fs9Z9FHcQK2CPZNQbbUQ==} + + '@otplib/plugin-base32-scure@13.3.0': + resolution: {integrity: sha512-/jYbL5S6GB0Ie3XGEWtLIr9s5ZICl/BfmNL7+8/W7usZaUU4GiyLd2S+JGsNCslPyqNekSudD864nDAvRI0s8w==} + + '@otplib/plugin-crypto-noble@13.3.0': + resolution: {integrity: sha512-wmV+jBVncepgwv99G7Plrdzd0tHfbpXk2U+OD7MO7DzpDqOYEgOPi+IIneksJSTL8QvWdfi+uQEuhnER4fKouA==} + + '@otplib/totp@13.3.0': + resolution: {integrity: sha512-XfjGNoN8d9S3Ove2j7AwkVV7+QDFsV7Lm7YwSiezNaHffkWtJ60aJYpmf+01dARdPST71U2ptueMsRJso4sq4A==} + + '@otplib/uri@13.3.0': + resolution: {integrity: sha512-3oh6nBXy+cm3UX9cxEAGZiDrfxHU2gfelYFV+XNCx+8dq39VaQVymwlU2yjPZiMAi/3agaUeEftf2RwM5F+Cyg==} + + '@phc/format@1.0.0': + resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} + engines: {node: '>=10'} + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@poppinss/macroable@1.1.0': + resolution: {integrity: sha512-y/YKzZDuG8XrpXpM7Z1RdQpiIc0MAKyva24Ux1PB4aI7RiSI/79K8JVDcdyubriTm7vJ1LhFs8CrZpmPnx/8Pw==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@rollup/plugin-commonjs@29.0.0': + resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@16.0.3': + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + + '@scure/base@2.0.0': + resolution: {integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==} + + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + '@so-ric/colorspace@1.1.6': + resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@sveltejs/acorn-typescript@1.0.9': + resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-node@5.5.4': + resolution: {integrity: sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ==} + peerDependencies: + '@sveltejs/kit': ^2.4.0 + + '@sveltejs/kit@2.53.4': + resolution: {integrity: sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ^5.3.3 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + typescript: + optional: true + + '@sveltejs/vite-plugin-svelte-inspector@5.0.2': + resolution: {integrity: sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@sveltejs/vite-plugin-svelte@6.2.4': + resolution: {integrity: sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@swc/helpers@0.5.19': + resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + + '@tailwindcss/forms@0.5.11': + resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' + + '@tailwindcss/node@4.2.1': + resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} + + '@tailwindcss/oxide-android-arm64@4.2.1': + resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.1': + resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.1': + resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.1': + resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.1': + resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} + engines: {node: '>= 20'} + + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tailwindcss/vite@4.2.1': + resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@types/aws-lambda@8.10.161': + resolution: {integrity: sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==} + + '@types/bun@1.3.9': + resolution: {integrity: sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw==} + + '@types/bunyan@1.8.11': + resolution: {integrity: sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/mailparser@3.4.6': + resolution: {integrity: sha512-wVV3cnIKzxTffaPH8iRnddX1zahbYB1ZEoAxyhoBo3TBCBuK6nZ8M8JYO/RhsCuuBVOw/DEN/t/ENbruwlxn6Q==} + + '@types/memcached@2.2.10': + resolution: {integrity: sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==} + + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + + '@types/oracledb@6.5.2': + resolution: {integrity: sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==} + + '@types/pdfkit@0.14.0': + resolution: {integrity: sha512-X94hoZVr9dNfV23roeXRm57AWS+AOMak3gq2wZvn4TXiLvXE8+TrYaM5IkMyZbGRw49jEqI49rP/UVL3+C3Svg==} + + '@types/pg-pool@2.0.7': + resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} + + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + + '@types/pg@8.16.0': + resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==} + + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + + '@types/tmp@0.2.6': + resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} + + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + + '@types/validator@13.15.10': + resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + + '@typeschema/class-validator@0.3.0': + resolution: {integrity: sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==} + peerDependencies: + class-validator: ^0.14.1 + peerDependenciesMeta: + class-validator: + optional: true + + '@typeschema/core@0.14.0': + resolution: {integrity: sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==} + peerDependencies: + '@types/json-schema': ^7.0.15 + peerDependenciesMeta: + '@types/json-schema': + optional: true + + '@valibot/to-json-schema@1.5.0': + resolution: {integrity: sha512-GE7DmSr1C2UCWPiV0upRH6mv0cCPsqYGs819fb6srCS1tWhyXrkGGe+zxUiwzn/L1BOfADH4sNjY/YHCuP8phQ==} + peerDependencies: + valibot: ^1.2.0 + + '@vinejs/compiler@3.0.0': + resolution: {integrity: sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==} + engines: {node: '>=18.0.0'} + + '@vinejs/vine@3.0.1': + resolution: {integrity: sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==} + engines: {node: '>=18.16.0'} + + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + + '@zone-eu/mailsplit@5.4.8': + resolution: {integrity: sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argon2@0.43.1: + resolution: {integrity: sha512-TfOzvDWUaQPurCT1hOwIeFNkgrAJDpbBGBGWDgzDsm11nNhImc13WhdGdCU6K7brkp8VpeY07oGtSex0Wmhg8w==} + engines: {node: '>=16.17.0'} + + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + + arkregex@0.0.5: + resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} + + arktype@2.1.29: + resolution: {integrity: sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + better-auth@1.4.20: + resolution: {integrity: sha512-cUQaUhZ/EZwb7xgoL9wHl78yWp0eaxC/L++B/r8RJxk23L766Tk7fLjWG6bQK8eAHDDpfQNwXsJowiei8tJWJw==} + peerDependencies: + '@lynx-js/react': '*' + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + '@sveltejs/kit': ^2.0.0 + '@tanstack/react-start': ^1.0.0 + '@tanstack/solid-start': ^1.0.0 + better-sqlite3: ^12.0.0 + drizzle-kit: '>=0.31.4' + drizzle-orm: '>=0.41.0' + mongodb: ^6.0.0 || ^7.0.0 + mysql2: ^3.0.0 + next: ^14.0.0 || ^15.0.0 || ^16.0.0 + pg: ^8.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + solid-js: ^1.0.0 + svelte: ^4.0.0 || ^5.0.0 + vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@prisma/client': + optional: true + '@sveltejs/kit': + optional: true + '@tanstack/react-start': + optional: true + '@tanstack/solid-start': + optional: true + better-sqlite3: + optional: true + drizzle-kit: + optional: true + drizzle-orm: + optional: true + mongodb: + optional: true + mysql2: + optional: true + next: + optional: true + pg: + optional: true + prisma: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vitest: + optional: true + vue: + optional: true + + better-call@1.1.8: + resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==} + peerDependencies: + zod: ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + bits-ui@2.16.2: + resolution: {integrity: sha512-bgEpRRF7Ck9nRP1pbuKVxpaSMrz+8Pm0y+dmuvlkrSe+uUwIQECef29y6eslFHM6pCAubUh7STrsTLUUp8fzFQ==} + engines: {node: '>=20'} + peerDependencies: + '@internationalized/date': ^3.8.1 + svelte: ^5.33.0 + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bun-types@1.3.9: + resolution: {integrity: sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg==} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + + class-validator@0.14.4: + resolution: {integrity: sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} + + color@5.0.3: + resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} + engines: {node: '>=18'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo-voronoi@2.1.0: + resolution: {integrity: sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate-path@2.3.0: + resolution: {integrity: sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-tile@1.0.0: + resolution: {integrity: sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q==} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-tricontour@1.1.0: + resolution: {integrity: sha512-G7gHKj89n2owmkGb6WX6ixcnQ0Kf/0wpa9VIh9DGdbHu8wdrlaHU4ir3/bFNERl8N8nn4G7e7qbtBG8N9caihQ==} + engines: {node: '>=12'} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + date-fns-tz@3.2.0: + resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==} + peerDependencies: + date-fns: ^3.0.0 || ^4.0.0 + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devalue@5.6.3: + resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} + + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + + drizzle-kit@0.31.9: + resolution: {integrity: sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==} + hasBin: true + + drizzle-orm@0.45.1: + resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + effect@3.19.19: + resolution: {integrity: sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg==} + + embla-carousel-reactive-utils@8.6.0: + resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel-svelte@8.6.0: + resolution: {integrity: sha512-ZDsKk8Sdv+AUTygMYcwZjfRd1DTh+JSUzxkOo8b9iKAkYjg+39mzbY/lwHsE3jXSpKxdKWS69hPSNuzlOGtR2Q==} + peerDependencies: + svelte: ^3.49.0 || ^4.0.0 || ^5.0.0 + + embla-carousel@8.6.0: + resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + + encoding-japanese@2.2.0: + resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==} + engines: {node: '>=8.10.0'} + + enhanced-resolve@5.20.0: + resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + esrap@2.2.3: + resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + formsnap@2.0.1: + resolution: {integrity: sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.0.0 + sveltekit-superforms: ^2.19.0 + + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gaxios@7.1.3: + resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} + engines: {node: '>=18'} + + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hono@4.12.8: + resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==} + engines: {node: '>=16.9.0'} + + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + imapflow@1.2.10: + resolution: {integrity: sha512-tqmk0Gj4YBEnGCjjrUYWIf3Z4tzn4iihUcMkBRbafvHF3LqEiYNCSJAAYYbwERFxlikOJ3zzqtEcoxCUTjMv2Q==} + + import-in-the-middle@2.0.6: + resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} + + import-in-the-middle@3.0.0: + resolution: {integrity: sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==} + engines: {node: '>=18'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + ioredis@5.10.0: + resolution: {integrity: sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA==} + engines: {node: '>=12.22.0'} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + kysely@0.28.11: + resolution: {integrity: sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==} + engines: {node: '>=20.0.0'} + + layerchart@2.0.0-next.43: + resolution: {integrity: sha512-1Ywm38NdzHWKwgaAHq3EcqshIgsq+pylntSnVWAVazXUk/NsxPcxdpR3tMt3ySjWV0ZPBBgLs78sdVf7FTgd+g==} + peerDependencies: + svelte: ^5.0.0 + + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + + libbase64@1.3.0: + resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==} + + libmime@5.3.7: + resolution: {integrity: sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==} + + libphonenumber-js@1.12.38: + resolution: {integrity: sha512-vwzxmasAy9hZigxtqTbFEwp8ZdZ975TiqVDwj5bKx5sR+zi5ucUQy9mbVTkKM9GzqdLdxux/hTw2nmN5J7POMA==} + + libqp@2.1.1: + resolution: {integrity: sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==} + + lightningcss-android-arm64@1.31.1: + resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.31.1: + resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.31.1: + resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.31.1: + resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.31.1: + resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.31.1: + resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.31.1: + resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.31.1: + resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.31.1: + resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.31.1: + resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.31.1: + resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.31.1: + resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + engines: {node: '>= 12.0.0'} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mailparser@3.9.3: + resolution: {integrity: sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==} + + memoize-weak@1.0.2: + resolution: {integrity: sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==} + + memoize@10.2.0: + resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==} + engines: {node: '>=18'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + mode-watcher@1.1.0: + resolution: {integrity: sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==} + peerDependencies: + svelte: ^5.27.0 + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + engines: {node: ^18 || >=20} + hasBin: true + + nanostores@1.1.1: + resolution: {integrity: sha512-EYJqS25r2iBeTtGQCHidXl1VfZ1jXM7Q04zXJOrMlxVVmD0ptxJaNux92n1mJ7c5lN3zTq12MhH/8x59nP+qmg==} + engines: {node: ^20.0.0 || >=22.0.0} + + neverthrow@8.2.0: + resolution: {integrity: sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==} + engines: {node: '>=18'} + + node-addon-api@8.6.0: + resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==} + engines: {node: ^18 || ^20 || >= 21} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + nodemailer@7.0.13: + resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==} + engines: {node: '>=6.0.0'} + + nodemailer@8.0.1: + resolution: {integrity: sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==} + engines: {node: '>=6.0.0'} + + normalize-url@8.1.1: + resolution: {integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==} + engines: {node: '>=14.16'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + otplib@13.3.0: + resolution: {integrity: sha512-VYMKyyDG8yt2q+z58sz54/EIyTh7+tyMrjeemR44iVh5+dkKtIs57irTqxjH+IkAL1uMmG1JIFhG5CxTpqdU5g==} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + paneforge@1.0.2: + resolution: {integrity: sha512-KzmIXQH1wCfwZ4RsMohD/IUtEjVhteR+c+ulb/CHYJHX8SuDXoJmChtsc/Xs5Wl8NHS4L5Q7cxL8MG40gSU1bA==} + peerDependencies: + svelte: ^5.29.0 + + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.12.0: + resolution: {integrity: sha512-uOANXNRACNdElMXJ0tPz6RBM0XQ61nONGAwlt8da5zs/iUOOCLBQOHSXnrC6fMsvtjxbOJrZZl5IScGv+7mpbg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + postgres@3.4.8: + resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==} + engines: {node: '>=12'} + + prettier-plugin-sort-imports@1.8.11: + resolution: {integrity: sha512-ApmZkEmNh2Fi6TAcYNgUR15rsP1+omX43+8neY+MXfNZ7JQwrqSdjzKhLUYTtaLo52aaC9gCs+lJaYSU8oSJJA==} + peerDependencies: + typescript: '>4.0.0' + + prettier-plugin-svelte@3.5.0: + resolution: {integrity: sha512-2lLO/7EupnjO/95t+XZesXs8Bf3nYLIDfCo270h5QWbj/vjLqmrQ1LiRk9LPggxSDsnVYfehamZNf+rgQYApZg==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + + prettier-plugin-tailwindcss@0.7.2: + resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + + protobufjs@8.0.0: + resolution: {integrity: sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==} + engines: {node: '>=12.0.0'} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rou3@0.7.12: + resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + + runed@0.23.4: + resolution: {integrity: sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==} + peerDependencies: + svelte: ^5.7.0 + + runed@0.25.0: + resolution: {integrity: sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg==} + peerDependencies: + svelte: ^5.7.0 + + runed@0.28.0: + resolution: {integrity: sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==} + peerDependencies: + svelte: ^5.7.0 + + runed@0.29.2: + resolution: {integrity: sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA==} + peerDependencies: + svelte: ^5.7.0 + + runed@0.31.1: + resolution: {integrity: sha512-v3czcTnO+EJjiPvD4dwIqfTdHLZ8oH0zJheKqAHh9QMViY7Qb29UlAMRpX7ZtHh7AFqV60KmfxaJ9QMy+L1igQ==} + peerDependencies: + svelte: ^5.7.0 + + runed@0.35.1: + resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==} + peerDependencies: + '@sveltejs/kit': ^2.21.0 + svelte: ^5.7.0 + peerDependenciesMeta: + '@sveltejs/kit': + optional: true + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + set-cookie-parser@3.0.1: + resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + superstruct@2.0.2: + resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} + engines: {node: '>=14.0.0'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svelte-check@4.4.4: + resolution: {integrity: sha512-F1pGqXc710Oi/wTI4d/x7d6lgPwwfx1U6w3Q35n4xsC2e8C/yN2sM1+mWxjlMcpAfWucjlq4vPi+P4FZ8a14sQ==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte-sonner@1.0.7: + resolution: {integrity: sha512-1EUFYmd7q/xfs2qCHwJzGPh9n5VJ3X6QjBN10fof2vxgy8fYE7kVfZ7uGnd7i6fQaWIr5KvXcwYXE/cmTEjk5A==} + peerDependencies: + svelte: ^5.0.0 + + svelte-toolbelt@0.10.6: + resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.30.2 + + svelte-toolbelt@0.5.0: + resolution: {integrity: sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.0.0-next.126 + + svelte-toolbelt@0.7.1: + resolution: {integrity: sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.0.0 + + svelte-toolbelt@0.9.3: + resolution: {integrity: sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.30.2 + + svelte@5.53.6: + resolution: {integrity: sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==} + engines: {node: '>=18'} + + sveltekit-superforms@2.30.0: + resolution: {integrity: sha512-EzXD7sHbi7yBU/eNtzVm6P6axcrVM8BArkbiT96Vdx48s5m4KXte/tbbp3UULtEW8Nk9wt2hYkGeq7nDBwVceg==} + peerDependencies: + '@sveltejs/kit': 1.x || 2.x + svelte: 3.x || 4.x || >=5.0.0-next.51 + + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + + tailwind-variants@3.2.2: + resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==} + engines: {node: '>=16.x', pnpm: '>=7.x'} + peerDependencies: + tailwind-merge: '>=3.0.0' + tailwindcss: '*' + peerDependenciesMeta: + tailwind-merge: + optional: true + + tailwindcss@4.2.1: + resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + + tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tlds@1.261.0: + resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} + hasBin: true + + toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + + ts-deepmerge@7.0.3: + resolution: {integrity: sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==} + engines: {node: '>=14.13.1'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + turbo-darwin-64@2.8.16: + resolution: {integrity: sha512-KWa4hUMWrpADC6Q/wIHRkBLw6X6MV9nx6X7hSXbTrrMz0KdaKhmfudUZ3sS76bJFmgArBU25cSc0AUyyrswYxg==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.8.16: + resolution: {integrity: sha512-NBgaqBDLQSZlJR4D5XCkQq6noaO0RvIgwm5eYFJYL3bH5dNu8o0UBpq7C5DYnQI8+ybyoHFjT5/icN4LeUYLow==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.8.16: + resolution: {integrity: sha512-VYPdcCRevI9kR/hr1H1xwXy7QQt/jNKiim1e1mjANBXD2E9VZWMkIL74J1Huad5MbU3/jw7voHOqDPLJPC2p6w==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.8.16: + resolution: {integrity: sha512-beq8tgUVI3uwkQkXJMiOr/hfxQRw54M3elpBwqgYFfemiK5LhCjjcwO0DkE8GZZfElBIlk+saMAQOZy3885wNQ==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.8.16: + resolution: {integrity: sha512-Ig7b46iUgiOIkea/D3Z7H+zNzvzSnIJcLYFpZLA0RxbUTrbLhv9qIPwv3pT9p/abmu0LXVKHxaOo+p26SuDhzw==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.8.16: + resolution: {integrity: sha512-fOWjbEA2PiE2HEnFQrwNZKYEdjewyPc2no9GmrXklZnTCuMsxeCN39aVlKpKpim03Zq/ykIuvApGwq8ZbfS2Yw==} + cpu: [arm64] + os: [win32] + + turbo@2.8.16: + resolution: {integrity: sha512-u6e9e3cTTpE2adQ1DYm3A3r8y3LAONEx1jYvJx6eIgSY4bMLxIxs0riWzI0Z/IK903ikiUzRPZ2c1Ph5lVLkhA==} + hasBin: true + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + typebox@1.1.5: + resolution: {integrity: sha512-TBdiM4mSppvWdmRDK5PoocxrMOqGIU9TxmS9zdHH+k8S/+2SIaNlPfMlx3f6hISxma14t2yX7SRySg7+TYYT9w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + unplugin-icons@23.0.1: + resolution: {integrity: sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==} + peerDependencies: + '@svgr/core': '>=7.0.0' + '@svgx/core': ^1.0.1 + '@vue/compiler-sfc': ^3.0.2 + svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + '@svgr/core': + optional: true + '@svgx/core': + optional: true + '@vue/compiler-sfc': + optional: true + svelte: + optional: true + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + valibot@1.2.0: + resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + + validator@13.15.26: + resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} + engines: {node: '>= 0.10'} + + vaul-svelte@1.0.0-next.7: + resolution: {integrity: sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.0.0 + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} + engines: {node: '>= 12.0.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yup@1.7.1: + resolution: {integrity: sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==} + + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + + zod-v3-to-json-schema@4.0.0: + resolution: {integrity: sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w==} + peerDependencies: + zod: ^3.25 || ^4.0.14 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + + '@ark/schema@0.56.0': + dependencies: + '@ark/util': 0.56.0 + optional: true + + '@ark/util@0.56.0': + optional: true + + '@babel/runtime@7.28.6': + optional: true + + '@better-auth/core@1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)': + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@standard-schema/spec': 1.1.0 + better-call: 1.1.8(zod@4.3.6) + jose: 6.1.3 + kysely: 0.28.11 + nanostores: 1.1.1 + zod: 4.3.6 + + '@better-auth/telemetry@1.4.20(@better-auth/core@1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))': + dependencies: + '@better-auth/core': 1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + + '@better-auth/utils@0.3.0': {} + + '@better-fetch/fetch@1.1.21': {} + + '@colors/colors@1.6.0': {} + + '@dabh/diagnostics@2.0.8': + dependencies: + '@so-ric/colorspace': 1.1.6 + enabled: 2.0.0 + kuler: 2.0.0 + + '@dagrejs/dagre@1.1.8': + dependencies: + '@dagrejs/graphlib': 2.2.4 + + '@dagrejs/graphlib@2.2.4': {} + + '@drizzle-team/brocli@0.10.2': {} + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.13.6 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@exodus/schemasafe@1.3.0': + optional: true + + '@floating-ui/core@1.7.4': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.5': + dependencies: + '@floating-ui/core': 1.7.4 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@grpc/grpc-js@1.14.3': + dependencies: + '@grpc/proto-loader': 0.8.0 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + + '@hapi/hoek@9.3.0': + optional: true + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + optional: true + + '@hono/node-server@1.19.9(hono@4.12.8)': + dependencies: + hono: 4.12.8 + + '@iconify/json@2.2.444': + dependencies: + '@iconify/types': 2.0.0 + pathe: 2.0.3 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@internationalized/date@3.11.0': + dependencies: + '@swc/helpers': 0.5.19 + + '@ioredis/commands@1.5.1': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@layerstack/svelte-actions@1.0.1-next.14': + dependencies: + '@floating-ui/dom': 1.7.5 + '@layerstack/utils': 2.0.0-next.14 + d3-scale: 4.0.2 + + '@layerstack/svelte-state@0.1.0-next.19': + dependencies: + '@layerstack/utils': 2.0.0-next.14 + + '@layerstack/tailwind@2.0.0-next.17': + dependencies: + '@layerstack/utils': 2.0.0-next.14 + clsx: 2.1.1 + d3-array: 3.2.4 + lodash-es: 4.17.23 + tailwind-merge: 3.5.0 + + '@layerstack/utils@2.0.0-next.14': + dependencies: + d3-array: 3.2.4 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + lodash-es: 4.17.23 + + '@lucide/svelte@0.561.0(svelte@5.53.6)': + dependencies: + svelte: 5.53.6 + + '@noble/ciphers@2.1.1': {} + + '@noble/hashes@2.0.1': {} + + '@opentelemetry/api-logs@0.212.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/auto-instrumentations-node@0.70.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-aws-lambda': 0.64.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-aws-sdk': 0.67.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-bunyan': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-cassandra-driver': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-cucumber': 0.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.29.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dns': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.60.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fastify': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.31.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.60.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-grpc': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.60.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.21.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.60.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-memcached': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.65.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-nestjs-core': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-net': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-openai': 0.10.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-oracledb': 0.37.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.64.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pino': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.60.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-restify': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-router': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-runtime-node': 0.25.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-socket.io': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.31.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.22.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-winston': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-alibaba-cloud': 0.33.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-aws': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-azure': 0.20.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-container': 0.8.3(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-gcp': 0.47.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/configuration@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + yaml: 2.8.2 + + '@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.212.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-http@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.212.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-http@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-proto@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-prometheus@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-zipkin@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/instrumentation-amqplib@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-aws-lambda@0.64.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/aws-lambda': 8.10.161 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-aws-sdk@0.67.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-bunyan@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@types/bunyan': 1.8.11 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-cassandra-driver@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-connect@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-cucumber@0.28.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dataloader@0.29.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dns@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.60.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fastify@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fs@0.31.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-generic-pool@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.60.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-grpc@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.60.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.21.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.60.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-lru-memoizer@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-memcached@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/memcached': 2.2.10 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.65.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-nestjs-core@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-net@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-openai@0.10.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-oracledb@0.37.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/oracledb': 6.5.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.64.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@types/pg': 8.15.6 + '@types/pg-pool': 2.0.7 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pino@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.60.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-restify@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-router@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-runtime-node@0.25.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-socket.io@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.31.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-undici@0.22.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-winston@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.212.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + protobufjs: 8.0.0 + + '@opentelemetry/propagator-b3@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/redis-common@0.38.2': {} + + '@opentelemetry/resource-detector-alibaba-cloud@0.33.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resource-detector-aws@2.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/resource-detector-azure@0.20.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/resource-detector-container@0.8.3(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resource-detector-gcp@0.47.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + gcp-metadata: 8.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-logs@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-node@0.212.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.212.0 + '@opentelemetry/configuration': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.212.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-trace-node@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/semantic-conventions@1.40.0': {} + + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/winston-transport@0.22.0': + dependencies: + '@opentelemetry/api-logs': 0.212.0 + winston-transport: 4.9.0 + + '@otplib/core@13.3.0': {} + + '@otplib/hotp@13.3.0': + dependencies: + '@otplib/core': 13.3.0 + '@otplib/uri': 13.3.0 + + '@otplib/plugin-base32-scure@13.3.0': + dependencies: + '@otplib/core': 13.3.0 + '@scure/base': 2.0.0 + + '@otplib/plugin-crypto-noble@13.3.0': + dependencies: + '@noble/hashes': 2.0.1 + '@otplib/core': 13.3.0 + + '@otplib/totp@13.3.0': + dependencies: + '@otplib/core': 13.3.0 + '@otplib/hotp': 13.3.0 + '@otplib/uri': 13.3.0 + + '@otplib/uri@13.3.0': + dependencies: + '@otplib/core': 13.3.0 + + '@phc/format@1.0.0': {} + + '@pinojs/redact@0.4.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@polka/url@1.0.0-next.29': {} + + '@poppinss/macroable@1.1.0': + optional: true + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@rollup/plugin-commonjs@29.0.0(rollup@4.59.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.5.0(picomatch@4.0.3) + is-reference: 1.2.1 + magic-string: 0.30.21 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.59.0 + + '@rollup/plugin-json@6.1.0(rollup@4.59.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + optionalDependencies: + rollup: 4.59.0 + + '@rollup/plugin-node-resolve@16.0.3(rollup@4.59.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.11 + optionalDependencies: + rollup: 4.59.0 + + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.59.0 + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@scure/base@2.0.0': {} + + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + optional: true + + '@sideway/formula@3.0.1': + optional: true + + '@sideway/pinpoint@2.0.0': + optional: true + + '@so-ric/colorspace@1.1.6': + dependencies: + color: 5.0.3 + text-hex: 1.0.0 + + '@standard-schema/spec@1.1.0': {} + + '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': + dependencies: + acorn: 8.16.0 + + '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))': + dependencies: + '@rollup/plugin-commonjs': 29.0.0(rollup@4.59.0) + '@rollup/plugin-json': 6.1.0(rollup@4.59.0) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.59.0) + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + rollup: 4.59.0 + + '@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@standard-schema/spec': 1.1.0 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@types/cookie': 0.6.0 + acorn: 8.16.0 + cookie: 0.6.0 + devalue: 5.6.3 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + set-cookie-parser: 3.0.1 + sirv: 3.0.2 + svelte: 5.53.6 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + typescript: 5.9.3 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + obug: 2.1.1 + svelte: 5.53.6 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + deepmerge: 4.3.1 + magic-string: 0.30.21 + obug: 2.1.1 + svelte: 5.53.6 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + + '@swc/helpers@0.5.19': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/forms@0.5.11(tailwindcss@4.2.1)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 4.2.1 + + '@tailwindcss/node@4.2.1': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.0 + jiti: 2.6.1 + lightningcss: 1.31.1 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.1 + + '@tailwindcss/oxide-android-arm64@4.2.1': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.1': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.1': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + optional: true + + '@tailwindcss/oxide@4.2.1': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-x64': 4.2.1 + '@tailwindcss/oxide-freebsd-x64': 4.2.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-x64-musl': 4.2.1 + '@tailwindcss/oxide-wasm32-wasi': 4.2.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + + '@tailwindcss/typography@0.5.19(tailwindcss@4.2.1)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 4.2.1 + + '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@tailwindcss/node': 4.2.1 + '@tailwindcss/oxide': 4.2.1 + tailwindcss: 4.2.1 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + '@tanstack/table-core@8.21.3': {} + + '@types/aws-lambda@8.10.161': {} + + '@types/bun@1.3.9': + dependencies: + bun-types: 1.3.9 + + '@types/bunyan@1.8.11': + dependencies: + '@types/node': 25.5.0 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.5.0 + + '@types/cookie@0.6.0': {} + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/mailparser@3.4.6': + dependencies: + '@types/node': 25.5.0 + iconv-lite: 0.6.3 + + '@types/memcached@2.2.10': + dependencies: + '@types/node': 25.5.0 + + '@types/mysql@2.15.27': + dependencies: + '@types/node': 25.5.0 + + '@types/node@25.5.0': + dependencies: + undici-types: 7.18.2 + + '@types/oracledb@6.5.2': + dependencies: + '@types/node': 25.5.0 + + '@types/pdfkit@0.14.0': + dependencies: + '@types/node': 25.5.0 + + '@types/pg-pool@2.0.7': + dependencies: + '@types/pg': 8.16.0 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 25.5.0 + pg-protocol: 1.12.0 + pg-types: 2.2.0 + + '@types/pg@8.16.0': + dependencies: + '@types/node': 25.5.0 + pg-protocol: 1.12.0 + pg-types: 2.2.0 + + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 25.5.0 + + '@types/resolve@1.20.2': {} + + '@types/tedious@4.0.14': + dependencies: + '@types/node': 25.5.0 + + '@types/tmp@0.2.6': {} + + '@types/triple-beam@1.3.5': {} + + '@types/trusted-types@2.0.7': {} + + '@types/uuid@10.0.0': {} + + '@types/validator@13.15.10': + optional: true + + '@typeschema/class-validator@0.3.0(class-validator@0.14.4)': + dependencies: + '@typeschema/core': 0.14.0 + optionalDependencies: + class-validator: 0.14.4 + transitivePeerDependencies: + - '@types/json-schema' + optional: true + + '@typeschema/core@0.14.0': + optional: true + + '@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3))': + dependencies: + valibot: 1.2.0(typescript@5.9.3) + optional: true + + '@vinejs/compiler@3.0.0': + optional: true + + '@vinejs/vine@3.0.1': + dependencies: + '@poppinss/macroable': 1.1.0 + '@types/validator': 13.15.10 + '@vinejs/compiler': 3.0.0 + camelcase: 8.0.0 + dayjs: 1.11.19 + dlv: 1.1.3 + normalize-url: 8.1.1 + validator: 13.15.26 + optional: true + + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + + '@zone-eu/mailsplit@5.4.8': + dependencies: + libbase64: 1.3.0 + libmime: 5.3.7 + libqp: 2.1.1 + + acorn-import-attributes@1.9.5(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + argon2@0.43.1: + dependencies: + '@phc/format': 1.0.0 + node-addon-api: 8.6.0 + node-gyp-build: 4.8.4 + + aria-query@5.3.1: {} + + arkregex@0.0.5: + dependencies: + '@ark/util': 0.56.0 + optional: true + + arktype@2.1.29: + dependencies: + '@ark/schema': 0.56.0 + '@ark/util': 0.56.0 + arkregex: 0.0.5 + optional: true + + assertion-error@2.0.1: {} + + async@3.2.6: {} + + atomic-sleep@1.0.0: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + better-auth@1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): + dependencies: + '@better-auth/core': 1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/telemetry': 1.4.20(@better-auth/core@1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@noble/ciphers': 2.1.1 + '@noble/hashes': 2.0.1 + better-call: 1.1.8(zod@4.3.6) + defu: 6.1.4 + jose: 6.1.3 + kysely: 0.28.11 + nanostores: 1.1.1 + zod: 4.3.6 + optionalDependencies: + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + drizzle-kit: 0.31.9 + drizzle-orm: 0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8) + svelte: 5.53.6 + vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + better-call@1.1.8(zod@4.3.6): + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + rou3: 0.7.12 + set-cookie-parser: 2.7.2 + optionalDependencies: + zod: 4.3.6 + + bignumber.js@9.3.1: {} + + bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): + dependencies: + '@floating-ui/core': 1.7.4 + '@floating-ui/dom': 1.7.5 + '@internationalized/date': 3.11.0 + esm-env: 1.2.2 + runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + svelte: 5.53.6 + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + tabbable: 6.4.0 + transitivePeerDependencies: + - '@sveltejs/kit' + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + buffer-from@1.1.2: {} + + bun-types@1.3.9: + dependencies: + '@types/node': 25.5.0 + + camelcase@5.3.1: {} + + camelcase@8.0.0: + optional: true + + chai@6.2.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + cjs-module-lexer@2.2.0: {} + + class-validator@0.14.4: + dependencies: + '@types/validator': 13.15.10 + libphonenumber-js: 1.12.38 + validator: 13.15.26 + optional: true + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + cluster-key-slot@1.1.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-convert@3.1.3: + dependencies: + color-name: 2.1.0 + + color-name@1.1.4: {} + + color-name@2.1.0: {} + + color-string@2.1.4: + dependencies: + color-name: 2.1.0 + + color@5.0.3: + dependencies: + color-convert: 3.1.3 + color-string: 2.1.4 + + commander@7.2.0: {} + + commondir@1.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + cookie@0.6.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo-voronoi@2.1.0: + dependencies: + d3-array: 3.2.4 + d3-delaunay: 6.0.4 + d3-geo: 3.1.1 + d3-tricontour: 1.1.0 + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate-path@2.3.0: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-tile@1.0.0: {} + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-tricontour@1.1.0: + dependencies: + d3-delaunay: 6.0.4 + d3-scale: 4.0.2 + + data-uri-to-buffer@4.0.1: {} + + date-fns-tz@3.2.0(date-fns@4.1.0): + dependencies: + date-fns: 4.1.0 + + date-fns@4.1.0: {} + + dayjs@1.11.19: + optional: true + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + deepmerge@4.3.1: {} + + defu@6.1.4: {} + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + denque@2.1.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + devalue@5.6.3: {} + + dijkstrajs@1.0.3: {} + + dlv@1.1.3: + optional: true + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dotenv@16.6.1: {} + + dotenv@17.3.1: {} + + drizzle-kit@0.31.9: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + esbuild-register: 3.6.0(esbuild@0.25.12) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.9)(kysely@0.28.11)(postgres@3.4.8): + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/pg': 8.16.0 + bun-types: 1.3.9 + kysely: 0.28.11 + postgres: 3.4.8 + + eastasianwidth@0.2.0: {} + + effect@3.19.19: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + optional: true + + embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel-svelte@8.6.0(svelte@5.53.6): + dependencies: + embla-carousel: 8.6.0 + embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0) + svelte: 5.53.6 + + embla-carousel@8.6.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enabled@2.0.0: {} + + encoding-japanese@2.2.0: {} + + enhanced-resolve@5.20.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + es-module-lexer@1.7.0: {} + + esbuild-register@3.6.0(esbuild@0.25.12): + dependencies: + debug: 4.4.3 + esbuild: 0.25.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + + escalade@3.2.0: {} + + esm-env@1.2.2: {} + + esrap@2.2.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + exsolve@1.0.8: {} + + extend@3.0.2: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + optional: true + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fecha@4.2.3: {} + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + fn.name@1.1.0: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + formsnap@2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)): + dependencies: + svelte: 5.53.6 + svelte-toolbelt: 0.5.0(svelte@5.53.6) + sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3) + + forwarded-parse@2.1.2: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gaxios@7.1.3: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + rimraf: 5.0.10 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.3 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + + get-caller-file@2.0.5: {} + + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + google-logging-utils@1.1.3: {} + + graceful-fs@4.2.11: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hono@4.12.8: {} + + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + imapflow@1.2.10: + dependencies: + '@zone-eu/mailsplit': 5.4.8 + encoding-japanese: 2.2.0 + iconv-lite: 0.7.2 + libbase64: 1.3.0 + libmime: 5.3.7 + libqp: 2.1.1 + nodemailer: 8.0.1 + pino: 10.3.1 + socks: 2.8.7 + + import-in-the-middle@2.0.6: + dependencies: + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + + import-in-the-middle@3.0.0: + dependencies: + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + + inherits@2.0.4: {} + + inline-style-parser@0.2.7: {} + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + ioredis@5.10.0: + dependencies: + '@ioredis/commands': 1.5.1 + cluster-key-slot: 1.1.2 + debug: 4.4.3 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ip-address@10.1.0: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-fullwidth-code-point@3.0.0: {} + + is-module@1.0.0: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.8 + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + is-stream@2.0.1: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@2.6.1: {} + + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + optional: true + + jose@6.1.3: {} + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.28.6 + ts-algebra: 2.0.0 + optional: true + + kleur@4.1.5: {} + + kuler@2.0.0: {} + + kysely@0.28.11: {} + + layerchart@2.0.0-next.43(svelte@5.53.6): + dependencies: + '@dagrejs/dagre': 1.1.8 + '@layerstack/svelte-actions': 1.0.1-next.14 + '@layerstack/svelte-state': 0.1.0-next.19 + '@layerstack/tailwind': 2.0.0-next.17 + '@layerstack/utils': 2.0.0-next.14 + d3-array: 3.2.4 + d3-color: 3.1.0 + d3-delaunay: 6.0.4 + d3-dsv: 3.0.1 + d3-force: 3.0.0 + d3-geo: 3.1.1 + d3-geo-voronoi: 2.1.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-interpolate-path: 2.3.0 + d3-path: 3.1.0 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-sankey: 0.12.3 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-shape: 3.2.0 + d3-tile: 1.0.0 + d3-time: 3.1.0 + lodash-es: 4.17.23 + memoize: 10.2.0 + runed: 0.31.1(svelte@5.53.6) + svelte: 5.53.6 + + leac@0.6.0: {} + + libbase64@1.3.0: {} + + libmime@5.3.7: + dependencies: + encoding-japanese: 2.2.0 + iconv-lite: 0.6.3 + libbase64: 1.3.0 + libqp: 2.1.1 + + libphonenumber-js@1.12.38: + optional: true + + libqp@2.1.1: {} + + lightningcss-android-arm64@1.31.1: + optional: true + + lightningcss-darwin-arm64@1.31.1: + optional: true + + lightningcss-darwin-x64@1.31.1: + optional: true + + lightningcss-freebsd-x64@1.31.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.31.1: + optional: true + + lightningcss-linux-arm64-gnu@1.31.1: + optional: true + + lightningcss-linux-arm64-musl@1.31.1: + optional: true + + lightningcss-linux-x64-gnu@1.31.1: + optional: true + + lightningcss-linux-x64-musl@1.31.1: + optional: true + + lightningcss-win32-arm64-msvc@1.31.1: + optional: true + + lightningcss-win32-x64-msvc@1.31.1: + optional: true + + lightningcss@1.31.1: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.31.1 + lightningcss-darwin-arm64: 1.31.1 + lightningcss-darwin-x64: 1.31.1 + lightningcss-freebsd-x64: 1.31.1 + lightningcss-linux-arm-gnueabihf: 1.31.1 + lightningcss-linux-arm64-gnu: 1.31.1 + lightningcss-linux-arm64-musl: 1.31.1 + lightningcss-linux-x64-gnu: 1.31.1 + lightningcss-linux-x64-musl: 1.31.1 + lightningcss-win32-arm64-msvc: 1.31.1 + lightningcss-win32-x64-msvc: 1.31.1 + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-character@3.0.0: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash-es@4.17.23: {} + + lodash.camelcase@4.3.0: {} + + lodash.defaults@4.2.0: {} + + lodash.isarguments@3.1.0: {} + + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + + long@5.3.2: {} + + lru-cache@10.4.3: {} + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mailparser@3.9.3: + dependencies: + '@zone-eu/mailsplit': 5.4.8 + encoding-japanese: 2.2.0 + he: 1.2.0 + html-to-text: 9.0.5 + iconv-lite: 0.7.2 + libmime: 5.3.7 + linkify-it: 5.0.0 + nodemailer: 7.0.13 + punycode.js: 2.3.1 + tlds: 1.261.0 + + memoize-weak@1.0.2: {} + + memoize@10.2.0: + dependencies: + mimic-function: 5.0.1 + + mimic-function@5.0.1: {} + + mini-svg-data-uri@1.4.4: {} + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.3: {} + + mlly@1.8.0: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + mode-watcher@1.1.0(svelte@5.53.6): + dependencies: + runed: 0.25.0(svelte@5.53.6) + svelte: 5.53.6 + svelte-toolbelt: 0.7.1(svelte@5.53.6) + + module-details-from-path@1.0.4: {} + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + nanoid@5.1.6: {} + + nanostores@1.1.1: {} + + neverthrow@8.2.0: + optionalDependencies: + '@rollup/rollup-linux-x64-gnu': 4.59.0 + + node-addon-api@8.6.0: {} + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-gyp-build@4.8.4: {} + + nodemailer@7.0.13: {} + + nodemailer@8.0.1: {} + + normalize-url@8.1.1: + optional: true + + obug@2.1.1: {} + + on-exit-leak-free@2.1.2: {} + + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + + otplib@13.3.0: + dependencies: + '@otplib/core': 13.3.0 + '@otplib/hotp': 13.3.0 + '@otplib/plugin-base32-scure': 13.3.0 + '@otplib/plugin-crypto-noble': 13.3.0 + '@otplib/totp': 13.3.0 + '@otplib/uri': 13.3.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + package-manager-detector@1.6.0: {} + + paneforge@1.0.2(svelte@5.53.6): + dependencies: + runed: 0.23.4(svelte@5.53.6) + svelte: 5.53.6 + svelte-toolbelt: 0.9.3(svelte@5.53.6) + + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + pathe@2.0.3: {} + + peberminta@0.9.0: {} + + pg-int8@1.0.1: {} + + pg-protocol@1.12.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + pngjs@5.0.0: {} + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + postgres@3.4.8: {} + + prettier-plugin-sort-imports@1.8.11(typescript@5.9.3): + dependencies: + prettier: 3.8.1 + typescript: 5.9.3 + + prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.6): + dependencies: + prettier: 3.8.1 + svelte: 5.53.6 + + prettier-plugin-tailwindcss@0.7.2(prettier-plugin-sort-imports@1.8.11(typescript@5.9.3))(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.6))(prettier@3.8.1): + dependencies: + prettier: 3.8.1 + optionalDependencies: + prettier-plugin-sort-imports: 1.8.11(typescript@5.9.3) + prettier-plugin-svelte: 3.5.0(prettier@3.8.1)(svelte@5.53.6) + + prettier@3.8.1: {} + + process-warning@5.0.0: {} + + property-expr@2.0.6: + optional: true + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.5.0 + long: 5.3.2 + + protobufjs@8.0.0: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.5.0 + long: 5.3.2 + + punycode.js@2.3.1: {} + + pure-rand@6.1.0: + optional: true + + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + + quansync@0.2.11: {} + + quick-format-unescaped@4.0.4: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@4.1.2: {} + + real-require@0.2.0: {} + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + require-directory@2.1.1: {} + + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + + require-main-filename@2.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rimraf@5.0.10: + dependencies: + glob: 10.5.0 + + robust-predicates@3.0.2: {} + + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 + fsevents: 2.3.3 + + rou3@0.7.12: {} + + runed@0.23.4(svelte@5.53.6): + dependencies: + esm-env: 1.2.2 + svelte: 5.53.6 + + runed@0.25.0(svelte@5.53.6): + dependencies: + esm-env: 1.2.2 + svelte: 5.53.6 + + runed@0.28.0(svelte@5.53.6): + dependencies: + esm-env: 1.2.2 + svelte: 5.53.6 + + runed@0.29.2(svelte@5.53.6): + dependencies: + esm-env: 1.2.2 + svelte: 5.53.6 + + runed@0.31.1(svelte@5.53.6): + dependencies: + esm-env: 1.2.2 + svelte: 5.53.6 + + runed@0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): + dependencies: + dequal: 2.0.3 + esm-env: 1.2.2 + lz-string: 1.5.0 + svelte: 5.53.6 + optionalDependencies: + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + + rw@1.3.3: {} + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + + semver@7.7.4: {} + + set-blocking@2.0.0: {} + + set-cookie-parser@2.7.2: {} + + set-cookie-parser@3.0.1: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + smart-buffer@4.2.0: {} + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + split2@4.2.0: {} + + stack-trace@0.0.10: {} + + stackback@0.0.2: {} + + standard-as-callback@2.1.0: {} + + std-env@3.10.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + superstruct@2.0.2: + optional: true + + supports-preserve-symlinks-flag@1.0.0: {} + + svelte-check@4.4.4(picomatch@4.0.3)(svelte@5.53.6)(typescript@5.9.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.53.6 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch + + svelte-sonner@1.0.7(svelte@5.53.6): + dependencies: + runed: 0.28.0(svelte@5.53.6) + svelte: 5.53.6 + + svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6): + dependencies: + clsx: 2.1.1 + runed: 0.35.1(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6) + style-to-object: 1.0.14 + svelte: 5.53.6 + transitivePeerDependencies: + - '@sveltejs/kit' + + svelte-toolbelt@0.5.0(svelte@5.53.6): + dependencies: + clsx: 2.1.1 + style-to-object: 1.0.14 + svelte: 5.53.6 + + svelte-toolbelt@0.7.1(svelte@5.53.6): + dependencies: + clsx: 2.1.1 + runed: 0.23.4(svelte@5.53.6) + style-to-object: 1.0.14 + svelte: 5.53.6 + + svelte-toolbelt@0.9.3(svelte@5.53.6): + dependencies: + clsx: 2.1.1 + runed: 0.29.2(svelte@5.53.6) + style-to-object: 1.0.14 + svelte: 5.53.6 + + svelte@5.53.6: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@types/estree': 1.0.8 + '@types/trusted-types': 2.0.7 + acorn: 8.16.0 + aria-query: 5.3.1 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.6.3 + esm-env: 1.2.2 + esrap: 2.2.3 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + + sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3): + dependencies: + '@sveltejs/kit': 2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + devalue: 5.6.3 + memoize-weak: 1.0.2 + svelte: 5.53.6 + ts-deepmerge: 7.0.3 + optionalDependencies: + '@exodus/schemasafe': 1.3.0 + '@standard-schema/spec': 1.1.0 + '@typeschema/class-validator': 0.3.0(class-validator@0.14.4) + '@valibot/to-json-schema': 1.5.0(valibot@1.2.0(typescript@5.9.3)) + '@vinejs/vine': 3.0.1 + arktype: 2.1.29 + class-validator: 0.14.4 + effect: 3.19.19 + joi: 17.13.3 + json-schema-to-ts: 3.1.1 + superstruct: 2.0.2 + typebox: 1.1.5 + valibot: 1.2.0(typescript@5.9.3) + yup: 1.7.1 + zod: 4.3.6 + zod-v3-to-json-schema: 4.0.0(zod@4.3.6) + transitivePeerDependencies: + - '@types/json-schema' + - typescript + + tabbable@6.4.0: {} + + tailwind-merge@3.5.0: {} + + tailwind-variants@3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.1): + dependencies: + tailwindcss: 4.2.1 + optionalDependencies: + tailwind-merge: 3.5.0 + + tailwindcss@4.2.1: {} + + tapable@2.3.0: {} + + text-hex@1.0.0: {} + + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + + tiny-case@1.0.3: + optional: true + + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + tlds@1.261.0: {} + + toposort@2.0.2: + optional: true + + totalist@3.0.1: {} + + triple-beam@1.4.1: {} + + ts-algebra@2.0.0: + optional: true + + ts-deepmerge@7.0.3: {} + + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.13.6 + optionalDependencies: + fsevents: 2.3.3 + + turbo-darwin-64@2.8.16: + optional: true + + turbo-darwin-arm64@2.8.16: + optional: true + + turbo-linux-64@2.8.16: + optional: true + + turbo-linux-arm64@2.8.16: + optional: true + + turbo-windows-64@2.8.16: + optional: true + + turbo-windows-arm64@2.8.16: + optional: true + + turbo@2.8.16: + optionalDependencies: + turbo-darwin-64: 2.8.16 + turbo-darwin-arm64: 2.8.16 + turbo-linux-64: 2.8.16 + turbo-linux-arm64: 2.8.16 + turbo-windows-64: 2.8.16 + turbo-windows-arm64: 2.8.16 + + tw-animate-css@1.4.0: {} + + type-fest@2.19.0: + optional: true + + typebox@1.1.5: + optional: true + + typescript@5.9.3: {} + + uc.micro@2.1.0: {} + + ufo@1.6.3: {} + + undici-types@7.18.2: {} + + unplugin-icons@23.0.1(svelte@5.53.6): + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/utils': 3.1.0 + local-pkg: 1.1.2 + obug: 2.1.1 + unplugin: 2.3.11 + optionalDependencies: + svelte: 5.53.6 + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + valibot@1.2.0(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + validator@13.15.26: + optional: true + + vaul-svelte@1.0.0-next.7(svelte@5.53.6): + dependencies: + runed: 0.23.4(svelte@5.53.6) + svelte: 5.53.6 + svelte-toolbelt: 0.7.1(svelte@5.53.6) + + vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.5.0 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.31.1 + tsx: 4.21.0 + yaml: 2.8.2 + + vitefu@1.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): + optionalDependencies: + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 25.5.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + web-streams-polyfill@3.3.3: {} + + webpack-virtual-modules@0.6.2: {} + + which-module@2.0.1: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.19.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.8 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + xtend@4.0.2: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yaml@2.8.2: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@21.1.1: {} + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yup@1.7.1: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + optional: true + + zimmerframe@1.1.4: {} + + zod-v3-to-json-schema@4.0.0(zod@4.3.6): + dependencies: + zod: 4.3.6 + optional: true + + zod@4.3.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..37408aa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,9 @@ +packages: + - apps/* + - packages/* + +onlyBuiltDependencies: + - argon2 + - esbuild + - protobufjs + - sharp diff --git a/scripts/generate_example_env_file.py b/scripts/generate_example_env_file.py new file mode 100755 index 0000000..49bf0ee --- /dev/null +++ b/scripts/generate_example_env_file.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import re +from pathlib import Path + +ENV_ASSIGNMENT_RE = re.compile( + r"^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)(\s*=\s*)(.*)$" +) + + +def split_inline_comment(rhs: str) -> tuple[str, str]: + in_single = False + in_double = False + escaped = False + + for i, char in enumerate(rhs): + if escaped: + escaped = False + continue + + if char == "\\": + escaped = True + continue + + if char == "'" and not in_double: + in_single = not in_single + continue + + if char == '"' and not in_single: + in_double = not in_double + continue + + if char == "#" and not in_single and not in_double: + if i == 0 or rhs[i - 1].isspace(): + return rhs[:i], rhs[i:] + + return rhs, "" + + +def transform_line(line: str) -> str: + stripped = line.strip() + if stripped == "" or stripped.startswith("#"): + return line + + newline = "" + raw = line + if line.endswith("\r\n"): + newline = "\r\n" + raw = line[:-2] + elif line.endswith("\n"): + newline = "\n" + raw = line[:-1] + + match = ENV_ASSIGNMENT_RE.match(raw) + if not match: + return line + + prefix, key, delimiter, rhs = match.groups() + value_part, comment_part = split_inline_comment(rhs) + trailing_ws = value_part[len(value_part.rstrip()) :] + placeholder = f"${{{{project.{key}}}}}" + + return f"{prefix}{key}{delimiter}{placeholder}{trailing_ws}{comment_part}{newline}" + + +def generate_example_env(source_path: Path, target_path: Path) -> None: + lines = source_path.read_text(encoding="utf-8").splitlines(keepends=True) + transformed = [transform_line(line) for line in lines] + target_path.write_text("".join(transformed), encoding="utf-8") + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Generate .env.example using ${{project.KEY}} placeholders." + ) + parser.add_argument("--source", default=".env", help="Path to source .env file") + parser.add_argument( + "--target", default=".env.example", help="Path to output .env.example file" + ) + args = parser.parse_args() + + source_path = Path(args.source) + target_path = Path(args.target) + + if not source_path.exists(): + raise FileNotFoundError(f"Source env file not found: {source_path}") + + generate_example_env(source_path, target_path) + + +if __name__ == "__main__": + main() diff --git a/scripts/migrate.sh b/scripts/migrate.sh new file mode 100755 index 0000000..2b020c3 --- /dev/null +++ b/scripts/migrate.sh @@ -0,0 +1,7 @@ +echo "🔄 Migrating Primary DB" + +cd packages/db + +pnpm run db:migrate + +cd ../../ diff --git a/scripts/populate.env.sh b/scripts/populate.env.sh new file mode 100755 index 0000000..0879bb6 --- /dev/null +++ b/scripts/populate.env.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# copy over the environment variables from the base /.env file to all other apps & packages in apps/* and packages/* + +for dir in apps/*; do + if [ -d "$dir" ]; then + cp .env $dir/.env + fi +done + +for dir in packages/*; do + if [ -d "$dir" ]; then + cp .env $dir/.env + fi +done diff --git a/scripts/prod.start.sh b/scripts/prod.start.sh new file mode 100755 index 0000000..1504095 --- /dev/null +++ b/scripts/prod.start.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +APP_PATH=$1 + +if [ -z "$APP_PATH" ]; then + echo "Usage: prod.start.sh " + exit 1 +fi + +echo "Starting $APP_PATH" + +cd $APP_PATH + +pnpm run prod diff --git a/scripts/spinup.dev.env.sh b/scripts/spinup.dev.env.sh new file mode 100755 index 0000000..a38f08a --- /dev/null +++ b/scripts/spinup.dev.env.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker compose -f dev/docker-compose.dev.yaml up --wait -d diff --git a/scripts/teardown.dev.env.sh b/scripts/teardown.dev.env.sh new file mode 100755 index 0000000..b70cd08 --- /dev/null +++ b/scripts/teardown.dev.env.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker compose -f dev/docker-compose.dev.yaml down --remove-orphans diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6360d8f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "strictNullChecks": true + } +} \ No newline at end of file diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..5d9295f --- /dev/null +++ b/turbo.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://v2-8-12.turborepo.dev/schema.json", + "ui": "tui", + "tasks": { + "build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": ["build/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "prod": { + "cache": false + }, + "db:migrate": { + "cache": false + }, + "test": { + "cache": false + } + } +}