From c8c789f4fdb3a26c87d6b97e882c6c2a11237f4e Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 3 Nov 2025 19:51:22 +0800 Subject: [PATCH] refactor(updater): app update based own server Signed-off-by: Innei --- apps/desktop/dev-only/dev-app-update.yml | 2 + apps/desktop/layer/main/src/lib/api-client.ts | 26 +- apps/desktop/layer/main/src/updater/api.ts | 15 + .../src/updater/custom-github-provider.ts | 413 --------------- .../src/updater/follow-update-provider.ts | 203 ++++++++ .../layer/main/src/updater/hot-updater.ts | 473 +++++++++--------- apps/desktop/layer/main/src/updater/index.ts | 467 +++++++++++------ apps/desktop/layer/main/src/updater/types.ts | 91 ---- apps/desktop/layer/main/src/updater/utils.ts | 55 -- pnpm-lock.yaml | 335 +++++-------- pnpm-workspace.yaml | 2 +- 11 files changed, 933 insertions(+), 1149 deletions(-) create mode 100644 apps/desktop/dev-only/dev-app-update.yml create mode 100644 apps/desktop/layer/main/src/updater/api.ts delete mode 100644 apps/desktop/layer/main/src/updater/custom-github-provider.ts create mode 100644 apps/desktop/layer/main/src/updater/follow-update-provider.ts delete mode 100644 apps/desktop/layer/main/src/updater/types.ts delete mode 100644 apps/desktop/layer/main/src/updater/utils.ts diff --git a/apps/desktop/dev-only/dev-app-update.yml b/apps/desktop/dev-only/dev-app-update.yml new file mode 100644 index 000000000..cc7b7f84c --- /dev/null +++ b/apps/desktop/dev-only/dev-app-update.yml @@ -0,0 +1,2 @@ +provider: custom +channel: latest diff --git a/apps/desktop/layer/main/src/lib/api-client.ts b/apps/desktop/layer/main/src/lib/api-client.ts index bf171a91d..1c66241e1 100644 --- a/apps/desktop/layer/main/src/lib/api-client.ts +++ b/apps/desktop/layer/main/src/lib/api-client.ts @@ -1,10 +1,12 @@ import { env } from "@follow/shared/env.desktop" import { createDesktopAPIHeaders } from "@follow/utils/headers" import { FollowClient } from "@follow-app/client-sdk" -import PKG from "@pkg" +import PKG, { mainHash, version as appVersion } from "@pkg" +import { gte } from "semver" import { BETTER_AUTH_COOKIE_NAME_SESSION_TOKEN } from "~/constants/app" import { WindowManager } from "~/manager/window" +import { getCurrentRendererManifest } from "~/updater/hot-updater" import { logger } from "../logger" @@ -20,13 +22,17 @@ export const followClient = new FollowClient({ }), }) -export const followApi = followClient.api +export const apiClient = followClient.api followClient.addRequestInterceptor(async (ctx) => { const { options } = ctx const header = options.headers || {} const apiHeader = createDesktopAPIHeaders({ version: PKG.version }) + const rendererManifest = getCurrentRendererManifest() + const rendererVersion = gte(rendererManifest?.version ?? appVersion, appVersion) + ? (rendererManifest?.version ?? appVersion) + : appVersion // Get cookies for authentication const window = WindowManager.getMainWindow() @@ -44,10 +50,14 @@ followClient.addRequestInterceptor(async (ctx) => { ...apiHeader, Cookie: headerCookie, "User-Agent": userAgent, + + "X-Follow-Main-Hash": mainHash, + "X-Follow-Renderer-Version": rendererVersion, + "X-Follow-App-Version": appVersion, + "X-Follow-Platform": process.platform, } return ctx }) - followClient.addResponseInterceptor(({ response }) => { logger.info(`API Response: ${response.status} ${response.statusText}`) return response @@ -67,14 +77,10 @@ followClient.addResponseInterceptor(async ({ response }) => { } try { - const json = await response.clone().json() - logger.error("API Error details:", json) - } catch { - // ignore JSON parsing errors + await response.clone().json() + } catch (error) { + logger.error("API Error details:", error) } return response }) - -// Legacy export for compatibility -export const apiClient = followApi diff --git a/apps/desktop/layer/main/src/updater/api.ts b/apps/desktop/layer/main/src/updater/api.ts new file mode 100644 index 000000000..dd9bad7fc --- /dev/null +++ b/apps/desktop/layer/main/src/updater/api.ts @@ -0,0 +1,15 @@ +import type { GetLatestReleaseQuery, LatestReleasePayload } from "@follow-app/client-sdk" + +import { apiClient } from "~/lib/api-client" + +let cachedLatestRelease: LatestReleasePayload | null = null + +export const getUpdateInfo = async ( + query: GetLatestReleaseQuery = {}, +): Promise => { + const response = await apiClient.updates.getLatestRelease(query) + cachedLatestRelease = response.data + return cachedLatestRelease +} + +export const getCachedUpdateInfo = () => cachedLatestRelease diff --git a/apps/desktop/layer/main/src/updater/custom-github-provider.ts b/apps/desktop/layer/main/src/updater/custom-github-provider.ts deleted file mode 100644 index 8e137853f..000000000 --- a/apps/desktop/layer/main/src/updater/custom-github-provider.ts +++ /dev/null @@ -1,413 +0,0 @@ -// credits: migrated from https://github.com/toeverything/AFFiNE/blob/a802dc4fd6aa720f7a7a995816c78c0b24c514f5/packages/frontend/electron/src/main/updater/custom-github-provider.ts - -import { URL } from "node:url" - -import type { - CustomPublishOptions, - GithubOptions, - ReleaseNoteInfo, - XElement, -} from "builder-util-runtime" -import { HttpError, newError, parseXml } from "builder-util-runtime" -import type { AppUpdater, ResolvedUpdateFileInfo, UpdateInfo } from "electron-updater" -import { CancellationToken } from "electron-updater" -import { BaseGitHubProvider } from "electron-updater/out/providers/GitHubProvider" -import type { ProviderRuntimeOptions } from "electron-updater/out/providers/Provider" -import { parseUpdateInfo, resolveFiles } from "electron-updater/out/providers/Provider" -import * as semver from "semver" - -import { isWindows } from "../env" -import { githubProviderLogger as logger, logObject } from "./logger" -import { isSquirrelBuild } from "./utils" - -interface GithubUpdateInfo extends UpdateInfo { - tag: string -} - -interface GithubRelease { - id: number - tag_name: string - target_commitish: string - name: string - draft: boolean - prerelease: boolean - created_at: string - published_at: string -} - -const hrefRegExp = /\/tag\/([^/]+)$/ - -export class CustomGitHubProvider extends BaseGitHubProvider { - constructor( - options: CustomPublishOptions, - private readonly updater: AppUpdater, - runtimeOptions: ProviderRuntimeOptions, - ) { - super(options as unknown as GithubOptions, "github.com", runtimeOptions) - } - - async getLatestVersion(): Promise { - logger.info("Starting getLatestVersion") - logObject(logger, "Provider Configuration", { - "Base URL": this.baseUrl.href, - "Base Path": this.basePath, - "Current Version": this.updater.currentVersion, - "Allow Prerelease": this.updater.allowPrerelease, - }) - - const cancellationToken = new CancellationToken() - - const feedUrl = newUrlFromBase(`${this.basePath}.atom`, this.baseUrl) - logger.info(`Fetching feed from: ${feedUrl.href}`) - - const feedXml = await this.httpRequest( - feedUrl, - { - accept: "application/xml, application/atom+xml, text/xml, */*", - }, - cancellationToken, - ) - - if (!feedXml) { - throw new Error(`Cannot find feed in the remote server (${this.baseUrl.href})`) - } - - logger.info(`Feed fetched successfully, length: ${feedXml.length} bytes`) - - const feed = parseXml(feedXml) - // noinspection TypeScriptValidateJSTypes - let latestRelease = feed.element("entry", false, `No published versions on GitHub`) - let tag: string | null = null - try { - const currentChannel = - this.options.channel || - this.updater?.channel || - (semver.prerelease(this.updater.currentVersion)?.[0] as string) || - null - - logger.info(`Current Channel: ${currentChannel}`) - - if (currentChannel === null) { - throw newError( - `Cannot parse channel from version: ${this.updater.currentVersion}`, - "ERR_UPDATER_INVALID_VERSION", - ) - } - - logger.info(`Fetching latest tag by release API for channel: ${currentChannel}`) - const releaseTag = await this.getLatestTagByRelease(currentChannel, cancellationToken) - logger.info(`Release tag from API: ${releaseTag || "null (will use feed matching)"}`) - logger.info("Iterating through feed entries to find matching release") - let entryCount = 0 - for (const element of feed.getElements("entry")) { - entryCount++ - // noinspection TypeScriptValidateJSTypes - const href = element.element("link").attribute("href") - const hrefElement = hrefRegExp.exec(href) - - // If this is null then something is wrong and skip this release - if (hrefElement === null) { - logger.warn(`Entry #${entryCount}: Invalid href format: ${href}`) - continue - } - - // This Release's Tag - const hrefTag = decodeURIComponent(hrefElement[1]!) - logger.debug(`Entry #${entryCount}: Processing tag: ${hrefTag}`) - - // Get Channel from this release's tag - // Handle new format: desktop/v1.2.3 or mobile/v1.2.3 - let hrefChannel = "stable" - if (hrefTag.startsWith("desktop/")) { - // For desktop tags, extract the version and check if it's a prerelease - const version = hrefTag.replace("desktop/", "") - hrefChannel = (semver.prerelease(version)?.[0] as string) || "stable" - logger.debug( - `Entry #${entryCount}: Desktop tag detected, version: ${version}, channel: ${hrefChannel}`, - ) - } else if (hrefTag.startsWith("mobile/")) { - // Skip mobile releases for desktop updater - logger.debug(`Entry #${entryCount}: Skipping mobile tag`) - continue - } else { - // Legacy format: check for prerelease directly - hrefChannel = (semver.prerelease(hrefTag)?.[0] as string) || "stable" - logger.debug(`Entry #${entryCount}: Legacy tag format, channel: ${hrefChannel}`) - } - - let isNextPreRelease = false - if (releaseTag) { - isNextPreRelease = releaseTag === hrefTag - logger.debug(`Entry #${entryCount}: Matching by release tag: ${releaseTag === hrefTag}`) - } else { - isNextPreRelease = hrefChannel === currentChannel - logger.debug( - `Entry #${entryCount}: Matching by channel: ${hrefChannel} === ${currentChannel} = ${isNextPreRelease}`, - ) - } - - if (isNextPreRelease) { - tag = hrefTag - latestRelease = element - logger.info(`✓ Found matching release at entry #${entryCount}: ${hrefTag}`) - break - } - } - logger.info(`Processed ${entryCount} feed entries total`) - } catch (e: any) { - logger.error(`Failed to parse releases feed: ${e.stack || e.message}`) - throw newError( - `Cannot parse releases feed: ${e.stack || e.message},\nXML:\n${feedXml}`, - "ERR_UPDATER_INVALID_RELEASE_FEED", - ) - } - - if (tag === null || tag === undefined) { - logger.error("No matching published versions found on GitHub") - throw newError(`No published versions on GitHub`, "ERR_UPDATER_NO_PUBLISHED_VERSIONS") - } - - let rawData: string | null = null - let channelFile = "" - let channelFileUrl: any = "" - const fetchData = async (channelName: string) => { - channelFile = getChannelFilename(channelName) - channelFileUrl = newUrlFromBase( - this.getBaseDownloadPath(String(tag), channelFile), - this.baseUrl, - ) - logger.info(`Fetching channel file: ${channelFile} from ${channelFileUrl}`) - const requestOptions = this.createRequestOptions(channelFileUrl) - try { - const data = await this.executor.request(requestOptions, cancellationToken) - logger.info(`Successfully fetched ${channelFile}`) - return data - } catch (e: any) { - if (e instanceof HttpError && e.statusCode === 404) { - logger.warn(`Channel file not found: ${channelFile} (404)`) - throw newError( - `Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${ - e.stack || e.message - }`, - "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND", - ) - } - logger.error(`Failed to fetch channel file: ${e.message}`) - throw e - } - } - - try { - const channel = this.updater.allowPrerelease - ? this.getCustomChannelName(String(semver.prerelease(tag)?.[0] || "latest")) - : this.getDefaultChannelName() - logger.info(`Attempting to fetch channel: ${channel}`) - rawData = await fetchData(channel) - } catch (e: any) { - if (this.updater.allowPrerelease) { - // Allow fallback to `latest.yml` - logger.info("Falling back to default channel (latest.yml)") - rawData = await fetchData(this.getDefaultChannelName()) - } else { - throw e - } - } - - const result = parseUpdateInfo(rawData, channelFile, channelFileUrl) - if (result.releaseName == null) { - result.releaseName = latestRelease.elementValueOrEmpty("title") - } - - if (result.releaseNotes == null) { - result.releaseNotes = computeReleaseNotes( - this.updater.currentVersion, - this.updater.fullChangelog, - feed, - latestRelease, - ) - } - - logger.info(`Update info parsed successfully`) - logObject(logger, "Update Result", { - Tag: tag, - Version: result.version, - "Release Name": result.releaseName || "N/A", - "Release Date": result.releaseDate || "N/A", - Files: result.files?.length || 0, - }) - - return { - tag, - ...result, - } - } - - private get basePath(): string { - return `/${this.options.owner}/${this.options.repo}/releases` - } - - /** - * Use release api to get latest version to filter draft version. - * But this api have low request limit 60-times/1-hour, use this to help, not depend on it - * https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28 - * https://api.github.com/repos/toeverything/affine/releases - * https://docs.github.com/en/rest/rate-limit/rate-limit?apiVersion=2022-11-28#about-rate-limits - */ - private async getLatestTagByRelease( - currentChannel: string, - cancellationToken: CancellationToken, - ) { - try { - const apiUrl = newUrlFromBase(`/repos${this.basePath}`, this.baseApiUrl) - logger.debug(`Fetching releases from GitHub API: ${apiUrl}`) - - const releasesStr = await this.httpRequest( - apiUrl, - { - accept: "Accept: application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - }, - cancellationToken, - ) - - if (!releasesStr) { - logger.warn("GitHub API returned empty response") - return null - } - - const releases: GithubRelease[] = JSON.parse(releasesStr) - logger.debug(`Received ${releases.length} releases from GitHub API`) - - let checkedCount = 0 - for (const release of releases) { - if (release.draft) { - logger.debug(`Skipping draft release: ${release.tag_name}`) - continue - } - - checkedCount++ - const releaseTag = release.tag_name - - // Handle new format: desktop/v1.2.3 or mobile/v1.2.3 - let releaseChannel = "stable" - if (releaseTag.startsWith("desktop/")) { - // For desktop tags, extract the version and check if it's a prerelease - const version = releaseTag.replace("desktop/", "") - releaseChannel = (semver.prerelease(version)?.[0] as string) || "stable" - logger.debug(`API Release: ${releaseTag} (desktop, channel: ${releaseChannel})`) - } else if (releaseTag.startsWith("mobile/")) { - // Skip mobile releases for desktop updater - logger.debug(`Skipping mobile release: ${releaseTag}`) - continue - } else { - // Legacy format: check for prerelease directly - releaseChannel = (semver.prerelease(releaseTag)?.[0] as string) || "stable" - logger.debug(`API Release: ${releaseTag} (legacy, channel: ${releaseChannel})`) - } - - if (releaseChannel === currentChannel) { - logger.info(`✓ Found matching release via API: ${release.tag_name}`) - return release.tag_name - } - } - - logger.info(`No matching release found via API (checked ${checkedCount} non-draft releases)`) - } catch (e: any) { - logger.warn(`Cannot parse release from API: ${e.message}`) - } - - return null - } - - resolveFiles(updateInfo: GithubUpdateInfo): Array { - logger.info(`Resolving files for tag: ${updateInfo.tag}`) - logger.debug(`Total files in update info: ${updateInfo.files.length}`) - - const filteredUpdateInfo = structuredClone(updateInfo) - // for windows, we need to determine its installer type (nsis or squirrel) - if (isWindows && updateInfo.files.length > 1) { - const isSquirrel = isSquirrelBuild() - logger.info(`Windows build detected, installer type: ${isSquirrel ? "Squirrel" : "NSIS"}`) - - // @ts-expect-error we should be able to modify the object - filteredUpdateInfo.files = updateInfo.files.filter((file) => - isSquirrel ? !file.url.includes("nsis.exe") : file.url.includes("nsis.exe"), - ) - - logger.debug( - `Filtered to ${filteredUpdateInfo.files.length} files after Windows installer type filtering`, - ) - } - - // still replace space to - due to backward compatibility - const resolved = resolveFiles(filteredUpdateInfo, this.baseUrl, (p) => - this.getBaseDownloadPath(filteredUpdateInfo.tag, p.replaceAll(" ", "-")), - ) - - logger.info(`Resolved ${resolved.length} file(s) for download`) - resolved.forEach((file, index) => { - logger.debug(` File ${index + 1}: ${file.url}`) - }) - - return resolved - } - - private getBaseDownloadPath(tag: string, fileName: string): string { - return `${this.basePath}/download/${tag}/${fileName}` - } -} - -export interface CustomGitHubOptions { - channel: string - repo: string - owner: string - releaseType: "release" | "prerelease" -} - -function getNoteValue(parent: XElement): string { - const result = parent.elementValueOrEmpty("content") - // GitHub reports empty notes as No content. - return result === "No content." ? "" : result -} - -export function computeReleaseNotes( - currentVersion: semver.SemVer, - isFullChangelog: boolean, - feed: XElement, - latestRelease: any, -): string | Array | null { - if (!isFullChangelog) { - return getNoteValue(latestRelease) - } - - const releaseNotes: Array = [] - for (const release of feed.getElements("entry")) { - // noinspection TypeScriptValidateJSTypes - const versionRelease = /\/tag\/v?([^/]+)$/.exec(release.element("link").attribute("href"))?.[1] - if (versionRelease && semver.lt(currentVersion, versionRelease)) { - releaseNotes.push({ - version: versionRelease, - note: getNoteValue(release), - }) - } - } - return releaseNotes.sort((a, b) => semver.rcompare(a.version, b.version)) -} - -// addRandomQueryToAvoidCaching is false by default because in most cases URL already contains version number, -// so, it makes sense only for Generic Provider for channel files -function newUrlFromBase(pathname: string, baseUrl: URL, addRandomQueryToAvoidCaching = false): URL { - const result = new URL(pathname, baseUrl) - // search is not propagated (search is an empty string if not specified) - const { search } = baseUrl - if (search != null && search.length > 0) { - result.search = search - } else if (addRandomQueryToAvoidCaching) { - result.search = `noCache=${Date.now().toString(32)}` - } - return result -} - -function getChannelFilename(channel: string): string { - return `${channel}.yml` -} diff --git a/apps/desktop/layer/main/src/updater/follow-update-provider.ts b/apps/desktop/layer/main/src/updater/follow-update-provider.ts new file mode 100644 index 000000000..3d669f281 --- /dev/null +++ b/apps/desktop/layer/main/src/updater/follow-update-provider.ts @@ -0,0 +1,203 @@ +import { URL } from "node:url" + +import type { + AppUpdate, + LatestReleasePayload, + PlatformUpdate, + PlatformUpdateFile, +} from "@follow-app/client-sdk" +import type { UpdateFileInfo, UpdateInfo } from "builder-util-runtime" +import { newError } from "builder-util-runtime" +import type { AppUpdater } from "electron-updater" +import type { ProviderRuntimeOptions } from "electron-updater/out/providers/Provider" +import { Provider } from "electron-updater/out/providers/Provider" +import type { ResolvedUpdateFileInfo } from "electron-updater/out/types" + +import { logger } from "../logger" +import { getUpdateInfo } from "./api" + +interface FollowProviderOptions { + provider: "custom" +} + +type FollowProviderContext = { + payload: LatestReleasePayload + platform: PlatformUpdate +} + +export class FollowUpdateProvider extends Provider { + private static context: FollowProviderContext | null = null + + static setContext(context: FollowProviderContext) { + FollowUpdateProvider.context = context + } + + static clearContext() { + FollowUpdateProvider.context = null + } + + static getContext() { + return FollowUpdateProvider.context + } + + constructor( + _options: FollowProviderOptions, + _updater: AppUpdater, + runtimeOptions: ProviderRuntimeOptions, + ) { + super(runtimeOptions) + } + + async getLatestVersion(): Promise { + const context = await this.ensureContext() + return this.buildUpdateInfo(context) + } + + resolveFiles(updateInfo: UpdateInfo): Array { + return updateInfo.files.map((file) => ({ + info: file, + url: new URL(file.url), + })) + } + + private buildUpdateInfo(context: FollowProviderContext): UpdateInfo { + const { payload, platform } = context + const files = this.mapFiles(platform.files) + + if (files.length === 0) { + throw newError( + `No downloadable files found for platform ${platform.platform}`, + "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND", + ) + } + + const primaryFile = files[0] + if (!primaryFile) { + throw newError( + `Platform ${platform.platform} provides no downloadable file`, + "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND", + ) + } + + let primaryPath = primaryFile.url + const primaryUrl = this.safeParseUrl(primaryFile.url) + if (primaryUrl) { + const filename = primaryUrl.pathname.split("/").pop() + if (filename) { + primaryPath = filename + } + } + + const { release } = payload + + return { + version: platform.version, + files, + path: primaryPath, + sha512: primaryFile.sha512, + releaseName: release.name, + releaseNotes: release.body, + releaseDate: platform.releaseDate || release.publishedAt || new Date().toISOString(), + } + } + + private mapFiles(files: PlatformUpdate["files"]): UpdateFileInfo[] { + if (!files) return [] + + return files + .map((file) => this.mapFile(file)) + .filter((file): file is UpdateFileInfo => file !== null) + } + + private mapFile(file: PlatformUpdateFile): UpdateFileInfo | null { + if (!file.downloadUrl || !file.sha512) { + logger.warn("Skip platform file without downloadUrl or sha512", file) + return null + } + + const mapped: UpdateFileInfo = { + url: file.downloadUrl, + sha512: file.sha512, + } + + if (typeof file.size === "number") { + mapped.size = file.size + } + + return mapped + } + + private safeParseUrl(value: string): URL | null { + try { + return new URL(value) + } catch (error) { + logger.debug?.("Unable to parse update file URL", error) + return null + } + } + + private async ensureContext(): Promise { + const context = FollowUpdateProvider.getContext() + if (context) return context + + const fetched = await this.fetchContext() + FollowUpdateProvider.setContext(fetched) + return fetched + } + + private async fetchContext(): Promise { + const payload = await getUpdateInfo({}) + const { decision } = payload + + if (!decision || decision.type !== "app" || !decision.app) { + throw newError( + "No app update metadata available from provider", + "ERR_UPDATER_NO_PUBLISHED_VERSIONS", + ) + } + + const platform = this.pickPlatform(decision.app) + if (!platform) { + throw newError( + `No matching platform update for ${process.platform}/${process.arch}`, + "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND", + ) + } + + return { payload, platform } + } + + private pickPlatform(appDecision: AppUpdate): PlatformUpdate | null { + const platforms = appDecision.platforms ?? [] + const selected = appDecision.selectedPlatform + if (selected) { + return selected + } + + const candidates = this.resolvePlatformCandidates() + const matched = platforms.find((platform) => + candidates.includes(platform.platform.toLowerCase()), + ) + + return matched ?? platforms[0] ?? null + } + + private resolvePlatformCandidates(): string[] { + const base = new Set() + base.add(process.platform) + base.add(`${process.platform}-${process.arch}`) + base.add(process.arch) + + if (process.platform === "darwin") { + base.add("mac") + base.add("macos") + } + + if (process.platform === "win32") { + base.add("windows") + base.add("win") + } + + return Array.from(base).map((value) => value.toLowerCase()) + } +} diff --git a/apps/desktop/layer/main/src/updater/hot-updater.ts b/apps/desktop/layer/main/src/updater/hot-updater.ts index afcc22bbb..3794969d7 100644 --- a/apps/desktop/layer/main/src/updater/hot-updater.ts +++ b/apps/desktop/layer/main/src/updater/hot-updater.ts @@ -1,271 +1,282 @@ -/** - * @description This file handles hot updates for the electron renderer layer - */ import { existsSync, readFileSync } from "node:fs" import { mkdir, readdir, rename, rm, stat, writeFile } from "node:fs/promises" import os from "node:os" import { callWindowExpose } from "@follow/shared/bridge" +import type { LatestReleasePayload, RendererUpdate } from "@follow-app/client-sdk" import { mainHash, version as appVersion } from "@pkg" import log from "electron-log" -import { load } from "js-yaml" +import { dump, load } from "js-yaml" import path from "pathe" import { x } from "tar" -import { GITHUB_OWNER, GITHUB_REPO, HOTUPDATE_RENDER_ENTRY_DIR } from "~/constants/app" +import { HOTUPDATE_RENDER_ENTRY_DIR } from "~/constants/app" import { downloadFileWithProgress } from "~/lib/download" import { WindowManager } from "~/manager/window" import { appUpdaterConfig } from "./configs" -const logger = log.scope("hot-updater") +declare const GIT_COMMIT_HASH: string | undefined -const url = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}` -const releasesUrl = `${url}/releases` -const releaseApiUrl = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases` +export type RendererManifest = RendererUpdate & { + downloadUrl: string + downloadedAt?: string +} -const getLatestReleaseTag = async () => { - // First try to get the latest release - try { - const latestRes = await fetch(`${releaseApiUrl}/latest`) - if (latestRes.ok) { - const latestRelease = await latestRes.json() - // Check if the latest release is a desktop release - if ( - latestRelease.tag_name && - latestRelease.tag_name.startsWith("desktop/") && - !latestRelease.draft - ) { - return latestRelease.tag_name +export enum RendererEligibilityStatus { + NoManifest, + RequiresFullAppUpdate, + AlreadyCurrent, + Eligible, +} + +export interface RendererEligibilityResult { + status: RendererEligibilityStatus + manifest?: RendererManifest + reason?: string +} + +class RendererHotUpdater { + private readonly logger = log.scope("updater:renderer") + private readonly tempDir = path.resolve(os.tmpdir(), "follow-render-update") + private readonly manifestPath = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, "manifest.yml") + + extractManifest(payload: LatestReleasePayload | null): RendererManifest | null { + if (!payload) return null + + const { decision } = payload + if (!decision || decision.type !== "renderer") { + return null + } + + const { renderer } = decision + if (!renderer) { + this.logger.debug("Renderer decision payload missing renderer field") + return null + } + + if (!renderer.downloadUrl) { + this.logger.warn("Renderer decision missing downloadUrl, skip renderer hot update") + return null + } + + if (!renderer.filename) { + this.logger.warn("Renderer decision missing filename, skip renderer hot update") + return null + } + + if (!renderer.hash) { + this.logger.warn("Renderer decision missing hash, skip renderer hot update") + return null + } + + return { + ...renderer, + downloadUrl: renderer.downloadUrl, + } + } + + evaluateManifest(manifest: RendererManifest | null): RendererEligibilityResult { + if (!manifest) { + return { status: RendererEligibilityStatus.NoManifest } + } + + if (manifest.mainHash && manifest.mainHash !== mainHash) { + return { + status: RendererEligibilityStatus.RequiresFullAppUpdate, + manifest, + reason: `Renderer payload requires main hash ${manifest.mainHash}, current main hash is ${mainHash}`, } } - } catch (error) { - logger.warn("Failed to fetch latest release, falling back to all releases", error) + + if (manifest.version === appVersion) { + return { + status: RendererEligibilityStatus.AlreadyCurrent, + reason: "Renderer version matches current app version", + } + } + + if (manifest.commit && GIT_COMMIT_HASH && manifest.commit === GIT_COMMIT_HASH) { + return { + status: RendererEligibilityStatus.AlreadyCurrent, + reason: "Renderer commit matches current main commit", + } + } + + const installedManifest = this.getCurrentManifest() + if (installedManifest) { + if (installedManifest.version === manifest.version) { + return { + status: RendererEligibilityStatus.AlreadyCurrent, + reason: "Installed renderer manifest already at target version", + } + } + + if ( + installedManifest.commit && + manifest.commit && + installedManifest.commit === manifest.commit + ) { + return { + status: RendererEligibilityStatus.AlreadyCurrent, + reason: "Installed renderer manifest commit matches target commit", + } + } + } + + return { + status: RendererEligibilityStatus.Eligible, + manifest, + } } - // If latest release is not a desktop release or fetch failed, get all releases - const res = await fetch(releaseApiUrl) + async applyManifest(manifest: RendererManifest): Promise { + if (!appUpdaterConfig.enableRenderHotUpdate) { + this.logger.info("Renderer hot update skipped because it is disabled in config") + return + } - if (!res.ok) { - throw new Error(`GitHub API request failed: ${res.status} ${res.statusText}`) + const archivePath = await this.downloadArchive(manifest) + + await mkdir(HOTUPDATE_RENDER_ENTRY_DIR, { recursive: true }) + this.logger.info(`Extracting renderer bundle to ${HOTUPDATE_RENDER_ENTRY_DIR}`) + + await x({ + f: archivePath, + cwd: HOTUPDATE_RENDER_ENTRY_DIR, + }) + + const extractedDir = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, "renderer") + const targetDir = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, manifest.version) + + const extractedStats = await stat(extractedDir).catch(() => null) + if (!extractedStats) { + throw new Error(`Extracted renderer directory not found at ${extractedDir}`) + } + + await rm(targetDir, { recursive: true, force: true }) + await rename(extractedDir, targetDir) + + await this.writeManifest({ ...manifest, downloadedAt: new Date().toISOString() }) + + try { + await rm(archivePath, { force: true }) + } catch (error) { + this.logger.warn("Failed to clean renderer archive", error) + } + + this.logger.info(`Renderer hot update applied successfully: ${manifest.version}`) + + const mainWindow = WindowManager.getMainWindow() + if (mainWindow) { + callWindowExpose(mainWindow).readyToUpdate() + } } - const releases = await res.json() + getCurrentManifest(): RendererManifest | null { + if (!existsSync(this.manifestPath)) { + return null + } - // Check if the response contains an error message - if (releases.message) { - throw new Error(`GitHub API error: ${releases.message}`) - } + try { + const content = readFileSync(this.manifestPath, "utf-8") + const parsed = load(content) + if (parsed && typeof parsed === "object") { + return parsed as RendererManifest + } + } catch (error) { + this.logger.warn("Failed to read renderer manifest from disk", error) + } - // Ensure releases is an array - if (!Array.isArray(releases)) { - throw new TypeError("Invalid response format from GitHub API") - } - - // Filter for desktop releases and find the latest one - const desktopReleases = releases.filter( - (release: any) => release.tag_name && release.tag_name.startsWith("desktop/") && !release.draft, - ) - - if (desktopReleases.length === 0) { - throw new Error("No desktop releases found") - } - - // Sort by created_at date in descending order to get the most recent first - desktopReleases.sort((a: any, b: any) => { - return new Date(b.created_at).getTime() - new Date(a.created_at).getTime() - }) - - // Return the most recent desktop release - return desktopReleases[0].tag_name -} - -const getFileDownloadUrl = async (filename: string) => { - const tag = await getLatestReleaseTag() - - return `${releasesUrl}/download/${tag}/${filename}` -} - -type Manifest = { - /** Render version */ - version: string - hash: string - commit: string - filename: string - /** Only electron main hash equal to this value, renderer will can be updated */ - mainHash: string -} -const getLatestReleaseManifest = async () => { - const url = await getFileDownloadUrl("manifest.yml") - logger.info(`Fetching manifest from ${url}`) - const res = await fetch(url) - - if (!res.ok) { - logger.error(`Failed to fetch manifest: ${res.status} ${res.statusText}`) return null } - const text = await res.text() - const manifest = load(text) as Manifest - if (typeof manifest !== "object") { - logger.error("Invalid manifest", text) - return null - } - return manifest -} -const downloadTempDir = path.resolve(os.tmpdir(), "follow-render-update") - -export enum CanUpdateRenderState { - // If version is equal, no need to update - NO_NEEDED, - // Can be only update render layer, not fully upgrade app - NEEDED, - // App not support, should trigger app force update - APP_NOT_SUPPORT, - // Network error, can fetch manifest - NETWORK_ERROR, -} -export const canUpdateRender = async (): Promise<[CanUpdateRenderState, Manifest | null]> => { - const manifest = await getLatestReleaseManifest() - - logger.info("fetched manifest", manifest) - - if (!manifest) return [CanUpdateRenderState.NETWORK_ERROR, null] - - const appSupport = mainHash === manifest.mainHash - if (!appSupport) { - logger.info("app not support, should trigger app force update, app version: ", appVersion) - - return [CanUpdateRenderState.APP_NOT_SUPPORT, null] - } - - const isVersionEqual = appVersion === manifest.version - if (isVersionEqual) { - logger.info("version is equal, skip update") - return [CanUpdateRenderState.NO_NEEDED, null] - } - const isCommitEqual = GIT_COMMIT_HASH === manifest.commit - if (isCommitEqual) { - logger.info("commit is equal, skip update") - return [CanUpdateRenderState.NO_NEEDED, null] - } - - const manifestFilePath = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, "manifest.yml") - const manifestExist = existsSync(manifestFilePath) - - const oldManifest: Manifest | null = manifestExist - ? (load(readFileSync(manifestFilePath, "utf-8")) as Manifest) - : null - - if (oldManifest) { - if (oldManifest.version === manifest.version) { - logger.info("manifest version is equal, skip update") - return [CanUpdateRenderState.NO_NEEDED, null] + async cleanup(): Promise { + const manifest = this.getCurrentManifest() + if (!manifest) { + await rm(HOTUPDATE_RENDER_ENTRY_DIR, { recursive: true, force: true }) + return } - if (oldManifest.commit === manifest.commit) { - logger.info("manifest commit is equal, skip update") - return [CanUpdateRenderState.NO_NEEDED, null] + + const keepDir = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, manifest.version) + let entries: string[] = [] + + try { + entries = await readdir(HOTUPDATE_RENDER_ENTRY_DIR) + } catch (error) { + this.logger.warn("Failed to read renderer directory for cleanup", error) + return } - } - return [CanUpdateRenderState.NEEDED, manifest] -} -const downloadRenderAsset = async (manifest: Manifest) => { - const { filename } = manifest - const url = await getFileDownloadUrl(filename) - const filePath = path.resolve(downloadTempDir, filename) - logger.info(`Downloading ${url}, Save to ${filePath}`) - - const success = await downloadFileWithProgress({ - url, - outputPath: filePath, - expectedHash: manifest.hash, - - onLog: (message) => { - logger.info(message) - }, - }) - if (!success) throw new Error("Download hot update render asset failed") - return filePath -} -export const hotUpdateRender = async (manifest: Manifest) => { - if (!appUpdaterConfig.enableRenderHotUpdate) return false - - if (!manifest) return false - - const filePath = await downloadRenderAsset(manifest) - logger.info(`Downloaded render asset to ${filePath}`) - if (!filePath) return false - - // Extract the tar.gz file - await mkdir(HOTUPDATE_RENDER_ENTRY_DIR, { recursive: true }) - logger.info(`Extracting render asset to ${HOTUPDATE_RENDER_ENTRY_DIR}`) - await x({ - f: filePath, - cwd: HOTUPDATE_RENDER_ENTRY_DIR, - }) - - logger.info( - `Extracted render asset to ${HOTUPDATE_RENDER_ENTRY_DIR}, rename to ${manifest.version}`, - ) - - // Rename `renderer` folder to `manifest.version` - await rename( - path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, "renderer"), - path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, manifest.version), - ) - - const manifestPath = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, "manifest.yml") - logger.info(`Write manifest to ${manifestPath}`) - - await writeFile(manifestPath, JSON.stringify(manifest)) - logger.info(`Hot update render success, update to ${manifest.version}`) - - const mainWindow = WindowManager.getMainWindow() - if (!mainWindow) return false - const caller = callWindowExpose(mainWindow) - caller.readyToUpdate() - return true -} - -export const getCurrentRenderManifest = () => { - const manifestFilePath = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, "manifest.yml") - const manifestExist = existsSync(manifestFilePath) - if (!manifestExist) return null - return load(readFileSync(manifestFilePath, "utf-8")) as Manifest -} -export const cleanupOldRender = async () => { - const manifest = getCurrentRenderManifest() - if (!manifest) { - // Empty the directory - await rm(HOTUPDATE_RENDER_ENTRY_DIR, { recursive: true, force: true }) - return + await Promise.all( + entries.map(async (entryName) => { + const entryPath = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, entryName) + const entryStat = await stat(entryPath).catch(() => null) + if (!entryStat?.isDirectory()) return + if (entryPath === keepDir) return + await rm(entryPath, { recursive: true, force: true }) + }), + ) } - const currentRenderVersion = manifest.version - // Clean all not current version - const dirs = await readdir(HOTUPDATE_RENDER_ENTRY_DIR) - for (const dir of dirs) { - const isDir = (await stat(path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, dir))).isDirectory() - if (!isDir) continue - if (dir === currentRenderVersion) continue - await rm(path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, dir), { recursive: true, force: true }) + loadDynamicEntry() { + if (!appUpdaterConfig.enableRenderHotUpdate) return + + const manifest = this.getCurrentManifest() + if (!manifest) return + if (manifest.mainHash && manifest.mainHash !== mainHash) return + + const dir = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, manifest.version) + const entryFile = path.resolve(dir, "index.html") + if (!existsSync(entryFile)) return + + return entryFile + } + + private async downloadArchive(manifest: RendererManifest) { + const archivePath = path.resolve(this.tempDir, manifest.filename) + + this.logger.info( + `Downloading renderer bundle ${manifest.filename} from ${manifest.downloadUrl}`, + ) + + const success = await downloadFileWithProgress({ + url: manifest.downloadUrl, + outputPath: archivePath, + expectedHash: manifest.hash, + onLog: (message) => this.logger.info(message), + }) + + if (!success) { + throw new Error("Failed to download renderer bundle") + } + + return archivePath + } + + private async writeManifest(manifest: RendererManifest) { + await writeFile(this.manifestPath, dump(manifest), "utf-8") } } -export const loadDynamicRenderEntry = () => { - if (!appUpdaterConfig.enableRenderHotUpdate) return - const manifest = getCurrentRenderManifest() - if (!manifest) return - // check main hash is equal to manifest.mainHash - const appSupport = mainHash === manifest.mainHash - if (!appSupport) return +export const rendererUpdater = new RendererHotUpdater() - const currentRenderVersion = manifest.version - const dir = path.resolve(HOTUPDATE_RENDER_ENTRY_DIR, currentRenderVersion) - const entryFile = path.resolve(dir, "index.html") - const entryFileExists = existsSync(entryFile) +export const extractRendererManifest = (payload: LatestReleasePayload | null) => + rendererUpdater.extractManifest(payload) - if (!entryFileExists) return - return entryFile +export const evaluateRendererUpdate = (manifest: RendererManifest | null) => + rendererUpdater.evaluateManifest(manifest) + +export const applyRendererUpdate = async (manifest: RendererManifest) => { + await rendererUpdater.applyManifest(manifest) } + +export const getCurrentRendererManifest = () => rendererUpdater.getCurrentManifest() + +export const cleanupOldRenderer = async () => { + await rendererUpdater.cleanup() +} + +export const cleanupOldRender = cleanupOldRenderer + +export const loadDynamicRenderEntry = () => rendererUpdater.loadDynamicEntry() diff --git a/apps/desktop/layer/main/src/updater/index.ts b/apps/desktop/layer/main/src/updater/index.ts index b12af4ba4..cada25f78 100644 --- a/apps/desktop/layer/main/src/updater/index.ts +++ b/apps/desktop/layer/main/src/updater/index.ts @@ -1,189 +1,352 @@ +import { fileURLToPath } from "node:url" + import { callWindowExpose } from "@follow/shared/bridge" import { DEV } from "@follow/shared/constants" +import type { LatestReleasePayload, PlatformUpdate } from "@follow-app/client-sdk" +import type { AppUpdater } from "electron-updater" import { autoUpdater as defaultAutoUpdater } from "electron-updater" +import { join } from "pathe" -import { GITHUB_OWNER, GITHUB_REPO } from "~/constants/app" import { WindowManager } from "~/manager/window" -import { canUpdateRender, CanUpdateRenderState, hotUpdateRender } from "~/updater/hot-updater" +import type { RendererManifest } from "~/updater/hot-updater" +import { RendererEligibilityStatus, rendererUpdater } from "~/updater/hot-updater" import { channel, isWindows } from "../env" import { logger } from "../logger" +import { getUpdateInfo } from "./api" import { appUpdaterConfig } from "./configs" -import { CustomGitHubProvider } from "./custom-github-provider" +import { FollowUpdateProvider } from "./follow-update-provider" import { WindowsUpdater } from "./windows-updater" -// skip auto update in dev mode -// const disabled = DEV -const disabled = !appUpdaterConfig.enableAppUpdate - -const autoUpdater = isWindows ? new WindowsUpdater() : defaultAutoUpdater - -export const quitAndInstall = () => { - const mainWindow = WindowManager.getMainWindow() - logger.info("Quit and install update, close main window, ", mainWindow?.id) - WindowManager.destroyMainWindow() - - setTimeout(() => { - logger.info("Window is closed, quit and install update") - autoUpdater.quitAndInstall() - }, 1000) +type UpdateCheckOptions = { + refresh?: boolean } -let downloading = false -let checkingUpdate = false - -const checkRenderUpdateAvailable = async () => { - const [state, manifest] = await canUpdateRender() - if (state === CanUpdateRenderState.NEEDED && manifest) { - return true - } - return false +type UpdateCheckResult = { + hasUpdate: boolean + error?: string } -const upgradeRenderIfNeeded = async () => { - const [state, manifest] = await canUpdateRender() - if (state === CanUpdateRenderState.NO_NEEDED) { - return { upgraded: false } - } - if (state === CanUpdateRenderState.NEEDED && manifest) { - await hotUpdateRender(manifest) - return { upgraded: true } - } - return { upgraded: false } -} -export const checkForAppUpdates = async (): Promise<{ hasUpdate: boolean; error?: string }> => { - if (disabled || checkingUpdate) { - return { hasUpdate: false } +class FollowUpdater { + private readonly disabled: boolean + private checkingUpdate = false + private downloadingUpdate = false + + private pollingTimer: NodeJS.Timeout | null = null + + constructor( + private readonly autoUpdater: AppUpdater, + private readonly renderer = rendererUpdater, + ) { + this.disabled = !appUpdaterConfig.enableAppUpdate } - checkingUpdate = true - try { - let hasUpdate = false - - if (appUpdaterConfig.enableRenderHotUpdate) { - const hasRenderUpdate = await checkRenderUpdateAvailable() - if (hasRenderUpdate) { - hasUpdate = true - - // Auto upgrade renderer - upgradeRenderIfNeeded() - return { hasUpdate } - } + register() { + if (this.disabled) { + logger.info("App auto-update disabled; updater not registered") + return } - // Check for core app updates - if (appUpdaterConfig.enableCoreUpdate) { - const result = await autoUpdater.checkForUpdates() - if (result !== null && result.updateInfo !== null) { - hasUpdate = true - } + this.autoUpdater.autoDownload = false + this.autoUpdater.allowPrerelease = channel !== "stable" + this.autoUpdater.autoInstallOnAppQuit = true + this.autoUpdater.autoRunAppAfterInstall = true + this.autoUpdater.forceDevUpdateConfig = DEV + + if (import.meta.env.DEV) { + const __dirname = fileURLToPath(new URL(".", import.meta.url)) + this.autoUpdater.updateConfigPath = join(__dirname, "../../dev-only/dev-app-update.yml") } - return { hasUpdate } - } catch (e) { - logger.error("Error checking for updates", e) - return { hasUpdate: false, error: e instanceof Error ? e.message : "Unknown error" } - } finally { - checkingUpdate = false - } -} + this.autoUpdater.setFeedURL({ + provider: "custom", + updateProvider: FollowUpdateProvider, + }) -export const downloadAppUpdate = async () => { - if (disabled || downloading) { - return - } - downloading = true - autoUpdater.downloadUpdate().catch((e) => { - downloading = false - logger.error("Failed to download update", e) - }) - logger.info("Update available, downloading...") - return -} + this.registerAutoUpdaterEvents() -export const registerUpdater = async () => { - if (disabled) { - return - } + if (appUpdaterConfig.app.autoCheckUpdate) { + void this.checkForUpdates().catch((error) => + logger.error("Initial update check failed", error), + ) + } - // Disable there, control this in event - autoUpdater.autoDownload = false - autoUpdater.allowPrerelease = channel !== "stable" - autoUpdater.autoInstallOnAppQuit = true - autoUpdater.autoRunAppAfterInstall = true + if (this.pollingTimer) { + clearInterval(this.pollingTimer) + } - const feedUrl: Exclude[0], string> = { - channel, - // hack for custom provider - provider: "custom" as "github", - repo: GITHUB_REPO, - owner: GITHUB_OWNER, - releaseType: channel === "stable" ? "release" : "prerelease", - // @ts-expect-error hack for custom provider - updateProvider: CustomGitHubProvider, - } - - logger.debug("auto-updater feed config", { - ...feedUrl, - updateProvider: undefined, - }) - - autoUpdater.setFeedURL(feedUrl) - - // register events for checkForUpdates - autoUpdater.on("checking-for-update", () => { - logger.info("Checking for update") - }) - autoUpdater.on("update-available", async (info) => { - logger.info("Update available", info) - - // The app hotfix strategy is as follows: - // Determine whether the app should be updated in full or only the renderer layer based on the version number. - // https://www.notion.so/rss3/Follow-Hotfix-Electron-Renderer-layer-RFC-fe2444b9ac194c2cb38f9fa0bb1ef3c1?pvs=4#12e35ea049b480f1b268f1e605d86a62 - if (appUpdaterConfig.enableRenderHotUpdate) { - const renderResult = await upgradeRenderIfNeeded() - if (renderResult.upgraded) { + this.pollingTimer = setInterval(() => { + if (!appUpdaterConfig.app.autoCheckUpdate) { return } - } - if (appUpdaterConfig.app.autoDownloadUpdate && appUpdaterConfig.enableCoreUpdate) { - downloadAppUpdate().catch((err) => { - logger.error(err) + void this.checkForUpdates().catch((error) => { + logger.error("Scheduled update check failed", error) }) - } - }) - autoUpdater.on("update-not-available", (info) => { - logger.info("Update not available", info) - }) - autoUpdater.on("download-progress", (e) => { - logger.info(`Download progress: ${e.percent}`) - }) - autoUpdater.on("update-downloaded", () => { - downloading = false - logger.info("Update downloaded, ready to install") + }, appUpdaterConfig.app.checkUpdateInterval) + } + async checkForUpdates(options: UpdateCheckOptions = {}): Promise { + if (this.disabled) { + return { hasUpdate: false } + } + + if (this.checkingUpdate) { + logger.info("Update check already in progress, skipping") + return { hasUpdate: false } + } + + this.checkingUpdate = true + + try { + const payload = await getUpdateInfo(options.refresh ? { refresh: true } : {}) + const { decision } = payload + + if (!decision || decision.type === "none") { + logger.info("Update decision: none") + return { hasUpdate: false } + } + + if (decision.type === "renderer") { + logger.info("Update decision: renderer") + return await this.handleRendererDecision(payload) + } + + if (decision.type === "app") { + logger.info("Update decision: app") + return await this.handleAppDecision(payload) + } + + logger.warn("Unknown update decision type", { type: decision.type }) + return { hasUpdate: false } + } catch (error) { + logger.error("Failed to check for updates", error) + return { hasUpdate: false, error: error instanceof Error ? error.message : "Unknown error" } + } finally { + this.checkingUpdate = false + } + } + + async downloadAppUpdate(): Promise { + if (this.disabled || this.downloadingUpdate) { + return + } + + this.downloadingUpdate = true + + try { + await this.autoUpdater.downloadUpdate() + logger.info("App update download requested") + } catch (error) { + this.downloadingUpdate = false + logger.error("Failed to download app update", error) + throw error + } + } + + quitAndInstall() { const mainWindow = WindowManager.getMainWindow() - if (!mainWindow) return - const handlers = callWindowExpose(mainWindow) + logger.info("Quit and install triggered", { windowId: mainWindow?.id }) + WindowManager.destroyMainWindow() - handlers.updateDownloaded() - }) - autoUpdater.on("error", (e) => { - logger.error("Error while updating client", e) - }) - autoUpdater.forceDevUpdateConfig = DEV + setTimeout(() => { + logger.info("Main window closed, quitting to install update") + this.autoUpdater.quitAndInstall() + }, 1000) + } - setInterval(() => { - if (appUpdaterConfig.app.autoCheckUpdate) { - checkForAppUpdates().catch((err) => { - logger.error("Error checking for updates", err) - }) + private resolvePlatformCandidates() { + const base = new Set() + base.add(process.platform) + base.add(`${process.platform}-${process.arch}`) + base.add(process.arch) + + if (process.platform === "darwin") { + base.add("mac") + base.add("macos") } - }, appUpdaterConfig.app.checkUpdateInterval) - if (appUpdaterConfig.app.autoCheckUpdate) { - checkForAppUpdates().catch((err) => { - logger.error("Error checking for updates", err) + + if (process.platform === "win32") { + base.add("windows") + base.add("win") + } + + return Array.from(base).map((value) => value.toLowerCase()) + } + + private pickPlatformUpdate( + platforms: PlatformUpdate[] | null | undefined, + selected?: PlatformUpdate | null, + ): PlatformUpdate | null { + if (!platforms || platforms.length === 0) { + return null + } + + if (selected) { + return selected + } + + const candidates = this.resolvePlatformCandidates() + + const matched = platforms.find((platform) => + candidates.includes(platform.platform.toLowerCase()), + ) + + return matched ?? platforms[0] ?? null + } + + private async handleAppDecision(payload: LatestReleasePayload): Promise { + const appDecision = payload.decision.app + if (!appUpdaterConfig.enableCoreUpdate) { + logger.info("Core app update disabled by configuration") + return { hasUpdate: false } + } + + if (!appDecision) { + logger.warn("App update decision missing app payload") + return { hasUpdate: false, error: "App update metadata unavailable" } + } + + const platformUpdate = this.pickPlatformUpdate( + appDecision.platforms, + appDecision.selectedPlatform, + ) + if (!platformUpdate) { + logger.warn("No matching platform update found", { + platform: process.platform, + arch: process.arch, + }) + return { hasUpdate: false, error: "No installer available for this platform" } + } + + FollowUpdateProvider.setContext({ payload, platform: platformUpdate }) + logger.info("FollowUpdateProvider context set", { platform: platformUpdate.platform }) + + try { + await this.autoUpdater.checkForUpdates() + } catch (error) { + logger.warn( + "autoUpdater.checkForUpdates failed after preparing FollowUpdateProvider context", + error, + ) + return { + hasUpdate: false, + error: error instanceof Error ? error.message : "Failed to check app update", + } + } finally { + FollowUpdateProvider.clearContext() + } + + return { hasUpdate: true } + } + + private async handleRendererDecision(payload: LatestReleasePayload): Promise { + if (!appUpdaterConfig.enableRenderHotUpdate) { + logger.info("Renderer hot update disabled; falling back to app decision if present") + if (payload.decision.app) { + return this.handleAppDecision(payload) + } + return { hasUpdate: false } + } + + const manifest = this.renderer.extractManifest(payload) + const eligibility = this.renderer.evaluateManifest(manifest) + + switch (eligibility.status) { + case RendererEligibilityStatus.NoManifest: { + return { hasUpdate: false, error: eligibility.reason } + } + + case RendererEligibilityStatus.AlreadyCurrent: { + if (eligibility.reason) { + logger.info(eligibility.reason) + } + return { hasUpdate: false } + } + + case RendererEligibilityStatus.RequiresFullAppUpdate: { + logger.info( + eligibility.reason || + "Renderer payload requires main process update, delegating to app updater", + ) + if (payload.decision.app) { + return this.handleAppDecision(payload) + } + logger.warn("Renderer update requested full app upgrade but no app payload provided") + return { hasUpdate: false, error: "Renderer update requires full app upgrade" } + } + + case RendererEligibilityStatus.Eligible: { + const manifestToApply = eligibility.manifest as RendererManifest | undefined + if (!manifestToApply) { + return { hasUpdate: false } + } + + try { + await this.renderer.applyManifest(manifestToApply) + return { hasUpdate: true } + } catch (error) { + logger.error("Renderer hot update failed", error) + return { + hasUpdate: false, + error: error instanceof Error ? error.message : "Renderer hot update failed", + } + } + } + + default: { + return { hasUpdate: false } + } + } + } + + private registerAutoUpdaterEvents() { + this.autoUpdater.on("checking-for-update", () => { + logger.info("autoUpdater: checking for update") + }) + + this.autoUpdater.on("update-available", (info) => { + logger.info("autoUpdater: update available", info) + + if (appUpdaterConfig.app.autoDownloadUpdate && appUpdaterConfig.enableCoreUpdate) { + void this.downloadAppUpdate().catch((error) => + logger.error("Automatic download failed", error), + ) + } + }) + + this.autoUpdater.on("update-not-available", (info) => { + logger.info("autoUpdater: update not available", info) + }) + + this.autoUpdater.on("download-progress", (progress) => { + logger.info(`autoUpdater: download progress ${progress.percent.toFixed(2)}%`) + }) + + this.autoUpdater.on("update-downloaded", () => { + this.downloadingUpdate = false + logger.info("autoUpdater: update downloaded") + + const mainWindow = WindowManager.getMainWindow() + if (!mainWindow) return + + callWindowExpose(mainWindow).updateDownloaded() + }) + + this.autoUpdater.on("error", (error) => { + logger.error("autoUpdater: error", error) }) } } + +const autoUpdater = isWindows ? new WindowsUpdater() : defaultAutoUpdater +const followUpdater = new FollowUpdater(autoUpdater) + +export const registerUpdater = () => { + followUpdater.register() +} + +export const checkForAppUpdates = (options: UpdateCheckOptions = {}) => + followUpdater.checkForUpdates(options) + +export const quitAndInstall = () => followUpdater.quitAndInstall() diff --git a/apps/desktop/layer/main/src/updater/types.ts b/apps/desktop/layer/main/src/updater/types.ts deleted file mode 100644 index 7a9cb54d7..000000000 --- a/apps/desktop/layer/main/src/updater/types.ts +++ /dev/null @@ -1,91 +0,0 @@ -export interface GitHubReleasesItem { - url: string - assets_url: string - upload_url: string - html_url: string - id: number - author: Author - node_id: string - tag_name: string - target_commitish: string - name: string - draft: boolean - prerelease: boolean - created_at: string - published_at: string - assets: AssetsItem[] - tarball_url: string - zipball_url: string - body: string - reactions?: Reactions - mentions_count?: number -} -interface Author { - login: string - id: number - node_id: string - avatar_url: string - gravatar_id: string - url: string - html_url: string - followers_url: string - following_url: string - gists_url: string - starred_url: string - subscriptions_url: string - organizations_url: string - repos_url: string - events_url: string - received_events_url: string - type: string - user_view_type: string - site_admin: boolean -} -interface AssetsItem { - url: string - id: number - node_id: string - name: string - label: string | null - uploader: Uploader - content_type: string - state: string - size: number - download_count: number - created_at: string - updated_at: string - browser_download_url: string -} -interface Uploader { - login: string - id: number - node_id: string - avatar_url: string - gravatar_id: string - url: string - html_url: string - followers_url: string - following_url: string - gists_url: string - starred_url: string - subscriptions_url: string - organizations_url: string - repos_url: string - events_url: string - received_events_url: string - type: string - user_view_type: string - site_admin: boolean -} -interface Reactions { - url: string - total_count: number - "+1": number - "-1": number - laugh: number - hooray: number - confused: number - heart: number - rocket: number - eyes: number -} diff --git a/apps/desktop/layer/main/src/updater/utils.ts b/apps/desktop/layer/main/src/updater/utils.ts deleted file mode 100644 index d81bf1389..000000000 --- a/apps/desktop/layer/main/src/updater/utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -import fs from "node:fs" - -import { app } from "electron" -import path from "pathe" -import { major, minor } from "semver" - -let _isSquirrelBuild: boolean | null = null -export function isSquirrelBuild() { - if (typeof _isSquirrelBuild === "boolean") { - return _isSquirrelBuild - } - - // if it is squirrel build, there will be 'squirrel.exe' - // otherwise it is in nsis web mode - const files = fs.readdirSync(path.dirname(app.getPath("exe"))) - _isSquirrelBuild = files.some((it) => it.includes("squirrel.exe")) - - return _isSquirrelBuild -} - -// The following scenario only requires updating the renderer, so the app update is skipped: -// In x.y.z, the update of z will only trigger renderer hotfix, while the update of y requires updating the entire app. -// The hotfix version of x.y.z-beta.0 adds a suffix number. It triggers renderer update. If the main code is modified and the entire app update needs to be triggered, the hotfix version adds a suffix like x.y.z-beta.0.app. -// For subsequent minor versions that require updating the main code, the suffix .app needs to be added. - -export const shouldUpdateApp = (currentVersion: string, nextVersion: string) => { - if (nextVersion.includes("app")) { - return true - } - // x.y.z 's y or x not equal, need update app - const [x1, x2] = [safeMajor(currentVersion), safeMajor(nextVersion)] - const [y1, y2] = [safeMinor(currentVersion), safeMinor(nextVersion)] - - // Here, it is not determined whether it is a problem of version number downgrade; the updater will handle it automatically. - if (x1 !== x2 || y1 !== y2) { - return true - } - - return false -} - -const safeMajor = (version: string) => { - try { - return major(version) - } catch { - return "0.0.0" - } -} -const safeMinor = (version: string) => { - try { - return minor(version) - } catch { - return "0.0.0" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35c65d038..45fcbd1a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: default: '@follow-app/client-sdk': - specifier: 0.3.78 - version: 0.3.78 + specifier: 0.3.79 + version: 0.3.79 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.78(@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.79(@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.78(@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.79(@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 @@ -812,7 +812,7 @@ importers: version: 4.1.1(react@19.0.0) '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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) + version: 0.3.79(@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 @@ -1193,7 +1193,7 @@ importers: version: 6.2.1 '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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.79(@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 @@ -1688,7 +1688,7 @@ importers: dependencies: '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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.79(@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 @@ -1700,7 +1700,7 @@ importers: dependencies: '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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.79(@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 @@ -1772,7 +1772,7 @@ importers: dependencies: '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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.79(@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 @@ -1803,7 +1803,7 @@ importers: version: 2.0.0(@types/node@24.8.1) '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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.79(@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) @@ -1833,7 +1833,7 @@ importers: dependencies: '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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.79(@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 @@ -1882,7 +1882,7 @@ importers: devDependencies: '@follow-app/client-sdk': specifier: 'catalog:' - version: 0.3.78(@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.79(@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 @@ -4358,32 +4358,29 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@follow-app/client-sdk@0.3.78': - resolution: {integrity: sha512-I8SPfimOqNcQZrN03wolVxj7IrOje3IuIxA9CA7ulS9i2K0GNhw8k64bhSjTGutkATstdzxTkCJYfS7wn9F0dQ==} + '@follow-app/client-sdk@0.3.79': + resolution: {integrity: sha512-w3EQEPjZMI638vzdB/klrldYvoh1LYWX6+5pMpkeLzC48hARUOfGi7dC8sVBM20/EXgvP/Wjkve637tjB7mKeg==} '@folo-services/ai-tools@0.2.49': resolution: {integrity: sha512-QsKTW8N18812Thkdcc7wDTff3QkQxxTYHxHi1RM321jk4DbCq+mHlAr10Krwj0mRtKuULtqFHPm13udmfIv1Bw==} - '@folo-services/constants@0.1.41': - resolution: {integrity: sha512-fXJ5DVWMgc4czW8i9JmVx3WckKhUksiwXkzzOClH0GePEOeS+DQ08o21UfZl1pBvhvhZm0pS+klHLljeBaRb1g==} - - '@folo-services/constants@0.1.42': - resolution: {integrity: sha512-GoHKg4Wdr+rGYGQauOhVFz+VyHZSiW1/9AGpIUa173a+HM5btvFN6e+Tu8ZNKl6Mu8J5M5GKIL0ditROp9Xv+A==} + '@folo-services/constants@0.1.43': + resolution: {integrity: sha512-vBauScJcR97H7uus0UdQ+g4rLFksRfWTjzIg8GcV3HDHBJOqISE5fxGUYzqkbEpbD6QqofNguVYQQrcXcIPWfQ==} '@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/drizzle@0.1.36': + resolution: {integrity: sha512-7BTGJXDPE7m5cs9mZIiYbpHluqPPrUVV0MtZxxkmbve9BlIWDij1Yga4r43CpiFCOK/a0JqMcmkqmokRJfKVxw==} '@folo-services/exceptions@0.1.19': resolution: {integrity: sha512-rXgLssE3r3w2WzC3knCX9nYe0cszHXpYwAllrYOsPpzjQSHWRwJ6LuJyt0xlvdeX/9i8KQ/UYgf4to6Abt2whg==} - '@folo-services/exceptions@0.1.20': - resolution: {integrity: sha512-iuV6F22XI6kvSSVQRTCoS/wfJnPGvI3XQbA3YQGgXa8fT28SE+8LBGCyAj0rqbfnXlXdpxZE/IooXdpJyxkp0Q==} + '@folo-services/exceptions@0.1.21': + resolution: {integrity: sha512-bEgKOFKAf0INIJUMSHqdiOvEE3KV096fYk+DeO5em3YkGfPY/l4Fcy0XeOOKJJZJQbQak4Sry7extFbfSmlb/Q==} - '@folo-services/shared@0.0.34': - resolution: {integrity: sha512-T0gZRBlDVP9WLG0Dg8tLgWdk+TVjbdKfSvRHEyBY+YioQ1LO4vulN8ddCv1oBvMQxl0iESY9mnmB4ua9iGGkyQ==} + '@folo-services/shared@0.0.35': + resolution: {integrity: sha512-pvcuDEg82ZxgaUlwtx16DBV3TRsZuFI4g4DuNaRejnZTLnW8XswG56TZloe+OwsbHUUzh4mOZBVM3PYUVpeayA==} '@fontsource/sn-pro@5.2.5': resolution: {integrity: sha512-rBdBv/0ygj6bkO7xDMMFpwobLdSrcQ2Jncb6DIwdeYGoAgeWkQRwVYhGDKasfLjEYCNYxrqr6wsXsM9+aU39RA==} @@ -4926,9 +4923,6 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.0.7': - resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} - '@noble/ciphers@2.0.1': resolution: {integrity: sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==} engines: {node: '>= 20.19.0'} @@ -5272,8 +5266,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@oxc-project/types@0.95.0': - resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} + '@oxc-project/runtime@0.71.0': + resolution: {integrity: sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw==} + engines: {node: '>=6.9.0'} + + '@oxc-project/types@0.71.0': + resolution: {integrity: sha512-5CwQ4MI+P4MQbjLWXgNurA+igGwu/opNetIE13LBs9+V93R64MLvDKOOLZIXSzEfovU3Zef3q3GjPnMTgJTn2w==} '@peculiar/asn1-android@2.5.0': resolution: {integrity: sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A==} @@ -6231,98 +6229,75 @@ packages: resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} engines: {node: '>= 10'} - '@rolldown/binding-android-arm64@1.0.0-beta.45': - resolution: {integrity: sha512-bfgKYhFiXJALeA/riil908+2vlyWGdwa7Ju5S+JgWZYdR4jtiPOGdM6WLfso1dojCh+4ZWeiTwPeV9IKQEX+4g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-beta.45': - resolution: {integrity: sha512-xjCv4CRVsSnnIxTuyH1RDJl5OEQ1c9JYOwfDAHddjJDxCw46ZX9q80+xq7Eok7KC4bRSZudMJllkvOKv0T9SeA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-Mp0/gqiPdepHjjVm7e0yL1acWvI0rJVVFQEADSezvAjon9sjQ7CEg9JnXICD4B1YrPmN9qV/e7cQZCp87tTV4w==} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.45': - resolution: {integrity: sha512-ddcO9TD3D/CLUa/l8GO8LHzBOaZqWg5ClMy3jICoxwCuoz47h9dtqPsIeTiB6yR501LQTeDsjA4lIFd7u3Ljfw==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-40re4rMNrsi57oavRzIOpRGmg3QRlW6Ea8Q3znaqgOuJuKVrrm2bIQInTfkZJG7a4/5YMX7T951d0+toGLTdCA==} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.45': - resolution: {integrity: sha512-MBTWdrzW9w+UMYDUvnEuh0pQvLENkl2Sis15fHTfHVW7ClbGuez+RWopZudIDEGkpZXdeI4CkRXk+vdIIebrmg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-8BDM939bbMariZupiHp3OmP5N+LXPT4mULA0hZjDaq970PCxv4krZOSMG+HkWUUwmuQROtV+/00xw39EO0P+8g==} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.45': - resolution: {integrity: sha512-4YgoCFiki1HR6oSg+GxxfzfnVCesQxLF1LEnw9uXS/MpBmuog0EOO2rYfy69rWP4tFZL9IWp6KEfGZLrZ7aUog==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-sntsPaPgrECpBB/+2xrQzVUt0r493TMPI+4kWRMhvMsmrxOqH1Ep5lM0Wua/ZdbfZNwm1aVa5pcESQfNfM4Fhw==} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.45': - resolution: {integrity: sha512-LE1gjAwQRrbCOorJJ7LFr10s5vqYf5a00V5Ea9wXcT2+56n5YosJkcp8eQ12FxRBv2YX8dsdQJb+ZTtYJwb6XQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-5clBW/I+er9F2uM1OFjJFWX86y7Lcy0M+NqsN4s3o07W+8467Zk8oQa4B45vdaXoNUF/yqIAgKkA/OEdQDxZqA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': - resolution: {integrity: sha512-tdy8ThO/fPp40B81v0YK3QC+KODOmzJzSUOO37DinQxzlTJ026gqUSOM8tzlVixRbQJltgVDCTYF8HNPRErQTA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-wv+rnAfQDk9p/CheX8/Kmqk2o1WaFa4xhWI9gOyDMk/ljvOX0u0ubeM8nI1Qfox7Tnh71eV5AjzSePXUhFOyOg==} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': - resolution: {integrity: sha512-lS082ROBWdmOyVY/0YB3JmsiClaWoxvC+dA8/rbhyB9VLkvVEaihLEOr4CYmrMse151C4+S6hCw6oa1iewox7g==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-gxD0/xhU4Py47IH3bKZbWtvB99tMkUPGPJFRfSc5UB9Osoje0l0j1PPbxpUtXIELurYCqwLBKXIMTQGifox1BQ==} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': - resolution: {integrity: sha512-Hi73aYY0cBkr1/SvNQqH8Cd+rSV6S9RB5izCv0ySBcRnd/Wfn5plguUoGYwBnhHgFbh6cPw9m2dUVBR6BG1gxA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-HotuVe3XUjDwqqEMbm3o3IRkP9gdm8raY/btd/6KE3JGLF/cv4+3ff1l6nOhAZI8wulWDPEXPtE7v+HQEaTXnA==} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': - resolution: {integrity: sha512-fljEqbO7RHHogNDxYtTzr+GNjlfOx21RUyGmF+NrkebZ8emYYiIqzPxsaMZuRx0rgZmVmliOzEp86/CQFDKhJQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.45': - resolution: {integrity: sha512-ZJDB7lkuZE9XUnWQSYrBObZxczut+8FZ5pdanm8nNS1DAo8zsrPuvGwn+U3fwU98WaiFsNrA4XHngesCGr8tEQ==} - engines: {node: '>=14.0.0'} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-8Cx+ucbd8n2dIr21FqBh6rUvTVL0uTgEtKR7l+MUZ5BgY4dFh1e4mPVX8oqmoYwOxBiXrsD2JIOCz4AyKLKxWA==} + engines: {node: '>=14.21.3'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.45': - resolution: {integrity: sha512-zyzAjItHPUmxg6Z8SyRhLdXlJn3/D9KL5b9mObUrBHhWS/GwRH4665xCiFqeuktAhhWutqfc+rOV2LjK4VYQGQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-Vhq5vikrVDxAa75fxsyqj0c0Y/uti/TwshXI71Xb8IeUQJOBnmLUsn5dgYf5ljpYYkNa0z9BPAvUDIDMmyDi+w==} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.45': - resolution: {integrity: sha512-wODcGzlfxqS6D7BR0srkJk3drPwXYLu7jPHN27ce2c4PUnVVmJnp9mJzUQGT4LpmHmmVdMZ+P6hKvyTGBzc1CA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-lN7RIg9Iugn08zP2aZN9y/MIdG8iOOCE93M1UrFlrxMTqPf8X+fDzmR/OKhTSd1A2pYNipZHjyTcb5H8kyQSow==} cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': - resolution: {integrity: sha512-wiU40G1nQo9rtfvF9jLbl79lUgjfaD/LTyUEw2Wg/gdF5OhjzpKMVugZQngO+RNdwYaNj+Fs+kWBWfp4VXPMHA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-7/7cLIn48Y+EpQ4CePvf8reFl63F15yPUlg4ZAhl+RXJIfydkdak1WD8Ir3AwAO+bJBXzrfNL+XQbxm0mcQZmw==} cpu: [x64] os: [win32] '@rolldown/pluginutils@1.0.0-beta.38': resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} - '@rolldown/pluginutils@1.0.0-beta.45': - resolution: {integrity: sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA==} + '@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': + resolution: {integrity: sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ==} '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -14840,9 +14815,8 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.45: - resolution: {integrity: sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ==} - engines: {node: ^20.19.0 || >=22.12.0} + rolldown@1.0.0-beta.9-commit.d91dfb5: + resolution: {integrity: sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g==} hasBin: true rollup@2.79.2: @@ -21348,12 +21322,12 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@follow-app/client-sdk@0.3.78(@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.79(@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.42 - '@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) + '@folo-services/constants': 0.1.43 + '@folo-services/drizzle': 0.1.36(@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.21 + '@folo-services/shared': 0.0.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)(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: @@ -21397,12 +21371,12 @@ snapshots: - svelte - vue - '@follow-app/client-sdk@0.3.78(@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-app/client-sdk@0.3.79(@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.42 - '@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) + '@folo-services/constants': 0.1.43 + '@folo-services/drizzle': 0.1.36(@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.21 + '@folo-services/shared': 0.0.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)(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: @@ -21446,12 +21420,12 @@ snapshots: - svelte - vue - '@follow-app/client-sdk@0.3.78(@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.79(@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.42 - '@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) + '@folo-services/constants': 0.1.43 + '@folo-services/drizzle': 0.1.36(@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.21 + '@folo-services/shared': 0.0.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)(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: @@ -21495,12 +21469,12 @@ snapshots: - svelte - vue - '@follow-app/client-sdk@0.3.78(@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.79(@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.42 - '@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) + '@folo-services/constants': 0.1.43 + '@folo-services/drizzle': 0.1.36(@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.21 + '@folo-services/shared': 0.0.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)(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: @@ -21584,11 +21558,7 @@ snapshots: - sql.js - sqlite3 - '@folo-services/constants@0.1.41': - dependencies: - zod: 4.1.12 - - '@folo-services/constants@0.1.42': + '@folo-services/constants@0.1.43': dependencies: zod: 4.1.12 @@ -21674,9 +21644,9 @@ 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)': + '@folo-services/drizzle@0.1.36(@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 + '@folo-services/exceptions': 0.1.21 '@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) @@ -21715,9 +21685,9 @@ 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.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/drizzle@0.1.36(@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 + '@folo-services/exceptions': 0.1.21 '@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) @@ -21756,9 +21726,9 @@ 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.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/drizzle@0.1.36(@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 + '@folo-services/exceptions': 0.1.21 '@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) @@ -21797,9 +21767,9 @@ 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.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/drizzle@0.1.36(@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 + '@folo-services/exceptions': 0.1.21 '@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) @@ -21840,12 +21810,12 @@ snapshots: '@folo-services/exceptions@0.1.19': {} - '@folo-services/exceptions@0.1.20': {} + '@folo-services/exceptions@0.1.21': {} - '@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)': + '@folo-services/shared@0.0.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)(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.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.43 + '@folo-services/drizzle': 0.1.36(@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) @@ -21883,10 +21853,10 @@ snapshots: - 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.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)': + '@folo-services/shared@0.0.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)(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.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/constants': 0.1.43 + '@folo-services/drizzle': 0.1.36(@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) @@ -21924,10 +21894,10 @@ snapshots: - 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)': + '@folo-services/shared@0.0.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)(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) + '@folo-services/constants': 0.1.43 + '@folo-services/drizzle': 0.1.36(@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) @@ -21965,10 +21935,10 @@ snapshots: - 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)))(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.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)(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)))(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.43 + '@folo-services/drizzle': 0.1.36(@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) @@ -22732,13 +22702,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.0.7': - dependencies: - '@emnapi/core': 1.6.0 - '@emnapi/runtime': 1.6.0 - '@tybys/wasm-util': 0.10.1 - optional: true - '@noble/ciphers@2.0.1': {} '@noble/hashes@2.0.1': {} @@ -23212,7 +23175,9 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@oxc-project/types@0.95.0': {} + '@oxc-project/runtime@0.71.0': {} + + '@oxc-project/types@0.71.0': {} '@peculiar/asn1-android@2.5.0': dependencies: @@ -24368,7 +24333,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)(utf-8-validate@6.0.5) + metro-config: 0.82.4(bufferutil@4.0.9) metro-core: 0.82.4 semver: 7.7.3 transitivePeerDependencies: @@ -24535,53 +24500,47 @@ snapshots: '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 '@resvg/resvg-js-win32-x64-msvc': 2.6.2 - '@rolldown/binding-android-arm64@1.0.0-beta.45': + '@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.45': + '@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.45': + '@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.45': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.45': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.45': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.45': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5': dependencies: - '@napi-rs/wasm-runtime': 1.0.7 + '@napi-rs/wasm-runtime': 0.2.12 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.45': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.45': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5': optional: true '@rolldown/pluginutils@1.0.0-beta.38': {} - '@rolldown/pluginutils@1.0.0-beta.45': {} + '@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@2.79.2)': dependencies: @@ -32503,22 +32462,6 @@ 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 @@ -32704,7 +32647,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)(utf-8-validate@6.0.5) + metro-config: 0.82.4(bufferutil@4.0.9) metro-core: 0.82.4 metro-file-map: 0.82.4 metro-resolver: 0.82.4 @@ -35274,7 +35217,7 @@ snapshots: sprintf-js: 1.1.3 optional: true - rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.45)(typescript@5.9.3): + rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.9-commit.d91dfb5)(typescript@5.9.3): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.4 @@ -35285,32 +35228,32 @@ snapshots: dts-resolver: 2.1.2 get-tsconfig: 4.12.0 magic-string: 0.30.19 - rolldown: 1.0.0-beta.45 + rolldown: 1.0.0-beta.9-commit.d91dfb5 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - oxc-resolver - supports-color - rolldown@1.0.0-beta.45: + rolldown@1.0.0-beta.9-commit.d91dfb5: dependencies: - '@oxc-project/types': 0.95.0 - '@rolldown/pluginutils': 1.0.0-beta.45 + '@oxc-project/runtime': 0.71.0 + '@oxc-project/types': 0.71.0 + '@rolldown/pluginutils': 1.0.0-beta.9-commit.d91dfb5 + ansis: 4.2.0 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.45 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.45 - '@rolldown/binding-darwin-x64': 1.0.0-beta.45 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.45 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.45 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.45 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.45 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.45 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.45 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.45 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.45 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.45 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.45 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.45 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-darwin-x64': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9-commit.d91dfb5 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9-commit.d91dfb5 rollup@2.79.2: optionalDependencies: @@ -36441,8 +36384,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.45 - rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.45)(typescript@5.9.3) + rolldown: 1.0.0-beta.9-commit.d91dfb5 + rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.9-commit.d91dfb5)(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.1 tinyglobby: 0.2.15 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 895053cbe..2ada2583a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,7 +8,7 @@ packages: - "!**/example/**" catalog: - "@follow-app/client-sdk": 0.3.78 + "@follow-app/client-sdk": 0.3.79 tailwindcss-uikit-colors: 1.0.0 typescript: 5.9.3