feat: new payment (#4629)

* feat: new payment

* update

* update

* update

* update

* feat: enhance plan feature formatting and update localization strings

* update

* update

* update

* update

* refactor: remove NeedActivationToast and related activation modal logic

* update

* feat(settings): guard paid settings

Centralized paid-level metadata in @follow/shared and exposed the SettingNamespace union.
Updated the builder helpers and every settings tab to pull paid badges from that shared map.
Hardened the atoms helper to block writes and surface default values when a user lacks the required role.
Added the necessary workspace dependencies for the atoms package.

* fix: types

* feat(rate-limit-notice): update error messaging and add upgrade prompt

* feat(settings): integrate review checks into settings components

* update

* fix(plan): correct priority calculation in setting page data

* feat(ai-summary): add upgrade suggestion for AI summary when plan is insufficient

* feat(settings): enhance PaidBadge component to conditionally render based on MAS review status

---------

Co-authored-by: DIYgod <i@diygod.me>
This commit is contained in:
Stephen Zhou 2025-10-31 11:42:57 +08:00 committed by GitHub
parent d255d7d6a2
commit 53e3578af2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 1124 additions and 695 deletions

View File

@ -16,6 +16,10 @@ export const [, , useServerConfigs, , getServerConfigs, setServerConfigs] = crea
),
)
export type ServerConfigs = ExtractResponseData<GetStatusConfigsResponse>
export type PaymentPlan = ServerConfigs["PAYMENT_PLAN_LIST"][number]
export type PaymentFeature = PaymentPlan["limit"]
export const useIsInMASReview = () => {
const serverConfigs = useServerConfigs()
return (
@ -24,3 +28,12 @@ export const useIsInMASReview = () => {
serverConfigs?.MAS_IN_REVIEW_VERSION === PKG.version
)
}
export const getIsInMASReview = () => {
const serverConfigs = getServerConfigs()
return (
typeof process !== "undefined" &&
process.mas &&
serverConfigs?.MAS_IN_REVIEW_VERSION === PKG.version
)
}

View File

@ -1,12 +1,15 @@
import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.js"
import { MotionButtonBase } from "@follow/components/ui/button/index.js"
import { cn } from "@follow/utils/utils"
import { isNeedUpgradeError } from "@follow-app/client-sdk"
import type { ReactNode } from "react"
import { useTranslation } from "react-i18next"
import { useIsInMASReview } from "~/atoms/server-configs"
import { CopyButton } from "~/components/ui/button/CopyButton"
import { Markdown } from "~/components/ui/markdown/Markdown"
import { useFeature } from "~/hooks/biz/useFeature"
import { useSettingModal } from "~/modules/settings/modal/useSettingModal"
interface AISummaryCardBaseProps {
/** Summary content to display */
@ -21,8 +24,6 @@ interface AISummaryCardBaseProps {
footerContent?: ReactNode
/** Custom loading state component */
loadingComponent?: ReactNode
/** Custom empty state component */
emptyComponent?: ReactNode
/** Title text for the AI Summary header */
title?: string
/** Whether to show the copy button */
@ -31,6 +32,8 @@ interface AISummaryCardBaseProps {
showAskAIButton?: boolean
/** Callback when Ask AI button is clicked */
onAskAI?: () => void
/** Error code returned when requesting the summary */
errorCode?: number
}
const DefaultLoadingState = () => (
@ -41,12 +44,24 @@ const DefaultLoadingState = () => (
</div>
)
const DefaultEmptyState = ({ message }: { message: string }) => (
<div className="py-4 text-center">
<i className="i-mingcute-document-line mb-2 text-2xl text-text-tertiary" />
<p className="text-sm text-text-secondary">{message}</p>
</div>
)
const DefaultEmptyState = ({
message,
shouldSuggestUpgrade,
}: {
message: string
shouldSuggestUpgrade?: boolean
}) => {
const settingModalPresent = useSettingModal()
return (
<div
className="text-center"
onClick={shouldSuggestUpgrade ? () => settingModalPresent("plan") : undefined}
>
<p className="text-sm text-text-secondary">{message}</p>
</div>
)
}
export const AISummaryCardBase: React.FC<AISummaryCardBaseProps> = ({
content,
@ -55,16 +70,20 @@ export const AISummaryCardBase: React.FC<AISummaryCardBaseProps> = ({
headerContent,
footerContent,
loadingComponent,
emptyComponent,
title = "AI Summary",
showCopyButton = true,
showAskAIButton = false,
onAskAI,
errorCode,
}) => {
const { t } = useTranslation("app")
const aiEnabled = useFeature("ai")
const isInMASReview = useIsInMASReview()
const hasContent = !isLoading && content
const normalizedErrorCode = typeof errorCode === "number" ? errorCode : undefined
const shouldSuggestUpgrade =
normalizedErrorCode !== undefined && !isInMASReview && isNeedUpgradeError(normalizedErrorCode)
return (
<div
@ -167,12 +186,16 @@ export const AISummaryCardBase: React.FC<AISummaryCardBaseProps> = ({
loadingComponent || <DefaultLoadingState />
) : hasContent ? (
<Markdown className="prose-sm max-w-none prose-p:m-0">{String(content)}</Markdown>
) : shouldSuggestUpgrade ? (
<DefaultEmptyState
message={t("ai.summary_upgrade_required_title")}
shouldSuggestUpgrade
/>
) : (
emptyComponent || <DefaultEmptyState message={t("ai.summary_not_available")} />
<DefaultEmptyState message={t("ai.summary_not_available")} />
)}
</AutoResizeHeight>
{/* Footer */}
{footerContent}
</div>
)

View File

@ -357,7 +357,6 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed
view,
),
active: isShowAISummaryOnce,
disabled: userRole === UserRole.Free || userRole === UserRole.Trial,
entryId,
}),
new EntryActionMenuItem({

View File

@ -1,63 +1,17 @@
import { UserRole } from "@follow/constants"
import { getFeedByIdOrUrl } from "@follow/store/feed/getter"
import { getSubscriptionByFeedId } from "@follow/store/subscription/getter"
import {
useFeedSubscriptionCount,
useListSubscriptionCount,
} from "@follow/store/subscription/hooks"
import { useUserRole } from "@follow/store/user/hooks"
import { t } from "i18next"
import { useCallback } from "react"
import { useNavigate } from "react-router"
import { withoutTrailingSlash, withTrailingSlash } from "ufo"
import { useEventCallback } from "usehooks-ts"
import { previewBackPath } from "~/atoms/preview"
import { useServerConfigs } from "~/atoms/server-configs"
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
import { CustomSafeError } from "~/errors/CustomSafeError"
import { useActivationModal } from "~/modules/activation"
import type { FeedFormDataValuesType } from "~/modules/discover/FeedForm"
import { FeedForm } from "~/modules/discover/FeedForm"
import type { ListFormDataValuesType } from "~/modules/discover/ListForm"
import { ListForm } from "~/modules/discover/ListForm"
const useCanFollowMoreInboxAndNotify = () => {
const role = useUserRole()
const listCurrentCount = useListSubscriptionCount()
const feedCurrentCount = useFeedSubscriptionCount()
const presentActivationModal = useActivationModal()
const serverConfigs = useServerConfigs()
return useEventCallback((type: "list" | "feed") => {
if (role === UserRole.Free || role === UserRole.Trial) {
const LIMIT =
(type !== "list"
? serverConfigs?.MAX_TRIAL_USER_FEED_SUBSCRIPTION
: serverConfigs?.MAX_TRIAL_USER_LIST_SUBSCRIPTION) || 50
const CURRENT = type === "list" ? listCurrentCount : feedCurrentCount
const can = CURRENT < LIMIT
if (!can) {
presentActivationModal()
throw new CustomSafeError(
`Trial user cannot create more ${type}, limit: ${LIMIT}, current: ${CURRENT}`,
true,
)
}
return can
} else {
// const can = currentInboxCount < MAX_INBOX_COUNT
// if (!can) {
// // TODO
// }
// return can
return true
}
})
}
export interface FollowOptions {
isList: boolean
id?: string
@ -68,17 +22,10 @@ export interface FollowOptions {
}
export const useFollow = () => {
const { present } = useModalStack()
const canFollowMoreInboxAndNotify = useCanFollowMoreInboxAndNotify()
const navigate = useNavigate()
return useCallback(
(options?: FollowOptions) => {
if (options?.isList) {
canFollowMoreInboxAndNotify("list")
} else {
canFollowMoreInboxAndNotify("feed")
}
// Some feeds redirect xxx.com/feed to xxx.com/feed/
// Try to get a valid feed, then we can check isFollowed correctly
const feed =
@ -117,6 +64,6 @@ export const useFollow = () => {
},
})
},
[canFollowMoreInboxAndNotify, present],
[present],
)
}

View File

@ -3,12 +3,9 @@ import { userActions } from "@follow/store/user/store"
import { createDesktopAPIHeaders } from "@follow/utils/headers"
import { FollowClient } from "@follow-app/client-sdk"
import PKG from "@pkg"
import { createElement } from "react"
import { toast } from "sonner"
import { NetworkStatus, setApiStatus } from "~/atoms/network"
import { setLoginModalShow } from "~/atoms/user"
import { NeedActivationToast } from "~/modules/activation/NeedActivationToast"
import { getClientId, getSessionId } from "./client-session"
@ -78,25 +75,6 @@ followClient.addResponseInterceptor(async ({ response }) => {
if (response.status === 400 && json.code === 1003) {
router.navigate("/invitation")
}
if (json.code.toString().startsWith("11")) {
setTimeout(() => {
const toastId = toast.error(
createElement(NeedActivationToast, {
dimiss: () => {
toast.dismiss(toastId)
},
}),
{
closeButton: true,
duration: 10e4,
classNames: {
content: tw`w-full`,
},
},
)
}, 500)
}
} catch {
// ignore
}

View File

@ -1,16 +1,11 @@
import { DEV } from "@follow/shared/constants"
import { env } from "@follow/shared/env.desktop"
import { userActions } from "@follow/store/user/store"
import { createDesktopAPIHeaders } from "@follow/utils/headers"
import PKG from "@pkg"
import { ofetch } from "ofetch"
import { createElement } from "react"
import { toast } from "sonner"
import { NetworkStatus, setApiStatus } from "~/atoms/network"
import { setLoginModalShow } from "~/atoms/user"
import { NeedActivationToast } from "~/modules/activation/NeedActivationToast"
import { DebugRegistry } from "~/modules/debug/registry"
import { getClientId, getSessionId } from "./client-session"
@ -57,48 +52,8 @@ export const apiFetch = ofetch.create({
if (context.response.status === 400 && json.code === 1003) {
router.navigate("/invitation")
}
if (json.code.toString().startsWith("11")) {
setTimeout(() => {
const toastId = toast.error(
createElement(NeedActivationToast, {
dimiss: () => {
toast.dismiss(toastId)
},
}),
{
closeButton: true,
duration: 10e4,
classNames: {
content: tw`w-full`,
},
},
)
}, 500)
}
} catch {
// ignore
}
},
})
if (DEV) {
DebugRegistry.add("Activation Toast", () => {
setTimeout(() => {
const toastId = toast.error(
createElement(NeedActivationToast, {
dimiss: () => {
toast.dismiss(toastId)
},
}),
{
closeButton: true,
duration: 10e4,
classNames: {
content: tw`w-full`,
},
},
)
}, 500)
})
}

View File

@ -1,11 +1,13 @@
import { DEV } from "@follow/shared/constants"
import { cn } from "@follow/utils/utils"
import { FollowAPIError, isNeedUpgradeError } from "@follow-app/client-sdk"
import { t } from "i18next"
import { FetchError } from "ofetch"
import { createElement } from "react"
import type { ExternalToast } from "sonner"
import { toast } from "sonner"
import { getIsInMASReview } from "~/atoms/server-configs"
import { CopyButton } from "~/components/ui/button/CopyButton"
import { Markdown } from "~/components/ui/markdown/Markdown"
import { DebugRegistry } from "~/modules/debug/registry"
@ -32,6 +34,20 @@ export const getFetchErrorInfo = (
}
}
if (error instanceof FollowAPIError && error.code) {
const code = Number(error.code)
try {
const i18nKey = `errors:${code}` as any
const i18nMessage = t(i18nKey) === i18nKey ? error.message : t(i18nKey)
return {
message: i18nMessage,
code,
}
} catch {
return { message: error.message }
}
}
return { message: error.message }
}
@ -78,6 +94,17 @@ export const toastFetchError = (
}
}
if ("code" in error && error.code) {
code = Number(error.code)
try {
const tValue = t(`errors:${code}` as any)
const i18nMessage = tValue === code?.toString() ? error.message : tValue
message = i18nMessage
} catch {
message = error.message
}
}
// 2fa errors are handled by the form
if (code === 4007 || code === 4008) {
return
@ -94,9 +121,24 @@ export const toastFetchError = (
}
if (!_reason) {
const title = _title || message
toastOptions.description = _title ? message : undefined
return toast.error(title, toastOptions)
const title = _title || message || "Unknown error occurred"
toastOptions.description = _title ? message : ""
const isInMASReview = getIsInMASReview()
const needUpgradeError = code && !isInMASReview ? isNeedUpgradeError(code) : false
if (needUpgradeError) {
toastOptions.description = "Please upgrade your plan."
}
return toast.error(title, {
...toastOptions,
action: needUpgradeError
? {
label: "Upgrade",
onClick: () => {
window.router.showSettings({ tab: "plan" })
},
}
: undefined,
})
} else {
return toast.error(message || _title, {
duration: 5000,

View File

@ -32,6 +32,7 @@ import { useDialog } from "~/components/ui/modal/stacked/hooks"
import { useContextMenu } from "~/hooks/common/useContextMenu"
import { getI18n } from "~/i18n"
import { copyToClipboard, readFromClipboard } from "~/lib/clipboard"
import { toastFetchError } from "~/lib/error-parser"
import { downloadJsonFile, selectJsonFile } from "~/lib/export"
import { RuleCard } from "~/modules/action/rule-card"
import {
@ -421,7 +422,7 @@ const ActionButtonGroup = ({ onCreateRule }: { onCreateRule: () => void }) => {
toast(t("actions.saveSuccess"))
},
onError: (error) => {
toast.error(error)
toastFetchError(error)
},
})

View File

@ -1,29 +0,0 @@
import { toastStyles } from "@follow/components/ui/toast/styles.js"
import { stopPropagation } from "@follow/utils/dom"
import { useCallback } from "react"
import { useTranslation } from "react-i18next"
import { useSettingModal } from "../settings/modal/use-setting-modal-hack"
export const NeedActivationToast = (props: { dimiss: () => void }) => {
const settingModalPresent = useSettingModal()
const { t } = useTranslation()
return (
<div className="flex justify-between gap-3">
<div>{t("activation.plan.description")}</div>
<button
className={toastStyles.actionButton}
type="button"
onPointerDown={stopPropagation}
onClick={useCallback(() => {
settingModalPresent("plan")
props.dimiss()
}, [settingModalPresent, props])}
>
{t("activation.activate")}
</button>
</div>
)
}

View File

@ -2,6 +2,8 @@ import { cn } from "@follow/utils"
import { m } from "motion/react"
import * as React from "react"
import { useSettingModal } from "~/modules/settings/modal/useSettingModal"
import { parseAIError } from "../../utils/error"
interface RateLimitNoticeProps {
@ -16,6 +18,7 @@ interface RateLimitNoticeProps {
export const RateLimitNotice: React.FC<RateLimitNoticeProps> = ({ error, className }) => {
const parsedError = React.useMemo(() => parseAIError(error), [error])
const { isRateLimitError, errorData } = parsedError
const settingModalPresent = useSettingModal()
// Only render for rate limit errors
if (!isRateLimitError || !errorData) {
@ -57,7 +60,7 @@ export const RateLimitNotice: React.FC<RateLimitNoticeProps> = ({ error, classNa
parts.push(`${remainingTokens.toLocaleString()} tokens left`)
}
} else {
parts.push("AI usage limit reached")
parts.push("Upgrade plan to get more AI credits.")
}
// Reset time
@ -79,9 +82,10 @@ export const RateLimitNotice: React.FC<RateLimitNoticeProps> = ({ error, classNa
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
className={cn("mb-3", className)}
onClick={() => settingModalPresent("plan")}
>
<div className="flex items-center gap-2 rounded-lg border border-border bg-fill/50 px-3 py-2 backdrop-blur-sm">
<i className="i-mgc-information-cute-re size-4 flex-shrink-0 text-text-tertiary" />
<i className="i-mgc-power size-4 flex-shrink-0 text-text" />
<span className="min-w-0 flex-1 truncate text-xs text-text-secondary">
{buildMessage()}
</span>

View File

@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"
import { useEntryIsInReadabilitySuccess } from "~/atoms/readability"
import { useActionLanguage } from "~/atoms/settings/general"
import { AISummaryCardBase } from "~/components/ui/ai-summary-card"
import { getFetchErrorInfo } from "~/lib/error-parser"
interface EntrySummaryCardProps {
entryId: string
@ -22,6 +23,8 @@ export const EntrySummaryCard: React.FC<EntrySummaryCardProps> = ({ entryId, cla
actionLanguage,
enabled: true,
})
const summaryErrorCode =
summary.error instanceof Error ? getFetchErrorInfo(summary.error).code : undefined
return (
<m.div
@ -35,6 +38,7 @@ export const EntrySummaryCard: React.FC<EntrySummaryCardProps> = ({ entryId, cla
isLoading={summary.isLoading}
className={className}
title={t("ai_summary")}
errorCode={summaryErrorCode}
/>
</m.div>
)

View File

@ -399,10 +399,6 @@ export const useRegisterEntryCommands = () => {
icon: <i className="i-mgc-ai-cute-re" />,
category,
run: () => {
if (role === UserRole.Free || role === UserRole.Trial) {
presentActivationModal()
return
}
toggleShowAISummaryOnce()
},
},

View File

@ -1,48 +1,17 @@
import { Button } from "@follow/components/ui/button/index.js"
import { UserRole } from "@follow/constants"
import { useUserRole } from "@follow/store/user/hooks"
import { repository } from "@pkg"
import { useTranslation } from "react-i18next"
import { useEventCallback } from "usehooks-ts"
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
import { CustomSafeError } from "~/errors/CustomSafeError"
import { useActivationModal } from "../activation"
import { InboxTable } from "./Inbox"
import { InboxForm } from "./InboxForm"
const useCanCreateMoreInboxAndNotify = () => {
const role = useUserRole()
const presentActivationModal = useActivationModal()
return useEventCallback(() => {
if (role === UserRole.Free || role === UserRole.Trial) {
const can = false
if (!can) {
presentActivationModal()
throw new CustomSafeError(`Trial user cannot create more inboxes`, true)
}
return can
} else {
// const can = currentInboxCount < MAX_INBOX_COUNT
// if (!can) {
// // TODO
// }
// return can
return true
}
})
}
export function DiscoverInboxList() {
const { t } = useTranslation()
const { present } = useModalStack()
const preCheck = useCanCreateMoreInboxAndNotify()
return (
<div className="mx-auto w-full max-w-[540px] rounded-lg border bg-material-ultra-thin p-5 shadow-sm">
<div className="mb-4 flex flex-wrap items-center gap-2 text-sm text-zinc-500">
@ -63,7 +32,6 @@ export function DiscoverInboxList() {
<Button
textClassName="flex items-center gap-2"
onClick={() =>
preCheck() &&
present({
title: t("sidebar.feed_actions.new_inbox"),
content: () => <InboxForm asWidget />,

View File

@ -13,12 +13,19 @@ import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
import { RootPortal } from "@follow/components/ui/portal/index.js"
import { ScrollArea } from "@follow/components/ui/scroll-area/index.js"
import { Switch } from "@follow/components/ui/switch/index.jsx"
import { FeedViewType } from "@follow/constants"
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "@follow/components/ui/tooltip/index.js"
import { FeedViewType, UserRole } from "@follow/constants"
import { useFeedByIdOrUrl } from "@follow/store/feed/hooks"
import type { FeedModel } from "@follow/store/feed/types"
import { useCategories, useSubscriptionByFeedId } from "@follow/store/subscription/hooks"
import { subscriptionSyncService } from "@follow/store/subscription/store"
import { whoami } from "@follow/store/user/getters"
import { useUserRole } from "@follow/store/user/hooks"
import { tracker } from "@follow/tracker"
import { cn } from "@follow/utils/utils"
import type { FeedAnalyticsModel, ParsedEntry } from "@follow-app/client-sdk"
@ -30,11 +37,13 @@ import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import { z } from "zod"
import { useIsInMASReview } from "~/atoms/server-configs"
import { Autocomplete } from "~/components/ui/auto-completion"
import { useCurrentModal, useIsInModal } from "~/components/ui/modal/stacked/hooks"
import { getRouteParams } from "~/hooks/biz/useRouteParams"
import { useI18n } from "~/hooks/common"
import { toastFetchError } from "~/lib/error-parser"
import { useSettingModal } from "~/modules/settings/modal/useSettingModal"
import { feed as feedQuery, useFeedQuery } from "~/queries/feed"
import { ViewSelectorRadioGroup } from "../shared/ViewSelectorRadioGroup"
@ -49,6 +58,35 @@ const formSchema = z.object({
})
export type FeedFormDataValuesType = z.infer<typeof formSchema>
export const PaidBadge = () => {
const { t } = useTranslation("settings")
const settingModalPresent = useSettingModal()
const isInMASReview = useIsInMASReview()
const handleClick = useCallback(
(e) => {
e.preventDefault()
settingModalPresent("plan")
},
[settingModalPresent],
)
if (isInMASReview) {
return null
}
return (
<Tooltip>
<TooltipTrigger asChild>
<i className="i-mgc-power block text-accent" onClick={handleClick} />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{t("control.paid_badge.plus_or_higher")}</TooltipContent>
</TooltipPortal>
</Tooltip>
)
}
export const FeedForm: Component<{
url?: string
id?: string
@ -297,6 +335,10 @@ const FeedInnerForm = ({
form.setValue("title", feed.title || "")
}, [feed.title, form])
const role = useUserRole()
const isInMASReview = useIsInMASReview()
const disabledForRole = role === UserRole.Free && !isInMASReview
return (
<div className="flex flex-1 flex-col gap-y-4">
<FeedSummary isLoading={isLoading} feed={feed} analytics={analytics} showAnalytics />
@ -367,7 +409,10 @@ const FeedInnerForm = ({
<FormItem>
<div className="flex items-center justify-between">
<div>
<FormLabel>{t("feed_form.private_follow")}</FormLabel>
<FormLabel className="flex items-center gap-1">
<span>{t("feed_form.private_follow")}</span>
<PaidBadge />
</FormLabel>
<FormDescription>{t("feed_form.private_follow_description")}</FormDescription>
</div>
<FormControl>
@ -375,6 +420,7 @@ const FeedInnerForm = ({
className="shrink-0"
checked={field.value}
onCheckedChange={field.onChange}
disabled={disabledForRole}
/>
</FormControl>
</div>
@ -388,7 +434,10 @@ const FeedInnerForm = ({
<FormItem>
<div className="flex items-center justify-between">
<div>
<FormLabel>{t("feed_form.hide_from_timeline")}</FormLabel>
<FormLabel className="flex items-center gap-1">
<span>{t("feed_form.hide_from_timeline")}</span>
<PaidBadge />
</FormLabel>
<FormDescription>
{t("feed_form.hide_from_timeline_description")}
</FormDescription>
@ -398,6 +447,7 @@ const FeedInnerForm = ({
className="shrink-0"
checked={field.value}
onCheckedChange={field.onChange}
disabled={disabledForRole}
/>
</FormControl>
</div>

View File

@ -12,6 +12,7 @@ import {
} from "~/atoms/settings/ai"
import { useActionLanguage } from "~/atoms/settings/general"
import { AISummaryCardBase } from "~/components/ui/ai-summary-card"
import { getFetchErrorInfo } from "~/lib/error-parser"
export function AISummary({ entryId }: { entryId: string }) {
const { t } = useTranslation()
@ -30,6 +31,8 @@ export function AISummary({ entryId }: { entryId: string }) {
target: isInReadabilitySuccess ? "readabilityContent" : "content",
enabled: showAISummary,
})
const summaryErrorCode =
summary.error instanceof Error ? getFetchErrorInfo(summary.error).code : undefined
// Show Ask AI button when:
// 1. Panel style is floating AND panel is not visible
@ -54,6 +57,7 @@ export function AISummary({ entryId }: { entryId: string }) {
title={t("entry_content.ai_summary")}
showAskAIButton={shouldShowAskAI}
onAskAI={handleAskAI}
errorCode={summaryErrorCode}
/>
)
}

View File

@ -4,11 +4,57 @@ import { Input, TextArea } from "@follow/components/ui/input/index.js"
import { Label } from "@follow/components/ui/label/index.jsx"
import { SegmentGroup, SegmentItem } from "@follow/components/ui/segment/index.jsx"
import { Switch } from "@follow/components/ui/switch/index.jsx"
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "@follow/components/ui/tooltip/index.js"
import { cn } from "@follow/utils/utils"
import type { ChangeEventHandler, ReactNode } from "react"
import { useId, useState } from "react"
import { useCallback, useId, useState } from "react"
import { useTranslation } from "react-i18next"
import { titleCase } from "title-case"
import { useIsInMASReview } from "~/atoms/server-configs"
import { SettingPaidLevels } from "./helper/setting-builder"
import { useSetSettingTab } from "./modal/context"
export const PaidBadge: Component<{
paidLevel: SettingPaidLevels
}> = ({ paidLevel }) => {
const { t } = useTranslation("settings")
const setTab = useSetSettingTab()
const isInMASReview = useIsInMASReview()
const handleClick = useCallback(
(e) => {
e.preventDefault()
setTab("plan")
},
[setTab],
)
if (isInMASReview) {
return null
}
return (
<Tooltip>
<TooltipTrigger asChild>
<i className="i-mgc-power block text-accent" onClick={handleClick} />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{paidLevel === SettingPaidLevels.FreeLimited && t("control.paid_badge.free_limited")}
{paidLevel === SettingPaidLevels.Plus && t("control.paid_badge.plus_or_higher")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
)
}
export const SettingCheckbox: Component<{
label: string
checked: boolean
@ -32,15 +78,20 @@ export const SettingSwitch: Component<{
label: string
checked: boolean
onCheckedChange: (checked: boolean) => void
}> = ({ checked, label, onCheckedChange, className }) => {
disabled?: boolean
paidLevel?: SettingPaidLevels
}> = ({ checked, label, onCheckedChange, className, disabled, paidLevel }) => {
const id = useId()
const handleCheckedChange = (checked: boolean) => {
onCheckedChange(checked)
}
return (
<div className={cn("mb-3 flex items-center justify-between gap-4", className)}>
<Label htmlFor={id}>{titleCase(label)}</Label>
<Switch id={id} checked={checked} onCheckedChange={handleCheckedChange} />
<Label htmlFor={id} className="flex items-center gap-1">
<span>{titleCase(label)}</span>
{!!paidLevel && <PaidBadge paidLevel={paidLevel} />}
</Label>
<Switch id={id} checked={checked} onCheckedChange={handleCheckedChange} disabled={disabled} />
</div>
)
}

View File

@ -1,10 +1,16 @@
import type { SettingNamespace } from "@follow/shared/settings/constants"
import { getSettingPaidLevel } from "@follow/shared/settings/constants"
import type { JSX } from "react/jsx-runtime"
import type { SettingItem } from "./setting-builder"
import { createSettingBuilder } from "./setting-builder"
export const createDefineSettingItem =
<T>(_getSetting: () => T, setSetting: (key: any, value: Partial<T>) => void) =>
<T>(
settingNamespace: SettingNamespace,
_getSetting: () => T,
setSetting: (key: any, value: Partial<T>) => void,
) =>
<K extends keyof T>(
key: K,
options: {
@ -13,9 +19,10 @@ export const createDefineSettingItem =
onChange?: (value: T[K]) => void
onAfterChange?: (value: T[K]) => void
hide?: boolean
} & Omit<SettingItem<any>, "onChange" | "description" | "label" | "hide" | "key">,
} & Omit<SettingItem<any>, "onChange" | "description" | "label" | "hide" | "key" | "paidLevel">,
): any => {
const { label, description, onChange, hide, onAfterChange, ...rest } = options
const paidLevel = getSettingPaidLevel(settingNamespace, String(key))
if (hide) return null
return {
@ -31,16 +38,18 @@ export const createDefineSettingItem =
}
},
disabled: hide,
paidLevel,
...rest,
} as SettingItem<any>
}
export const createSetting = <T extends object>(
settingNamespace: SettingNamespace,
useSetting: () => T,
setSetting: (key: any, value: Partial<T>) => void,
) => {
const SettingBuilder = createSettingBuilder(useSetting)
const defineSettingItem = createDefineSettingItem(useSetting, setSetting)
const defineSettingItem = createDefineSettingItem(settingNamespace, useSetting, setSetting)
return {
SettingBuilder,
defineSettingItem,

View File

@ -1,4 +1,7 @@
/* eslint-disable @eslint-react/no-array-index-key */
import { UserRole } from "@follow/constants"
import { SettingPaidLevels } from "@follow/shared/settings/constants"
import { useUserRole } from "@follow/store/user/hooks"
import type { FC, ReactNode } from "react"
import * as React from "react"
import { isValidElement } from "react"
@ -6,6 +9,8 @@ import { isValidElement } from "react"
import { SettingActionItem, SettingDescription, SettingInput, SettingSwitch } from "../control"
import { SettingItemGroup, SettingSectionTitle } from "../section"
export { SettingPaidLevels } from "@follow/shared/settings/constants"
type SharedSettingItem = {
disabled?: boolean
}
@ -25,6 +30,7 @@ export type SettingItem<T, K extends keyof T = keyof T> = {
className?: string
[key: string]: any
}
paidLevel?: SettingPaidLevels
} & SharedSettingItem
type SectionSettingItem = {
@ -54,6 +60,7 @@ export const createSettingBuilder =
}) => {
const { settings } = props
const settingObject = useSetting()
const role = useUserRole()
const filteredSettings = settings.filter((i) => !!i)
return filteredSettings.map((setting, index) => {
@ -64,7 +71,7 @@ export const createSettingBuilder =
const assertSetting = setting as SettingItem<T> | SectionSettingItem | ActionSettingItem
if (!assertSetting) return null
if (assertSetting.disabled) return null
// if (assertSetting.disabled) return null
const nextItem = filteredSettings[index + 1]
// If has no next item or next item is also a title, then it is an empty section
@ -90,6 +97,12 @@ export const createSettingBuilder =
if ("type" in assertSetting && assertSetting.type === "title") {
return null
}
const disabledForRole =
role === UserRole.Free &&
"paidLevel" in assertSetting &&
assertSetting.paidLevel !== undefined &&
assertSetting.paidLevel !== SettingPaidLevels.Free &&
assertSetting.paidLevel !== SettingPaidLevels.FreeLimited
let ControlElement: React.ReactNode
@ -110,6 +123,8 @@ export const createSettingBuilder =
assertSetting.onChange(checked as T[keyof T])
}}
label={assertSetting.label}
disabled={assertSetting.disabled || disabledForRole}
paidLevel={assertSetting.paidLevel}
/>
)
break

View File

@ -14,7 +14,7 @@ import { TaskSchedulingSection } from "./ai/tasks"
import { UsageAnalysisSection } from "./ai/usage"
const SettingBuilder = createSettingBuilder(useAISettingValue)
const defineSettingItem = createDefineSettingItem(useAISettingValue, setAISetting)
const defineSettingItem = createDefineSettingItem("ai", useAISettingValue, setAISetting)
export const AI_SETTING_SECTION_IDS = {
shortcuts: "settings-ai-shortcuts",

View File

@ -48,7 +48,7 @@ import { SettingItemGroup } from "../section"
import { ContentFontSelector, UIFontSelector } from "../sections/fonts"
const SettingBuilder = createSettingBuilder(useUISettingValue)
const _defineItem = createDefineSettingItem(useUISettingValue, setUISetting)
const _defineItem = createDefineSettingItem("ui", useUISettingValue, setUISetting)
export const SettingAppearance = () => {
const { t } = useTranslation("settings")

View File

@ -33,7 +33,7 @@ import { SettingActionItem, SettingDescription } from "../control"
import { createSetting } from "../helper/builder"
import { SettingItemGroup } from "../section"
const { SettingBuilder } = createSetting(useGeneralSettingValue, setGeneralSetting)
const { SettingBuilder } = createSetting("general", useGeneralSettingValue, setGeneralSetting)
export const SettingDataControl = () => {
const { t } = useTranslation("settings")

View File

@ -1,7 +1,9 @@
import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js"
import { UserRole } from "@follow/constants"
import { useTypeScriptHappyCallback } from "@follow/hooks"
import { ACTION_LANGUAGE_MAP } from "@follow/shared"
import { IN_ELECTRON } from "@follow/shared/constants"
import { useUserRole } from "@follow/store/user/hooks"
import { cn } from "@follow/utils/utils"
import { useQuery } from "@tanstack/react-query"
import dayjs from "dayjs"
@ -12,6 +14,7 @@ import { useTranslation } from "react-i18next"
import { currentSupportedLanguages } from "~/@types/constants"
import { defaultResources } from "~/@types/default-resource"
import { langLoadingLockMapAtom } from "~/atoms/lang"
import { useIsInMASReview } from "~/atoms/server-configs"
import {
DEFAULT_ACTION_LANGUAGE,
setGeneralSetting,
@ -26,8 +29,9 @@ import { fallbackLanguage } from "~/i18n"
import { ipcServices } from "~/lib/client"
import { setTranslationCache } from "~/modules/entry-content/atoms"
import { SettingDescription, SettingInput, SettingSwitch } from "../control"
import { PaidBadge, SettingDescription, SettingInput, SettingSwitch } from "../control"
import { createSetting } from "../helper/builder"
import { SettingPaidLevels } from "../helper/setting-builder"
import {
useWrapEnhancedSettingItem,
WrapEnhancedSettingTab,
@ -35,6 +39,7 @@ import {
import { SettingItemGroup } from "../section"
const { defineSettingItem: _defineSettingItem, SettingBuilder } = createSetting(
"general",
useGeneralSettingValue,
setGeneralSetting,
)
@ -78,7 +83,7 @@ export const SettingGeneral = () => {
defineSettingItem("appLaunchOnStartup", {
label: t("general.launch_at_login"),
disabled: !ipcServices,
hide: !ipcServices,
onChange(value) {
saveLoginSetting(value)
},
@ -290,11 +295,17 @@ export const LanguageSelector = ({
const TranslationModeSelector = () => {
const { t } = useTranslation("settings")
const translationMode = useGeneralSettingKey("translationMode")
const role = useUserRole()
const isInMASReview = useIsInMASReview()
const disabledForRole = role === UserRole.Free && !isInMASReview
return (
<>
<div className="mt-4 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">{t("general.translation_mode.label")}</span>
<span className="flex shrink-0 items-center gap-1 text-sm font-medium">
<span>{t("general.translation_mode.label")}</span>
<PaidBadge paidLevel={SettingPaidLevels.Plus} />
</span>
<ResponsiveSelect
size="sm"
triggerClassName="w-48"
@ -307,6 +318,7 @@ const TranslationModeSelector = () => {
{ label: t("general.translation_mode.bilingual"), value: "bilingual" },
{ label: t("general.translation_mode.translation-only"), value: "translation-only" },
]}
disabled={disabledForRole}
/>
</div>
<SettingDescription>{t("general.translation_mode.description")}</SettingDescription>

View File

@ -13,6 +13,7 @@ import { SettingSectionTitle } from "../../section"
import { CustomIntegrationModalContent } from "./CustomIntegrationModal"
const { defineSettingItem, SettingBuilder } = createSetting(
"integration",
useIntegrationSettingValue,
setIntegrationSetting,
)

View File

@ -31,6 +31,7 @@ import { SettingSectionTitle } from "../../section"
import { CustomIntegrationSection } from "./CustomIntegrationSection"
const { defineSettingItem, SettingBuilder } = createSetting(
"integration",
useIntegrationSettingValue,
setIntegrationSetting,
)

View File

@ -1,71 +1,52 @@
import { Button } from "@follow/components/ui/button/index.js"
import { UserRole, UserRoleName } from "@follow/constants"
import { UserRole } from "@follow/constants"
import { DEEPLINK_SCHEME, IN_ELECTRON } from "@follow/shared"
import { env } from "@follow/shared/env.desktop"
import { useRoleEndAt, useUserRole, useWhoami } from "@follow/store/user/hooks"
import { cn } from "@follow/utils/utils"
import { useMutation, useQuery } from "@tanstack/react-query"
import { useState } from "react"
import { Trans } from "react-i18next"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import type { PaymentFeature, PaymentPlan } from "~/atoms/server-configs"
import { useIsInMASReview, useServerConfigs } from "~/atoms/server-configs"
import { subscription } from "~/lib/auth"
// Plan configuration types
interface Plan {
id: string
title: string
monthlyPrice: number
yearlyPrice: number
features: string[]
isPopular?: boolean
role: UserRole
isComingSoon?: boolean
tier: number // Add tier for hierarchy comparison
const formatFeatureValue = (
key: keyof PaymentFeature,
value: number | boolean | null | undefined,
): string => {
if (value == null || value === undefined) {
return "—"
}
if (typeof value === "boolean") {
return value ? "✓" : "—"
}
if (key === "PRIORITY_SUPPORT" && typeof value === "number") {
return "⭐️".repeat(value)
}
if (value === Number.MAX_SAFE_INTEGER) {
return "Unlimited"
}
return new Intl.NumberFormat("en", {
notation: "compact",
compactDisplay: "short",
maximumFractionDigits: 1,
}).format(value)
}
// Plan hierarchy: Free (1) < Pro Preview (2) < Pro (3)
const PLAN_TIER_MAP: Record<UserRole, number> = {
[UserRole.Admin]: 4, // Admin has highest tier
[UserRole.Free]: 1,
[UserRole.Trial]: 1, // Same as Free (deprecated)
[UserRole.PreProTrial]: 2,
[UserRole.Pro]: 3,
}
// Plan configurations
const PLAN_CONFIGS: Plan[] = [
{
id: "free",
title: UserRoleName[UserRole.Free],
monthlyPrice: 0,
yearlyPrice: 0,
features: ["50 feeds", "10 lists"],
isPopular: false,
role: UserRole.Free,
tier: PLAN_TIER_MAP[UserRole.Free],
},
{
id: "folo pro",
title: UserRoleName[UserRole.Pro],
monthlyPrice: 20,
yearlyPrice: 180,
features: [
"1000 feeds and lists",
"10 inboxes",
"10 actions",
"100 webhooks",
"Advanced AI features",
],
isPopular: true,
role: UserRole.Pro,
tier: PLAN_TIER_MAP[UserRole.Pro],
},
]
const useUpgradePlan = ({ plan, annual }: { plan: string; annual: boolean }) => {
const useUpgradePlan = ({ plan, annual }: { plan: string | undefined; annual: boolean }) => {
return useMutation({
mutationFn: async () => {
if (!plan) {
return
}
const res = await subscription.upgrade({
plan,
annual,
@ -86,22 +67,20 @@ const useActiveSubscription = () => {
queryKey: ["activeSubscription"],
queryFn: async () => {
const { data } = await subscription.list()
return data
return data?.find((sub) => sub.status === "active" || sub.status === "trialing")
},
enabled: !!userId,
})
}
const useCancelPlan = () => {
const activeSubscription = useActiveSubscription()
const latestSubscription = activeSubscription.data?.at(-1)
const { data: latestSubscription } = useActiveSubscription()
const subscriptionId = latestSubscription?.id
const cancelAtPeriodEnd = latestSubscription?.cancelAtPeriodEnd
const cancelMutation = useMutation({
mutationFn: async () => {
if (!subscriptionId) {
toast.error("No active subscription found.")
return
}
@ -117,7 +96,7 @@ const useCancelPlan = () => {
},
})
if (cancelAtPeriodEnd) {
if (cancelAtPeriodEnd || !subscriptionId) {
return null
} else {
return cancelMutation
@ -125,6 +104,7 @@ const useCancelPlan = () => {
}
export function SettingPlan() {
const isInMASReview = useIsInMASReview()
const role = useUserRole()
const roleEndDate = useRoleEndAt()
const [billingPeriod, setBillingPeriod] = useState<"monthly" | "yearly">("yearly")
@ -133,13 +113,28 @@ export function SettingPlan() {
? Math.ceil((roleEndDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24))
: null
const serverConfig = useServerConfigs()
const plans = serverConfig?.PAYMENT_PLAN_LIST || []
const currentPlan = plans.find((plan) => plan.role === role)
const currentTier = currentPlan?.tier || 0
// Calculate average savings percentage across all paid plans
const averageSavings = Math.round(
plans
.filter((plan) => plan.priceInDollars > 0 && plan.priceInDollarsAnnual > 0)
.reduce((acc, plan) => {
const monthlyTotal = plan.priceInDollars * 12
const yearlyTotal = plan.priceInDollarsAnnual
const savings = ((monthlyTotal - yearlyTotal) / monthlyTotal) * 100
return acc + savings
}, 0) / plans.filter((plan) => plan.priceInDollars > 0).length,
)
if (isInMASReview) {
return null
}
return (
<section className="mt-4 space-y-8">
{/* Description Section */}
<p className="mb-4 space-y-2 text-sm">
<Trans ns="settings" i18nKey="plan.description" />
</p>
{/* Billing Period Toggle */}
<div className="flex justify-center">
<div className="inline-flex rounded-lg bg-fill-secondary p-1">
@ -166,7 +161,9 @@ export function SettingPlan() {
)}
>
<span>Yearly</span>
<span className="ml-2 text-xs font-medium text-green">Save 25%</span>
{averageSavings > 0 && (
<span className="ml-2 text-xs font-medium text-green">Save {averageSavings}%</span>
)}
</button>
</div>
</div>
@ -174,111 +171,119 @@ export function SettingPlan() {
{/* Plans Grid */}
<div className="@container">
<div className="grid grid-cols-1 gap-4 @md:grid-cols-2 @xl:grid-cols-3">
{PLAN_CONFIGS.map((plan) => (
{plans.map((plan) => (
<PlanCard
key={plan.id}
key={plan.name}
plan={plan}
billingPeriod={billingPeriod}
currentUserRole={role || null}
daysLeft={daysLeft}
isCurrentPlan={role === plan.role}
currentTier={currentTier}
/>
))}
</div>
</div>
{/* Comparison Table */}
<PlanComparisonTable plans={plans} />
</section>
)
}
// Reusable PlanCard Component
interface PlanCardProps {
plan: Plan
plan: PaymentPlan
billingPeriod: "monthly" | "yearly"
currentUserRole: UserRole | null
isCurrentPlan: boolean
currentTier: number
daysLeft: number | null
}
const PlanCard = ({
plan,
billingPeriod,
currentUserRole,
isCurrentPlan,
daysLeft,
}: PlanCardProps) => {
const getPlanActionType = (): "current" | "upgrade" | "coming-soon" | "in-trial" | null => {
const PlanCard = ({ plan, billingPeriod, isCurrentPlan, currentTier, daysLeft }: PlanCardProps) => {
const { t } = useTranslation("settings")
const getPlanActionType = ():
| "current"
| "upgrade"
| "coming-soon"
| "in-trial"
| "switch"
| null => {
if (plan.isComingSoon) return "coming-soon"
if (!currentUserRole) {
return plan.tier > PLAN_TIER_MAP[UserRole.Free] ? "upgrade" : "current"
}
const currentTier = PLAN_TIER_MAP[currentUserRole]
const targetTier = plan.tier
if (currentTier === targetTier) {
// if (currentUserRole === UserRole.PreProTrial) {
// return "in-trial"
// } else {
// return "current"
switch (true) {
case isCurrentPlan: {
return "current"
}
case plan.tier > currentTier && !!plan.planID: {
return "upgrade"
}
// case plan.tier < currentTier && !!plan.planID: {
// return "switch"
// }
return "current"
default: {
return null
}
}
if (targetTier > currentTier) return "upgrade"
return null
}
const actionType = getPlanActionType()
const upgradePlanMutation = useUpgradePlan({
plan: "folo pro",
plan: plan.planID,
annual: billingPeriod === "yearly",
})
const cancelPlanMutation = useCancelPlan()
// Calculate price and period based on billing period
const price = billingPeriod === "yearly" ? plan.yearlyPrice : plan.monthlyPrice
const formattedPrice = price === 0 ? "$0" : `$${price}`
const period = plan.role === UserRole.Free ? "" : billingPeriod === "yearly" ? "year" : "month"
const regularPrice =
billingPeriod === "yearly" ? plan.priceInDollarsAnnual / 12 : plan.priceInDollars
const discountPrice =
billingPeriod === "yearly"
? (plan.priceInDollarsInDiscountAnnual || 0) / 12
: plan.priceInDollarsInDiscount
const period = plan.role === UserRole.Free ? "" : "month"
// Calculate savings for yearly plan
const yearlyTotalPrice = plan.yearlyPrice
const monthlyTotalPrice = plan.monthlyPrice * 12
const savingsPercentage =
plan.monthlyPrice > 0 && plan.yearlyPrice > 0
? Math.round(((monthlyTotalPrice - yearlyTotalPrice) / monthlyTotalPrice) * 100)
: 0
// Calculate discount percentage from prices
const hasDiscount =
discountPrice &&
discountPrice > 0 &&
discountPrice < regularPrice &&
discountPrice !== regularPrice
const discountPercentage = hasDiscount
? Math.round(((regularPrice - discountPrice) / regularPrice) * 100)
: 0
// Use discount price if available, otherwise use regular price
const finalPrice = hasDiscount ? discountPrice : regularPrice
const formattedPrice = finalPrice === 0 ? "$0" : `$${finalPrice.toFixed(2)}`
const formattedRegularPrice =
hasDiscount && regularPrice > 0 ? `$${regularPrice.toFixed(2)}` : undefined
// Get plan description from i18n
const planDescriptionKey = `plan.descriptions.${plan.role}` as const
const planDescription = t(planDescriptionKey, { defaultValue: "" })
return (
<div
className={cn(
"group relative flex h-full flex-col overflow-hidden rounded-xl border transition-all duration-200",
plan.isPopular
actionType === "upgrade"
? "border-accent"
: "border-fill-tertiary bg-background hover:border-fill-secondary",
isCurrentPlan &&
"bg-gradient-to-b from-accent/5 to-transparent shadow-lg shadow-accent/10 ring-2 ring-accent ring-offset-2 ring-offset-background",
plan.isComingSoon && "opacity-75",
)}
>
<PlanBadges isPopular={plan.isPopular || false} />
<div className="flex h-full flex-col p-4 @md:p-5">
<div className="flex-1 space-y-3 @md:space-y-4">
<PlanHeader
title={plan.title}
price={formattedPrice}
period={period}
billingPeriod={billingPeriod}
monthlyPrice={plan.monthlyPrice}
yearlyPrice={plan.yearlyPrice}
savingsPercentage={savingsPercentage}
/>
<PlanFeatures features={plan.features} />
<div />
</div>
<div className="flex h-full flex-col justify-between gap-4 p-4 @md:p-5">
<PlanHeader
title={plan.name}
price={formattedPrice}
regularPrice={formattedRegularPrice}
period={period}
description={planDescription}
discountPercentage={discountPercentage}
/>
<PlanAction
isPopular={plan.isPopular || false}
actionType={actionType}
daysLeft={daysLeft}
isLoading={upgradePlanMutation.isPending || cancelPlanMutation?.isPending}
@ -290,7 +295,7 @@ const PlanCard = ({
: undefined
}
onCancel={
isCurrentPlan && plan.role !== UserRole.Free && cancelPlanMutation
isCurrentPlan && cancelPlanMutation
? () => {
cancelPlanMutation.mutate()
}
@ -321,70 +326,48 @@ const PlanBadges = ({ isPopular }: { isPopular: boolean }) => (
const PlanHeader = ({
title,
price,
regularPrice,
period,
billingPeriod,
monthlyPrice,
yearlyPrice,
savingsPercentage,
description,
discountPercentage,
}: {
title: string
price: string
regularPrice?: string
period: string
billingPeriod: "monthly" | "yearly"
monthlyPrice: number
yearlyPrice: number
savingsPercentage: number
description?: string
discountPercentage?: number
}) => (
<div className="space-y-1">
<div className="space-y-2">
<h3 className="text-base font-semibold @md:text-lg">{title}</h3>
<div className="flex items-baseline gap-1">
<span className="text-xl font-bold">{price}</span>
{period && <span className="text-xs text-text-secondary @md:text-sm">/{period}</span>}
</div>
<div className="h-7 space-y-0.5 @md:h-8">
{billingPeriod === "yearly" &&
monthlyPrice > 0 &&
yearlyPrice > 0 &&
savingsPercentage > 0 && (
<div className="flex items-center gap-2 text-xs">
<span className="text-text-tertiary line-through">
${(monthlyPrice * 12).toFixed(0)}/year
</span>
<span className="font-medium text-green">Save {savingsPercentage}%</span>
</div>
<div className="space-y-1">
<div className="flex items-baseline gap-2">
<span className="text-xl font-bold">{price}</span>
{period && <span className="text-xs text-text-secondary @md:text-sm">/{period}</span>}
{regularPrice && (
<span className="text-sm text-text-tertiary line-through">{regularPrice}</span>
)}
{billingPeriod === "yearly" && monthlyPrice > 0 && yearlyPrice > 0 && (
<div className="text-xs text-text-secondary">
${(yearlyPrice / 12).toFixed(1)}/month billed annually
</div>
{!!discountPercentage && discountPercentage > 0 && (
<div className="inline-flex items-center gap-1.5 rounded-md bg-green/10 px-2 py-1">
<span className="text-xs font-semibold text-green">-{discountPercentage}% OFF</span>
</div>
)}
</div>
</div>
)
const PlanFeatures = ({ features }: { features: string[] }) => (
<div className="space-y-1.5 @md:space-y-2">
{features.map((feature) => (
<div key={feature} className="flex items-start gap-2.5 @md:gap-3">
<div className="mt-0.5 flex size-3.5 items-center justify-center rounded-full bg-green/10 @md:size-4">
<i className="i-mgc-check-cute-re text-[10px] text-green @md:text-xs" />
</div>
<span className="text-xs leading-relaxed @md:text-sm">{feature}</span>
</div>
))}
{description && (
<p className="text-xs leading-relaxed text-text-secondary @md:text-sm">{description}</p>
)}
</div>
)
const PlanAction = ({
isPopular,
actionType,
onSelect,
onCancel,
isLoading,
daysLeft,
}: {
isPopular: boolean
actionType: "current" | "upgrade" | "coming-soon" | "in-trial" | null
actionType: "current" | "upgrade" | "coming-soon" | "in-trial" | "switch" | null
onSelect?: () => void
onCancel?: () => void
isLoading?: boolean
@ -396,33 +379,37 @@ const PlanAction = ({
return {
text: "Coming Soon",
variant: "outline" as const,
className: "w-full h-9 @md:h-10 text-xs @md:text-sm",
disabled: true,
}
}
case "current": {
return {
text: typeof daysLeft === "number" ? `${daysLeft} days left` : "Current Plan",
text: `${typeof daysLeft === "number" ? `${daysLeft} days left` : "Current Plan"}${onCancel ? " | Cancel" : ""}`,
variant: "outline" as const,
className: "w-full h-9 @md:h-10 text-xs @md:text-sm text-text-secondary",
disabled: true,
className: onCancel ? "" : "text-text-secondary",
disabled: onCancel ? false : true,
}
}
case "in-trial": {
return {
text: `In Trial (${daysLeft} days left)`,
variant: "outline" as const,
className: "w-full h-9 @md:h-10 text-xs @md:text-sm",
disabled: false,
}
}
case "upgrade": {
return {
text: "Upgrade",
variant: isPopular ? undefined : ("outline" as const),
className: isPopular
? "w-full h-9 @md:h-10 text-xs @md:text-sm bg-gradient-to-r from-accent to-accent/80 hover:from-accent/90 hover:to-accent/70"
: "w-full h-9 @md:h-10 text-xs @md:text-sm",
className:
"bg-gradient-to-r from-accent to-accent/80 hover:from-accent/90 hover:to-accent/70",
disabled: false,
}
}
case "switch": {
return {
text: "Switch",
className:
"bg-gradient-to-r from-accent to-accent/80 hover:from-accent/90 hover:to-accent/70",
disabled: false,
}
}
@ -439,27 +426,92 @@ const PlanAction = ({
}
return (
<div className="space-y-2">
<Button
variant={buttonConfig.variant}
buttonClassName={buttonConfig.className}
disabled={buttonConfig.disabled}
onClick={buttonConfig.disabled ? undefined : onSelect}
isLoading={isLoading}
>
{buttonConfig.text}
</Button>
<Button
variant={buttonConfig.variant}
buttonClassName={cn("w-full h-9 @md:h-10 text-xs @md:text-sm", buttonConfig.className)}
disabled={buttonConfig.disabled}
onClick={buttonConfig.disabled ? undefined : (onCancel ?? onSelect)}
isLoading={isLoading}
>
{buttonConfig.text}
</Button>
)
}
{actionType === "current" && typeof daysLeft === "number" && onCancel && (
<Button
variant="ghost"
buttonClassName="w-full h-8 text-xs text-text-tertiary hover:text-red"
onClick={onCancel}
disabled={isLoading}
>
Cancel
</Button>
)}
const PlanComparisonTable = ({ plans }: { plans: PaymentPlan[] }) => {
const { t } = useTranslation("settings")
// Get all unique feature keys
const allFeatureKeys = Array.from(
new Set(plans.flatMap((plan) => Object.keys(plan.limit))),
) as (keyof PaymentFeature)[]
// Filter out features that are all false/0/null
const visibleFeatureKeys = allFeatureKeys.filter((key) =>
plans.some((plan) => {
const value = plan.limit[key]
if (value == null) return false
if (typeof value === "boolean" && !value) return false
if (typeof value === "number" && value === 0) return false
return true
}),
)
return (
<div className="overflow-hidden rounded-xl border border-fill-tertiary bg-background">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-fill-tertiary bg-fill-secondary/50">
<th className="sticky left-0 z-10 bg-fill-secondary/50 px-4 py-3 text-left text-sm font-semibold">
Features
</th>
{plans.map((plan) => (
<th key={plan.name} className="px-4 py-3 text-center text-sm font-semibold">
{plan.name}
</th>
))}
</tr>
</thead>
<tbody>
{visibleFeatureKeys.map((featureKey, index) => (
<tr
key={featureKey}
className={cn(
"border-b border-fill-tertiary transition-colors hover:bg-fill-secondary/30",
index % 2 === 0 ? "bg-background" : "bg-fill-secondary/20",
)}
>
<td className="sticky left-0 z-10 bg-inherit px-4 py-3 text-sm font-medium">
{t(`plan.features.${featureKey}` as any)}
</td>
{plans.map((plan) => {
const value = plan.limit[featureKey]
const formattedValue = formatFeatureValue(featureKey, value)
return (
<td
key={`${plan.name}-${featureKey}`}
className="px-4 py-3 text-center text-sm"
>
<span
className={cn(
"font-medium",
formattedValue === "—" && "text-text-tertiary",
formattedValue === "✓" && "text-green",
formattedValue === "Unlimited" && "text-accent",
)}
>
{formattedValue}
</span>
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}

View File

@ -2,7 +2,6 @@ import { ActionButton } from "@follow/components/ui/button/index.js"
import { RSSHubLogo } from "@follow/components/ui/platform-icon/icons.js"
import { RootPortal } from "@follow/components/ui/portal/index.js"
import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/EllipsisWithTooltip.js"
import { UserRole } from "@follow/constants"
import { useMeasure } from "@follow/hooks"
import { useUserRole } from "@follow/store/user/hooks"
import { cn } from "@follow/utils/utils"
@ -28,7 +27,6 @@ import { usePresentUserProfileModal } from "~/modules/profile/hooks"
import { useSettingModal } from "~/modules/settings/modal/use-setting-modal-hack"
import { signOut, useSession } from "~/queries/auth"
import { useActivationModal } from "../activation"
import type { LoginProps } from "./LoginButton"
import { LoginButton } from "./LoginButton"
import { UserAvatar } from "./UserAvatar"
@ -53,7 +51,6 @@ export const ProfileButton: FC<ProfileButtonProps> = memo((props) => {
const navigate = useNavigate()
const role = useUserRole()
const presentActivationModal = useActivationModal()
const isInMASReview = useIsInMASReview()
if (status === "unauthenticated") {
@ -83,7 +80,7 @@ export const ProfileButton: FC<ProfileButtonProps> = memo((props) => {
<EllipsisHorizontalTextWithTooltip className="mx-auto max-w-[20ch] truncate text-lg">
{user?.name}
</EllipsisHorizontalTextWithTooltip>
{serverConfig?.REFERRAL_ENABLED ? (
{!isInMASReview && serverConfig?.PAYMENT_ENABLED ? (
<UserProBadge
role={role}
withText
@ -121,26 +118,22 @@ export const ProfileButton: FC<ProfileButtonProps> = memo((props) => {
<DropdownMenuItem
className="pl-3"
onClick={() => {
if (role !== UserRole.Trial && role !== UserRole.Free) {
presentAchievement()
} else {
presentActivationModal()
}
presentAchievement()
}}
icon={<i className="i-mgc-trophy-cute-re" />}
>
{t("user_button.achievement")}
</DropdownMenuItem>
{!isInMASReview && (
{!isInMASReview && serverConfig?.PAYMENT_ENABLED && (
<DropdownMenuItem
className="pl-3"
onClick={() => {
navigate("/power")
settingModalPresent("plan")
}}
icon={<i className="i-mgc-power-outline" />}
>
{t("user_button.power")}
{t("activation.plan.title")}
</DropdownMenuItem>
)}

View File

@ -82,7 +82,7 @@ export const UserAvatar = ({
{renderUserData?.name?.[0]}
</AvatarFallback>
</Avatar>
{serverConfig?.REFERRAL_ENABLED &&
{serverConfig?.PAYMENT_ENABLED &&
!userId &&
role !== UserRole.Free &&
role !== UserRole.Trial && (

View File

@ -27,7 +27,7 @@ export const UserProBadge = ({
)}
onClick={onClick}
>
<i className={cn("i-mgc-power block text-folo", iconClassName)} />
<i className={cn("i-mgc-power block", iconClassName)} />
{withText && <span className="text-xs">{UserRoleName[role]}</span>}
</div>
)

View File

@ -2,16 +2,12 @@ import { Divider } from "@follow/components/ui/divider/Divider.js"
import { useScrollElementUpdate } from "@follow/components/ui/scroll-area/hooks.js"
import { ScrollArea } from "@follow/components/ui/scroll-area/index.js"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@follow/components/ui/tabs/index.jsx"
import { UserRole } from "@follow/constants"
import { useUserRole } from "@follow/store/user/hooks"
import { cn } from "@follow/utils/utils"
import { createElement } from "react"
import { useTranslation } from "react-i18next"
import { useSearchParams } from "react-router"
import { AppErrorBoundary } from "~/components/common/AppErrorBoundary"
import { ErrorComponentType } from "~/components/errors/enum"
import { useActivationModal } from "~/modules/activation"
import { useSubViewTitle } from "~/modules/app-layout/subview/hooks"
import { DiscoverForm } from "~/modules/discover/DiscoverForm"
import { DiscoverImport } from "~/modules/discover/DiscoverImport"
@ -24,7 +20,6 @@ import { Trending } from "~/modules/trending"
const tabs: {
name: I18nKeys
value: string
disableForTrial?: boolean
}[] = [
{
name: "words.search",
@ -41,7 +36,6 @@ const tabs: {
{
name: "words.inbox",
value: "inbox",
disableForTrial: true,
},
{
name: "words.user",
@ -62,18 +56,8 @@ export function Component() {
const { t } = useTranslation()
useSubViewTitle("words.discover")
const presentActivationModal = useActivationModal()
const role = useUserRole()
const { onUpdateMaxScroll } = useScrollElementUpdate()
const currentTabs = tabs.map((tab) => {
const disabled = tab.disableForTrial && (role === UserRole.Free || role === UserRole.Trial)
return {
...tab,
disabled,
}
})
return (
<div className="flex size-full flex-col px-6 py-8">
{/* Simple Header */}
@ -100,17 +84,12 @@ export function Component() {
<div className="mb-8">
<ScrollArea.ScrollArea flex orientation="horizontal" rootClassName="w-full">
<TabsList className="relative flex w-full">
{currentTabs.map((tab) => (
{tabs.map((tab) => (
<TabsTrigger
key={tab.name}
value={tab.value}
className={cn(tab.disabled && "cursor-not-allowed opacity-50")}
onClick={() => {
if (tab.disabled) {
presentActivationModal()
} else {
onUpdateMaxScroll?.()
}
onUpdateMaxScroll?.()
}}
>
{t(tab.name)}
@ -122,7 +101,7 @@ export function Component() {
{/* Tab Content */}
<div className="space-y-8">
{currentTabs.map((tab) => (
{tabs.map((tab) => (
<TabsContent key={tab.name} value={tab.value} className="mt-0">
<div className={tab.value === "inbox" ? "" : "flex flex-col items-center"}>
{createElement(TabComponent[tab.value]! || TabComponent.default, {

View File

@ -1,8 +1,6 @@
import { UserRole } from "@follow/constants"
import { SettingLists } from "~/modules/settings/tabs/lists"
import { SettingsTitle } from "~/modules/settings/title"
import { defineSettingPageData, DisableWhy } from "~/modules/settings/utils"
import { defineSettingPageData } from "~/modules/settings/utils"
const iconName = "i-mgc-rada-cute-re"
const priority = (1000 << 2) + 10
@ -11,10 +9,6 @@ export const loader = defineSettingPageData({
icon: iconName,
name: "titles.lists",
priority,
disableIf: (ctx) => [
ctx.role === UserRole.Free || ctx.role === UserRole.Trial,
DisableWhy.NotActivation,
],
hideIf: (ctx) => ctx.isInMASReview,
})

View File

@ -1,8 +1,6 @@
import { UserRole } from "@follow/constants"
import { SettingNotifications } from "~/modules/settings/tabs/notifications"
import { SettingsTitle } from "~/modules/settings/title"
import { defineSettingPageData, DisableWhy } from "~/modules/settings/utils"
import { defineSettingPageData } from "~/modules/settings/utils"
const iconName = "i-mgc-notification-cute-re"
const priority = (1000 << 1) + 50
@ -11,10 +9,6 @@ export const loader = defineSettingPageData({
icon: iconName,
name: "titles.notifications",
priority,
disableIf: (ctx) => [
ctx.role === UserRole.Free || ctx.role === UserRole.Trial,
DisableWhy.NotActivation,
],
})
export function Component() {

View File

@ -3,14 +3,14 @@ import { SettingsTitle } from "~/modules/settings/title"
import { defineSettingPageData } from "~/modules/settings/utils"
const iconName = "i-mgc-power-outline"
const priority = (1000 << 2) + 30
const priority = (1000 << 1) + 17
export const loader = defineSettingPageData({
icon: iconName,
name: "titles.plan.short",
title: "titles.plan.long",
priority,
hideIf: (ctx, serverConfigs) => ctx.isInMASReview || !serverConfigs?.REFERRAL_ENABLED,
hideIf: (ctx, serverConfigs) => ctx.isInMASReview || !serverConfigs?.PAYMENT_ENABLED,
})
export function Component() {

View File

@ -1,67 +1,16 @@
import { UserRole, UserRoleName } from "@follow/constants"
import { getStorageNS } from "@follow/utils/ns"
import { useEffect, useMemo } from "react"
import { toast } from "sonner"
import { useEffect } from "react"
import { setIntegrationIdentify } from "~/initialize/helper"
import { useSettingModal } from "~/modules/settings/modal/useSettingModal"
import { useSession } from "~/queries/auth"
export const UserProvider = () => {
const { session } = useSession()
const settingModalPresent = useSettingModal()
useEffect(() => {
if (!session?.user) return
setIntegrationIdentify(session.user)
}, [session?.user])
const roleEndDate = useMemo(
() =>
session?.roleEndAt
? typeof session.roleEndAt === "string"
? new Date(session.roleEndAt)
: session.roleEndAt
: undefined,
[session?.roleEndAt],
)
useEffect(() => {
if (!session?.role) return
const itemKey = getStorageNS("pro-preview-toast-dismissed")
const isToastDismissed = localStorage.getItem(itemKey)
const sessionRole = session.role as string
if (sessionRole && sessionRole !== UserRole.Pro && !isToastDismissed) {
const message =
sessionRole === UserRole.Free || sessionRole === UserRole.Trial
? `You are currently on the ${UserRoleName[UserRole.Free]} plan. Some features may be limited.`
: sessionRole === UserRole.PreProTrial
? `You are currently on the ${UserRoleName[UserRole.PreProTrial]} plan.${roleEndDate ? ` It will end on ${roleEndDate.toLocaleDateString()}.` : ""}`
: ""
if (!message) {
localStorage.setItem(itemKey, "true")
return
}
toast.warning(message, {
duration: Number.POSITIVE_INFINITY,
action: {
label: "More",
onClick: () => {
settingModalPresent("plan")
localStorage.setItem(itemKey, "true")
},
},
onDismiss: () => {
localStorage.setItem(itemKey, "true")
},
})
}
}, [roleEndDate, session?.role, settingModalPresent])
return null
}

View File

@ -21,6 +21,7 @@
"@better-auth/expo": "1.3.28",
"@expo/metro-runtime": "5.0.4",
"@expo/react-native-action-sheet": "4.1.1",
"@follow-app/client-sdk": "catalog:",
"@follow/components": "workspace:*",
"@follow/constants": "workspace:*",
"@follow/database": "workspace:*",

View File

@ -1,3 +1,4 @@
import { FollowAPIError } from "@follow-app/client-sdk"
import { t } from "i18next"
import { FetchError } from "ofetch"
@ -25,6 +26,20 @@ export const getFetchErrorInfo = (
}
}
if (error instanceof FollowAPIError && error.code) {
const code = Number(error.code)
try {
const i18nKey = `errors:${code}` as any
const i18nMessage = t(i18nKey) === i18nKey ? error.message : t(i18nKey)
return {
message: i18nMessage,
code,
}
} catch {
return { message: error.message }
}
}
return { message: error.message }
}
@ -67,6 +82,17 @@ export const toastFetchError = (error: Error, { title: _title }: { title?: strin
}
}
if (error instanceof FollowAPIError && error.code) {
code = Number(error.code)
try {
const tValue = t(`errors:${code}` as any)
const i18nMessage = tValue === code?.toString() ? error.message : tValue
message = i18nMessage
} catch {
message = error.message
}
}
// 2fa errors are handled by the form
if (code === 4007 || code === 4008) {
return

View File

@ -31,6 +31,7 @@ import { Switch } from "@/src/components/ui/switch/Switch"
import { Text } from "@/src/components/ui/typography/Text"
import { Book6CuteReIcon } from "@/src/icons/book_6_cute_re"
import { Magic2CuteFiIcon } from "@/src/icons/magic_2_cute_fi"
import { toastFetchError } from "@/src/lib/error-parser"
import { useNavigation } from "@/src/lib/navigation/hooks"
import { toast } from "@/src/lib/toast"
import { accentColor } from "@/src/theme/colors"
@ -129,8 +130,8 @@ const SaveRuleButton = ({ disabled }: { disabled?: boolean }) => {
navigation.back()
toast.success("Actions saved")
},
onError(errorMessage) {
toast.error(errorMessage)
onError(error) {
toastFetchError(error)
},
})
return (

View File

@ -7,22 +7,17 @@ import type { ProductPurchase } from "expo-iap"
import { useIAP } from "expo-iap"
import { openURL } from "expo-linking"
import { useEffect } from "react"
import { Trans, useTranslation } from "react-i18next"
import { Linking, Pressable, View } from "react-native"
import { useTranslation } from "react-i18next"
import { Pressable, View } from "react-native"
import { useServerConfigs } from "@/src/atoms/server-configs"
import {
NavigationBlurEffectHeaderView,
SafeNavigationScrollView,
} from "@/src/components/layouts/views/SafeNavigationScrollView"
import {
GroupedInformationCell,
GroupedInsetListCard,
} from "@/src/components/ui/grouped/GroupedList"
// Plan configuration types
import { Text } from "@/src/components/ui/typography/Text"
import { CheckLineIcon } from "@/src/icons/check_line"
import { PowerOutlineIcon } from "@/src/icons/power_outline"
import { TimeCuteReIcon } from "@/src/icons/time_cute_re"
import { followClient } from "@/src/lib/api-client"
import { authClient } from "@/src/lib/auth"
@ -30,7 +25,6 @@ import { useNavigation } from "@/src/lib/navigation/hooks"
import type { NavigationControllerView } from "@/src/lib/navigation/types"
import { isIOS } from "@/src/lib/platform"
import { proxyEnv } from "@/src/lib/proxy-env"
import { accentColor } from "@/src/theme/colors"
import { ReferralScreen } from "./Referral"
@ -55,6 +49,7 @@ const PLAN_TIER_MAP: Record<UserRole, number> = {
// Same as Free (deprecated)
[UserRole.PreProTrial]: 2,
[UserRole.Pro]: 3,
[UserRole.Plus]: 4,
}
// Plan configurations
@ -129,7 +124,6 @@ export const PlanScreen: NavigationControllerView = () => {
const navigation = useNavigation()
const { t } = useTranslation("settings")
const serverConfigs = useServerConfigs()
const ruleLink = serverConfigs?.REFERRAL_RULE_LINK
const requiredInvitationsAmount = serverConfigs?.REFERRAL_REQUIRED_INVITATIONS || 3
const { data: referralInfo } = useReferralInfoQuery()
const validInvitationsAmount = referralInfo?.invitations.filter((i) => i.usedAt).length || 0
@ -165,40 +159,6 @@ export const PlanScreen: NavigationControllerView = () => {
className="bg-system-grouped-background"
Header={<NavigationBlurEffectHeaderView title={t("titles.plan.long")} />}
>
<View className="mt-6">
<GroupedInsetListCard>
<GroupedInformationCell
title={t("titles.plan.short")}
icon={<PowerOutlineIcon height={40} width={40} color="#fff" />}
iconBackgroundColor={accentColor}
>
<Trans
ns="settings"
i18nKey="plan.description"
parent={({ children }: { children: React.ReactNode }) => (
<Text className="mt-3 text-left text-base leading-tight text-label">
{children}
</Text>
)}
components={{
Link: (
<Text
className="text-accent"
onPress={() => {
if (ruleLink) {
Linking.openURL(ruleLink)
}
}}
>
Learn more
</Text>
),
}}
/>
</GroupedInformationCell>
</GroupedInsetListCard>
</View>
<View className="gap-4 p-4">
{PLAN_CONFIGS.map((plan) => {
const isProPreview = plan.id === "pro-preview"

View File

@ -25,6 +25,7 @@
"activation.plan.upgrade": "Upgrade",
"activation.title": "Invitation Code",
"ai.summary_not_available": "Summary not available",
"ai.summary_upgrade_required_title": "Upgrade plan to continue using AI summary",
"app.copy_logo_svg": "Copy Logo SVG",
"app.copy_logo_text_svg": "Copy Logo Text SVG",
"app.toggle_sidebar": "Toggle Sidebar",

View File

@ -212,6 +212,8 @@
"appearance.use_pointer_cursor.label": "Use Hand Cursor",
"appearance.words.customize": "Customize",
"common.give_star": "<HeartIcon />Love our product? <Link>Give us a star on GitHub!</Link>",
"control.paid_badge.free_limited": "This feature is limited for free plan",
"control.paid_badge.plus_or_higher": "This feature requires a Plus plan or higher to use",
"customizeToolbar.more_actions.description": "Will be shown in the dropdown menu.",
"customizeToolbar.more_actions.title": "More Actions",
"customizeToolbar.quick_actions.description": "Customize and reorder your frequently used actions.",
@ -553,7 +555,23 @@
"notifications.test": "Test Notification",
"notifications.test_success": "Test notification sent successfully.",
"notifications.token": "Client Token",
"plan.description": "Enjoy every Pro feature from day one—new users get a fullaccess trial, and you can keep those perks simply by inviting friends or making a small support contribution. <Link>Learn more</Link>.",
"plan.descriptions.free": "Great for beginners.",
"plan.descriptions.plus": "Unlock AI features and more feeds.",
"plan.descriptions.pro": "Full access to the best of Folo.",
"plan.features.AI_CREDIT": "AI Credits",
"plan.features.AI_ENTRY_TRANSLATION": "AI Entry Translation",
"plan.features.BOOSTS": "Feed Boosts",
"plan.features.INTEGRATION_SUPPORTED": "Third-Party Integrations",
"plan.features.MAX_ACTIONS": "Actions",
"plan.features.MAX_AI_ENTRY_SUMMARY_PER_DAY": "AI Summaries Per Day",
"plan.features.MAX_AI_REQUESTS_PER_DAY": "AI Requests Per Day",
"plan.features.MAX_AI_TASKS": "AI Tasks",
"plan.features.MAX_INBOXES": "Inbox",
"plan.features.MAX_LISTS": "List",
"plan.features.MAX_RSSHUB_SUBSCRIPTIONS": "RSSHub Subscriptions",
"plan.features.MAX_SUBSCRIPTIONS": "Feed Subscriptions",
"plan.features.PRIORITY_SUPPORT": "Priority Support",
"plan.features.PRIVATE_SUBSCRIPTION": "Private Subscriptions",
"privacy.privacy": "Privacy",
"privacy.terms": "Terms",
"profile.avatar.cropInstructions": "Drag the crop area to adjust your avatar",

View File

@ -208,6 +208,8 @@
"appearance.use_pointer_cursor.description": "マウスがインタラクティブな要素の上にあるとき、カーソルは手の形になります。",
"appearance.use_pointer_cursor.label": "手の形のカーソルを使用する",
"common.give_star": "<HeartIcon />私たちの製品が好きですか?<Link>GitHub で Star を付けましょう!</Link>",
"control.paid_badge.free_limited": "この機能は無料プランでは制限されています",
"control.paid_badge.plus_or_higher": "この機能を利用するには Plus プラン以上が必要です",
"customizeToolbar.more_actions.description": "ドロップダウンメニューに表示されます",
"customizeToolbar.more_actions.title": "その他のアクション",
"customizeToolbar.quick_actions.description": "よく使用するアクションをカスタマイズして並べ替える。",
@ -549,7 +551,20 @@
"notifications.test": "テスト通知",
"notifications.test_success": "テスト通知が正常に送信されました。",
"notifications.token": "クライアントトークン",
"plan.description": "すべての Pro 機能を初日から利用できます。新しいユーザーはフルアクセスのトライアルを受け取ります。友達を招待するか、少額のサポート寄付をすることで、その特典を維持できます。<Link>詳細はこちら</Link>。",
"plan.features.AI_CREDIT": "AI クレジット",
"plan.features.AI_ENTRY_TRANSLATION": "AI エントリ翻訳",
"plan.features.BOOSTS": "購読フィードの加速",
"plan.features.INTEGRATION_SUPPORTED": "サードパーティ統合",
"plan.features.MAX_ACTIONS": "自動化アクション",
"plan.features.MAX_AI_ENTRY_SUMMARY_PER_DAY": "1日あたりのAI要約数",
"plan.features.MAX_AI_REQUESTS_PER_DAY": "1日あたりのAIリクエスト数",
"plan.features.MAX_AI_TASKS": "AI タスク",
"plan.features.MAX_INBOXES": "受信トレイフィード",
"plan.features.MAX_LISTS": "カスタムリスト",
"plan.features.MAX_RSSHUB_SUBSCRIPTIONS": "RSSHub サブスクリプション",
"plan.features.MAX_SUBSCRIPTIONS": "フィードサブスクリプション",
"plan.features.PRIORITY_SUPPORT": "優先サポート",
"plan.features.PRIVATE_SUBSCRIPTION": "プライベートサブスクリプション",
"privacy.privacy": "プライバシー",
"privacy.terms": "利用規約",
"profile.avatar.cropInstructions": "ドラッグしてトリミングエリアを調整します",

View File

@ -208,6 +208,8 @@
"appearance.use_pointer_cursor.description": "当鼠标悬停在任何可交互元素上时,光标会显示为手型光标。",
"appearance.use_pointer_cursor.label": "使用手型光标",
"common.give_star": "<HeartIcon />喜欢我们的产品吗? <Link>在 GitHub 上给我们「Star」吧</Link>",
"control.paid_badge.free_limited": "此功能在免费计划中有限制",
"control.paid_badge.plus_or_higher": "此功能需要 Plus 计划或更高级别才能使用",
"customizeToolbar.more_actions.description": "将显示在下拉菜单中",
"customizeToolbar.more_actions.title": "更多操作",
"customizeToolbar.quick_actions.description": "自定义并重新排列您常用的操作",
@ -549,7 +551,20 @@
"notifications.test": "测试通知",
"notifications.test_success": "测试通知发送成功。",
"notifications.token": "客户端令牌",
"plan.description": "从第一天起享受所有专业版功能——新用户可获得完整试用,只需邀请朋友或小额支持贡献即可保留这些权益。<Link>了解更多</Link>。",
"plan.features.AI_CREDIT": "AI 积分",
"plan.features.AI_ENTRY_TRANSLATION": "AI 条目翻译",
"plan.features.BOOSTS": "订阅源加速",
"plan.features.INTEGRATION_SUPPORTED": "第三方集成",
"plan.features.MAX_ACTIONS": "自动化操作",
"plan.features.MAX_AI_ENTRY_SUMMARY_PER_DAY": "每日 AI 摘要次数",
"plan.features.MAX_AI_REQUESTS_PER_DAY": "每日 AI 请求次数",
"plan.features.MAX_AI_TASKS": "AI 任务",
"plan.features.MAX_INBOXES": "收件箱订阅源",
"plan.features.MAX_LISTS": "自定义列表",
"plan.features.MAX_RSSHUB_SUBSCRIPTIONS": "RSSHub 订阅",
"plan.features.MAX_SUBSCRIPTIONS": "订阅源数量",
"plan.features.PRIORITY_SUPPORT": "优先支持",
"plan.features.PRIVATE_SUBSCRIPTION": "私有订阅",
"privacy.privacy": "隐私政策",
"privacy.terms": "服务条款",
"profile.avatar.cropInstructions": "拖动裁剪区域以调整头像",

View File

@ -212,6 +212,8 @@
"appearance.use_pointer_cursor.label": "使用手指遊標",
"appearance.words.customize": "自訂",
"common.give_star": "<HeartIcon />喜歡我們的產品嗎? <Link>在 GitHub 上給我們 Star 吧!</Link>",
"control.paid_badge.free_limited": "此功能在免費方案中受限",
"control.paid_badge.plus_or_higher": "此功能需要 Plus 方案或更高級別才能使用",
"customizeToolbar.more_actions.description": "將顯示在下拉選單中",
"customizeToolbar.more_actions.title": "更多操作",
"customizeToolbar.quick_actions.description": "自訂並重新排列您常用的操作",
@ -553,7 +555,6 @@
"notifications.test": "測試通知",
"notifications.test_success": "測試通知發送成功。",
"notifications.token": "客户端令牌",
"plan.description": "享受每個專業功能,從第一天開始 — 新用戶獲得完整訪問試用,您可以通過邀請朋友或進行小額支持貢獻來保留這些特權。<Link>了解更多</Link>。",
"privacy.privacy": "隱私政策",
"privacy.terms": "服務條款",
"profile.avatar.cropInstructions": "拖曳裁切區域以調整你的頭像",

View File

@ -17,7 +17,10 @@
},
"dependencies": {
"@follow/configs": "workspace:*",
"@follow/constants": "workspace:*",
"@follow/hooks": "workspace:*",
"@follow/shared": "workspace:*",
"@follow/store": "workspace:*",
"@follow/types": "workspace:*",
"@follow/utils": "workspace:*",
"jotai": "2.15.0"

View File

@ -1,4 +1,7 @@
import { UserRole } from "@follow/constants"
import { useRefValue } from "@follow/hooks"
import { getSettingPaidLevel, SettingPaidLevels } from "@follow/shared/settings/constants"
import { useUserStore } from "@follow/store/user/store"
import { EventBus } from "@follow/utils/event-bus"
import { createAtomHooks } from "@follow/utils/jotai"
import { getStorageNS } from "@follow/utils/ns"
@ -25,21 +28,66 @@ export const createSettingAtom = <T extends object>(
getOnInit: true,
})
const [, , useSettingValue, , getSettings, setSettings] = createAtomHooks(atom)
const [, , useSettingValueRaw, , getSettingsRaw, setSettings] = createAtomHooks(atom)
const initializeDefaultSettings = () => {
const currentSettings = getSettings()
const currentSettings = getSettingsRaw()
const defaultSettings = createDefaultSettings()
if (typeof currentSettings !== "object") setSettings(defaultSettings)
const newSettings = { ...defaultSettings, ...currentSettings }
setSettings(newSettings)
}
const selectAtomCacheMap = {} as Record<keyof ReturnType<typeof getSettings>, any>
const selectAtomCacheMap = {} as Record<keyof ReturnType<typeof getSettingsRaw>, any>
const noopAtom = jotaiAtom(null)
const useMaybeSettingKey = <T extends keyof ReturnType<typeof getSettings>>(key: Nullable<T>) => {
const canUpdatePaidSetting = (requiredLevel?: SettingPaidLevels) => {
if (requiredLevel === undefined) return true
if (
requiredLevel === SettingPaidLevels.Free ||
requiredLevel === SettingPaidLevels.FreeLimited
) {
return true
}
const role = useUserStore.getState().role ?? UserRole.Free
return role !== UserRole.Free && role !== UserRole.Trial
}
const resolveAccessibleValue = (
key: string,
value: unknown,
defaults: Record<string, unknown>,
) => {
const requiredLevel = getSettingPaidLevel(settingKey, key)
if (requiredLevel === undefined || canUpdatePaidSetting(requiredLevel)) {
return value
}
if (Object.prototype.hasOwnProperty.call(defaults, key)) {
return defaults[key]
}
return value
}
const sanitizeSettingsSnapshot = (settings: ReturnType<typeof getSettingsRaw>) => {
const defaults = createDefaultSettings() as Record<string, unknown>
const raw = settings as Record<string, unknown>
let sanitized: Record<string, unknown> | null = null
for (const key of Object.keys(defaults)) {
const safeValue = resolveAccessibleValue(key, raw[key], defaults)
if (safeValue !== raw[key]) {
if (!sanitized) sanitized = { ...raw }
sanitized[key] = safeValue
}
}
return (sanitized ?? raw) as ReturnType<typeof getSettingsRaw>
}
const useMaybeSettingKey = <T extends keyof ReturnType<typeof getSettingsRaw>>(
key: Nullable<T>,
) => {
// @ts-expect-error
let selectedAtom: Record<keyof T, any>[T] | null = null
if (key) {
@ -52,15 +100,20 @@ export const createSettingAtom = <T extends object>(
selectedAtom = noopAtom
}
return useAtomValue(selectedAtom) as ReturnType<typeof getSettings>[T]
const value = useAtomValue(selectedAtom) as ReturnType<typeof getSettingsRaw>[T]
if (!key) return value
const defaults = createDefaultSettings() as Record<string, unknown>
return resolveAccessibleValue(String(key), value, defaults) as ReturnType<
typeof getSettingsRaw
>[T]
}
const useSettingKey = <T extends keyof ReturnType<typeof getSettings>>(key: T) => {
return useMaybeSettingKey(key) as ReturnType<typeof getSettings>[T]
const useSettingKey = <T extends keyof ReturnType<typeof getSettingsRaw>>(key: T) => {
return useMaybeSettingKey(key) as ReturnType<typeof getSettingsRaw>[T]
}
function useSettingKeys<
T extends keyof ReturnType<typeof getSettings>,
T extends keyof ReturnType<typeof getSettingsRaw>,
K1 extends T,
K2 extends T,
K3 extends T,
@ -84,22 +137,22 @@ export const createSettingAtom = <T extends object>(
useMaybeSettingKey(keys[8]),
useMaybeSettingKey(keys[9]),
] as [
ReturnType<typeof getSettings>[K1],
ReturnType<typeof getSettings>[K2],
ReturnType<typeof getSettings>[K3],
ReturnType<typeof getSettings>[K4],
ReturnType<typeof getSettings>[K5],
ReturnType<typeof getSettings>[K6],
ReturnType<typeof getSettings>[K7],
ReturnType<typeof getSettings>[K8],
ReturnType<typeof getSettings>[K9],
ReturnType<typeof getSettings>[K10],
ReturnType<typeof getSettingsRaw>[K1],
ReturnType<typeof getSettingsRaw>[K2],
ReturnType<typeof getSettingsRaw>[K3],
ReturnType<typeof getSettingsRaw>[K4],
ReturnType<typeof getSettingsRaw>[K5],
ReturnType<typeof getSettingsRaw>[K6],
ReturnType<typeof getSettingsRaw>[K7],
ReturnType<typeof getSettingsRaw>[K8],
ReturnType<typeof getSettingsRaw>[K9],
ReturnType<typeof getSettingsRaw>[K10],
]
}
const useSettingSelector = <
T extends keyof ReturnType<typeof getSettings>,
S extends ReturnType<typeof getSettings>,
T extends keyof ReturnType<typeof getSettingsRaw>,
S extends ReturnType<typeof getSettingsRaw>,
R = S[T],
>(
selector: (s: S) => R,
@ -107,18 +160,29 @@ export const createSettingAtom = <T extends object>(
const stableSelector = useRefValue(selector)
return useAtomValue(
// @ts-expect-error
useMemo(() => selectAtom(atom, stableSelector.current, shallow), [stableSelector]),
useMemo(
() =>
selectAtom(
atom,
(state) => stableSelector.current(sanitizeSettingsSnapshot(state) as S),
shallow,
),
[stableSelector],
),
)
}
const setSetting = <K extends keyof ReturnType<typeof getSettings>>(
const setSetting = <K extends keyof ReturnType<typeof getSettingsRaw>>(
key: K,
value: ReturnType<typeof getSettings>[K],
value: ReturnType<typeof getSettingsRaw>[K],
) => {
const requiredLevel = getSettingPaidLevel(settingKey, String(key))
if (!canUpdatePaidSetting(requiredLevel)) {
return
}
const updated = Date.now()
setSettings({
...getSettings(),
...getSettingsRaw(),
[key]: value,
updated,
@ -135,6 +199,15 @@ export const createSettingAtom = <T extends object>(
setSettings(createDefaultSettings())
}
const useSettingValue = () => {
const value = useSettingValueRaw()
return useMemo(() => sanitizeSettingsSnapshot(value), [value])
}
const getSettings = () => {
return sanitizeSettingsSnapshot(getSettingsRaw())
}
Object.defineProperty(useSettingValue, "select", {
value: useSettingSelector,
})

View File

@ -15,6 +15,7 @@ export enum UserRole {
// TODO: remove this
Trial = "trial",
Pro = "pro",
Plus = "plus",
}
export const UserRoleName: Record<UserRole, string> = {
@ -27,4 +28,5 @@ export const UserRoleName: Record<UserRole, string> = {
*/
[UserRole.Trial]: "Free",
[UserRole.Pro]: "Pro",
[UserRole.Plus]: "Plus",
} as const

View File

@ -1,4 +1,57 @@
import type { AccentColor } from "./interface"
import type {
AccentColor,
AISettings,
GeneralSettings,
IntegrationSettings,
UISettings,
} from "./interface"
export enum SettingPaidLevels {
Free,
FreeLimited,
Plus,
}
type PartialRecord<K extends PropertyKey, V> = Partial<Record<K, V>>
export const PAID_SETTINGS = {
general: {
summary: SettingPaidLevels.FreeLimited,
translation: SettingPaidLevels.Plus,
translationMode: SettingPaidLevels.Plus,
hidePrivateSubscriptionsInTimeline: SettingPaidLevels.Plus,
},
ui: {
hideExtraBadge: SettingPaidLevels.Plus,
hideRecentReader: SettingPaidLevels.Plus,
},
integration: {
enableCubox: SettingPaidLevels.Plus,
enableObsidian: SettingPaidLevels.Plus,
enableOutline: SettingPaidLevels.Plus,
enableReadwise: SettingPaidLevels.Plus,
enableZotero: SettingPaidLevels.Plus,
enableInstapaper: SettingPaidLevels.Plus,
enableReadeck: SettingPaidLevels.Plus,
enableEagle: SettingPaidLevels.Plus,
enableQBittorrent: SettingPaidLevels.Plus,
enableCustomIntegration: SettingPaidLevels.Plus,
},
ai: {},
} as const satisfies {
general: PartialRecord<keyof GeneralSettings, SettingPaidLevels>
ui: PartialRecord<keyof UISettings, SettingPaidLevels>
integration: PartialRecord<keyof IntegrationSettings, SettingPaidLevels>
ai: PartialRecord<keyof AISettings, SettingPaidLevels>
}
export type SettingNamespace = keyof typeof PAID_SETTINGS
export const getSettingPaidLevel = (namespace: string, key: string) => {
const group = PAID_SETTINGS[namespace as keyof typeof PAID_SETTINGS]
if (!group) return
return group[key as keyof typeof group]
}
const ACCENT_COLOR_MAP = {
orange: {
@ -40,5 +93,6 @@ export const getAccentColorValue = (color: AccentColor) => {
if (color.startsWith("#")) {
return { light: color, dark: color }
}
return ACCENT_COLOR_MAP[color] || ACCENT_COLOR_MAP.orange
const preset = ACCENT_COLOR_MAP[color as keyof typeof ACCENT_COLOR_MAP]
return preset || ACCENT_COLOR_MAP.orange
}

View File

@ -1,6 +1,5 @@
import type { ActionConditionIndex } from "@follow-app/client-sdk"
import { useMutation, useQuery } from "@tanstack/react-query"
import { FetchError } from "ofetch"
import { useCallback } from "react"
import type { GeneralMutationOptions } from "../../types"
@ -16,19 +15,8 @@ export const usePrefetchActions = () => {
export const useUpdateActionsMutation = (options?: GeneralMutationOptions) => {
return useMutation({
...options,
mutationFn: () => actionSyncService.saveRules(),
onSuccess() {
options?.onSuccess?.()
},
onError(err) {
if (err instanceof FetchError && err.response?._data) {
const { message } = err.response._data
options?.onError?.(message)
return
}
options?.onError?.("Error saving actions")
},
})
}

View File

@ -2,7 +2,7 @@ import type { ModuleAPIs } from "@follow-app/client-sdk"
export type GeneralMutationOptions = {
onSuccess?: () => void
onError?: (errorMessage: string) => void
onError?: (errorMessage: Error) => void
}
export type GeneralQueryOptions = {

View File

@ -7,8 +7,8 @@ settings:
catalogs:
default:
'@follow-app/client-sdk':
specifier: 0.3.76
version: 0.3.76
specifier: 0.3.77
version: 0.3.77
tailwindcss-uikit-colors:
specifier: 1.0.0
version: 1.0.0
@ -339,7 +339,7 @@ importers:
version: 4.3.0
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow-app/readability':
specifier: workspace:*
version: link:../../../../packages/readability
@ -457,7 +457,7 @@ importers:
version: 3.0.2(electron@38.3.0)
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/database':
specifier: workspace:*
version: link:../../../../packages/internal/database
@ -807,6 +807,9 @@ importers:
'@expo/react-native-action-sheet':
specifier: 4.1.1
version: 4.1.1(react@19.0.0)
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/components':
specifier: workspace:*
version: link:../../packages/internal/components
@ -1187,7 +1190,7 @@ importers:
version: 6.2.1
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/tracker':
specifier: workspace:*
version: link:../../packages/internal/tracker
@ -1427,9 +1430,18 @@ importers:
'@follow/configs':
specifier: workspace:*
version: link:../../configs
'@follow/constants':
specifier: workspace:*
version: link:../constants
'@follow/hooks':
specifier: workspace:*
version: link:../hooks
'@follow/shared':
specifier: workspace:*
version: link:../shared
'@follow/store':
specifier: workspace:*
version: link:../store
'@follow/types':
specifier: workspace:*
version: link:../types
@ -1673,7 +1685,7 @@ importers:
dependencies:
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/configs':
specifier: workspace:*
version: link:../../configs
@ -1685,7 +1697,7 @@ importers:
dependencies:
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/constants':
specifier: workspace:*
version: link:../constants
@ -1757,7 +1769,7 @@ importers:
dependencies:
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/constants':
specifier: workspace:*
version: link:../constants
@ -1788,7 +1800,7 @@ importers:
version: 2.0.0(@types/node@24.8.1)
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@folo-services/drizzle':
specifier: 0.1.34
version: 0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
@ -1818,7 +1830,7 @@ importers:
dependencies:
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/configs':
specifier: workspace:*
version: link:../../configs
@ -1867,7 +1879,7 @@ importers:
devDependencies:
'@follow-app/client-sdk':
specifier: 'catalog:'
version: 0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@follow/configs':
specifier: workspace:*
version: link:../../configs
@ -4343,23 +4355,29 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@follow-app/client-sdk@0.3.76':
resolution: {integrity: sha512-cVZgG8BTXoqEomE+WzxrT47O1SYJEY1YDLI+InQuku/L2mQcwZCxxYJbPUicNIcH4b3smj7c+E4qr7QJGwgchg==}
'@follow-app/client-sdk@0.3.77':
resolution: {integrity: sha512-zr1FneNho5/wdDn2UWcjixlUf29kxbUqKXGnlvjJ6TOUJrygF8bh8GshgoHEkNj+4JUwMKo+WUtimyx6QpRrag==}
'@folo-services/ai-tools@0.2.49':
resolution: {integrity: sha512-QsKTW8N18812Thkdcc7wDTff3QkQxxTYHxHi1RM321jk4DbCq+mHlAr10Krwj0mRtKuULtqFHPm13udmfIv1Bw==}
'@folo-services/constants@0.1.40':
resolution: {integrity: sha512-sAYr/I+dlBrgQKTy085Xjler64wawOneeqvU5SYOugwf36RpWvVtz1aTo0KdiF1+M7gM1W+G1vkfnXAzOeBEaA==}
'@folo-services/constants@0.1.41':
resolution: {integrity: sha512-fXJ5DVWMgc4czW8i9JmVx3WckKhUksiwXkzzOClH0GePEOeS+DQ08o21UfZl1pBvhvhZm0pS+klHLljeBaRb1g==}
'@folo-services/drizzle@0.1.34':
resolution: {integrity: sha512-p6dsveNwXwNxmy+8+FHHKiFjVFXwc7yx7c4902k13hd1vcBihxiWUD4MdCwAcQTAPSJ3dYx1UZojHSdyiXwqnw==}
'@folo-services/drizzle@0.1.35':
resolution: {integrity: sha512-b+5OPwjKW3gnf0lkLj7PuhjLCldD9TItXNWRwIvJrPYmIGlrQmUiKgQIuLR8dsksF9L7w8IcIi4Vt40Qppx7nQ==}
'@folo-services/exceptions@0.1.19':
resolution: {integrity: sha512-rXgLssE3r3w2WzC3knCX9nYe0cszHXpYwAllrYOsPpzjQSHWRwJ6LuJyt0xlvdeX/9i8KQ/UYgf4to6Abt2whg==}
'@folo-services/shared@0.0.33':
resolution: {integrity: sha512-fN4tp7Euum0XyTfrNLPdkxqUwTxHOOYq15vSJm/xqVGZxMQx9DTwtZasydEGVzns9zqnb7HlrkjjvG7kxlJdXA==}
'@folo-services/exceptions@0.1.20':
resolution: {integrity: sha512-iuV6F22XI6kvSSVQRTCoS/wfJnPGvI3XQbA3YQGgXa8fT28SE+8LBGCyAj0rqbfnXlXdpxZE/IooXdpJyxkp0Q==}
'@folo-services/shared@0.0.34':
resolution: {integrity: sha512-T0gZRBlDVP9WLG0Dg8tLgWdk+TVjbdKfSvRHEyBY+YioQ1LO4vulN8ddCv1oBvMQxl0iESY9mnmB4ua9iGGkyQ==}
'@fontsource/sn-pro@5.2.5':
resolution: {integrity: sha512-rBdBv/0ygj6bkO7xDMMFpwobLdSrcQ2Jncb6DIwdeYGoAgeWkQRwVYhGDKasfLjEYCNYxrqr6wsXsM9+aU39RA==}
@ -21312,12 +21330,12 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
'@follow-app/client-sdk@0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
'@follow-app/client-sdk@0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@folo-services/constants': 0.1.40
'@folo-services/drizzle': 0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/exceptions': 0.1.19
'@folo-services/shared': 0.0.33(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/exceptions': 0.1.20
'@folo-services/shared': 0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
better-auth: 1.3.28(better-sqlite3@12.4.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
zod: 4.1.12
transitivePeerDependencies:
@ -21361,12 +21379,12 @@ snapshots:
- svelte
- vue
'@follow-app/client-sdk@0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
'@follow-app/client-sdk@0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@folo-services/constants': 0.1.40
'@folo-services/drizzle': 0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/exceptions': 0.1.19
'@folo-services/shared': 0.0.33(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/exceptions': 0.1.20
'@folo-services/shared': 0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
better-auth: 1.3.28(better-sqlite3@12.4.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
zod: 4.1.12
transitivePeerDependencies:
@ -21410,12 +21428,61 @@ snapshots:
- svelte
- vue
'@follow-app/client-sdk@0.3.76(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
'@follow-app/client-sdk@0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@folo-services/constants': 0.1.40
'@folo-services/drizzle': 0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/exceptions': 0.1.19
'@folo-services/shared': 0.0.33(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/exceptions': 0.1.20
'@folo-services/shared': 0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
better-auth: 1.3.28(better-sqlite3@12.4.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
zod: 4.1.12
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
- '@electric-sql/pglite'
- '@libsql/client'
- '@libsql/client-wasm'
- '@lynx-js/react'
- '@neondatabase/serverless'
- '@op-engineering/op-sqlite'
- '@opentelemetry/api'
- '@planetscale/database'
- '@prisma/client'
- '@sveltejs/kit'
- '@tidbcloud/serverless'
- '@types/better-sqlite3'
- '@types/pg'
- '@types/sql.js'
- '@upstash/redis'
- '@vercel/postgres'
- '@xata.io/client'
- better-sqlite3
- bun-types
- expo-sqlite
- gel
- hono
- knex
- kysely
- mysql2
- next
- pg
- pg-native
- postgres
- prisma
- react
- react-dom
- solid-js
- sql.js
- sqlite3
- svelte
- vue
'@follow-app/client-sdk@0.3.77(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/exceptions': 0.1.20
'@folo-services/shared': 0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
better-auth: 1.3.28(better-sqlite3@12.4.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
zod: 4.1.12
transitivePeerDependencies:
@ -21499,7 +21566,7 @@ snapshots:
- sql.js
- sqlite3
'@folo-services/constants@0.1.40':
'@folo-services/constants@0.1.41':
dependencies:
zod: 4.1.12
@ -21544,47 +21611,6 @@ snapshots:
- sql.js
- sqlite3
'@folo-services/drizzle@0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)':
dependencies:
'@folo-services/exceptions': 0.1.19
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
nanoid: 5.1.6
pg: 8.16.3
zod: 4.1.12
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
- '@electric-sql/pglite'
- '@libsql/client'
- '@libsql/client-wasm'
- '@neondatabase/serverless'
- '@op-engineering/op-sqlite'
- '@opentelemetry/api'
- '@planetscale/database'
- '@prisma/client'
- '@tidbcloud/serverless'
- '@types/better-sqlite3'
- '@types/pg'
- '@types/sql.js'
- '@upstash/redis'
- '@vercel/postgres'
- '@xata.io/client'
- better-sqlite3
- bun-types
- expo-sqlite
- gel
- hono
- knex
- kysely
- mysql2
- pg-native
- postgres
- prisma
- sql.js
- sqlite3
'@folo-services/drizzle@0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)':
dependencies:
'@folo-services/exceptions': 0.1.19
@ -21626,11 +21652,178 @@ snapshots:
- sql.js
- sqlite3
'@folo-services/drizzle@0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)':
dependencies:
'@folo-services/exceptions': 0.1.20
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
nanoid: 5.1.6
pg: 8.16.3
zod: 4.1.12
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
- '@electric-sql/pglite'
- '@libsql/client'
- '@libsql/client-wasm'
- '@neondatabase/serverless'
- '@op-engineering/op-sqlite'
- '@opentelemetry/api'
- '@planetscale/database'
- '@prisma/client'
- '@tidbcloud/serverless'
- '@types/better-sqlite3'
- '@types/pg'
- '@types/sql.js'
- '@upstash/redis'
- '@vercel/postgres'
- '@xata.io/client'
- better-sqlite3
- bun-types
- expo-sqlite
- gel
- hono
- knex
- kysely
- mysql2
- pg-native
- postgres
- prisma
- sql.js
- sqlite3
'@folo-services/drizzle@0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)':
dependencies:
'@folo-services/exceptions': 0.1.20
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
nanoid: 5.1.6
pg: 8.16.3
zod: 4.1.12
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
- '@electric-sql/pglite'
- '@libsql/client'
- '@libsql/client-wasm'
- '@neondatabase/serverless'
- '@op-engineering/op-sqlite'
- '@opentelemetry/api'
- '@planetscale/database'
- '@prisma/client'
- '@tidbcloud/serverless'
- '@types/better-sqlite3'
- '@types/pg'
- '@types/sql.js'
- '@upstash/redis'
- '@vercel/postgres'
- '@xata.io/client'
- better-sqlite3
- bun-types
- expo-sqlite
- gel
- hono
- knex
- kysely
- mysql2
- pg-native
- postgres
- prisma
- sql.js
- sqlite3
'@folo-services/drizzle@0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)':
dependencies:
'@folo-services/exceptions': 0.1.20
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
nanoid: 5.1.6
pg: 8.16.3
zod: 4.1.12
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
- '@electric-sql/pglite'
- '@libsql/client'
- '@libsql/client-wasm'
- '@neondatabase/serverless'
- '@op-engineering/op-sqlite'
- '@opentelemetry/api'
- '@planetscale/database'
- '@prisma/client'
- '@tidbcloud/serverless'
- '@types/better-sqlite3'
- '@types/pg'
- '@types/sql.js'
- '@upstash/redis'
- '@vercel/postgres'
- '@xata.io/client'
- better-sqlite3
- bun-types
- expo-sqlite
- gel
- hono
- knex
- kysely
- mysql2
- pg-native
- postgres
- prisma
- sql.js
- sqlite3
'@folo-services/drizzle@0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)':
dependencies:
'@folo-services/exceptions': 0.1.20
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
nanoid: 5.1.6
pg: 8.16.3
zod: 4.1.12
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
- '@electric-sql/pglite'
- '@libsql/client'
- '@libsql/client-wasm'
- '@neondatabase/serverless'
- '@op-engineering/op-sqlite'
- '@opentelemetry/api'
- '@planetscale/database'
- '@prisma/client'
- '@tidbcloud/serverless'
- '@types/better-sqlite3'
- '@types/pg'
- '@types/sql.js'
- '@upstash/redis'
- '@vercel/postgres'
- '@xata.io/client'
- better-sqlite3
- bun-types
- expo-sqlite
- gel
- hono
- knex
- kysely
- mysql2
- pg-native
- postgres
- prisma
- sql.js
- sqlite3
'@folo-services/exceptions@0.1.19': {}
'@folo-services/shared@0.0.33(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
'@folo-services/exceptions@0.1.20': {}
'@folo-services/shared@0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
dependencies:
'@folo-services/drizzle': 0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
@ -21668,9 +21861,51 @@ snapshots:
- sql.js
- sqlite3
'@folo-services/shared@0.0.33(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
'@folo-services/shared@0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
dependencies:
'@folo-services/drizzle': 0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
zod: 4.1.12
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
- '@electric-sql/pglite'
- '@libsql/client'
- '@libsql/client-wasm'
- '@neondatabase/serverless'
- '@op-engineering/op-sqlite'
- '@opentelemetry/api'
- '@planetscale/database'
- '@prisma/client'
- '@tidbcloud/serverless'
- '@types/better-sqlite3'
- '@types/pg'
- '@types/sql.js'
- '@upstash/redis'
- '@vercel/postgres'
- '@xata.io/client'
- better-sqlite3
- bun-types
- expo-sqlite
- gel
- hono
- knex
- kysely
- mysql2
- pg
- pg-native
- postgres
- prisma
- sql.js
- sqlite3
'@folo-services/shared@0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
dependencies:
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
@ -21708,9 +21943,10 @@ snapshots:
- sql.js
- sqlite3
'@folo-services/shared@0.0.33(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
'@folo-services/shared@0.0.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
dependencies:
'@folo-services/drizzle': 0.1.34(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@folo-services/constants': 0.1.41
'@folo-services/drizzle': 0.1.35(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(hono@4.9.8)(kysely@0.28.8)
'@hono/zod-openapi': 1.1.4(hono@4.9.8)(zod@4.1.12)
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12)
@ -24103,7 +24339,7 @@ snapshots:
debug: 2.6.9
invariant: 2.2.4
metro: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-config: 0.82.4(bufferutil@4.0.9)
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-core: 0.82.4
semver: 7.7.3
transitivePeerDependencies:
@ -27953,6 +28189,15 @@ snapshots:
kysely: 0.28.8
pg: 8.16.3
drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3):
optionalDependencies:
'@opentelemetry/api': 1.9.0
'@types/pg': 8.15.5
better-sqlite3: 12.4.1
expo-sqlite: 15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0)
kysely: 0.28.8
pg: 8.16.3
drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3):
optionalDependencies:
'@opentelemetry/api': 1.9.0
@ -27994,6 +28239,11 @@ snapshots:
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
zod: 4.1.12
drizzle-zod@0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12):
dependencies:
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
zod: 4.1.12
drizzle-zod@0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.12):
dependencies:
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.5)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.5)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(kysely@0.28.8)(pg@8.16.3)
@ -32222,6 +32472,22 @@ snapshots:
- supports-color
- utf-8-validate
metro-config@0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5):
dependencies:
connect: 3.7.0
cosmiconfig: 5.2.1
flow-enums-runtime: 0.0.6
jest-validate: 29.7.0
metro: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-cache: 0.82.4
metro-core: 0.82.4
metro-runtime: 0.82.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
optional: true
metro-core@0.82.4:
dependencies:
flow-enums-runtime: 0.0.6
@ -32407,7 +32673,7 @@ snapshots:
metro-babel-transformer: 0.82.4
metro-cache: 0.82.4
metro-cache-key: 0.82.4
metro-config: 0.82.4(bufferutil@4.0.9)
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-core: 0.82.4
metro-file-map: 0.82.4
metro-resolver: 0.82.4

View File

@ -8,7 +8,7 @@ packages:
- "!**/example/**"
catalog:
"@follow-app/client-sdk": 0.3.76
"@follow-app/client-sdk": 0.3.77
tailwindcss-uikit-colors: 1.0.0
typescript: 5.9.3